Guarantee that PAC Local Proxy owns Port

This changes the PAC support to not broadcast the Proxy information until
the Local Proxy has started up and successfully bound to a port so that
the local proxy information can be guaranteed to be owned by the proxy.

Bug: 10459877
Change-Id: I175cd3388c758c55e341115e4a8241884b90d633
diff --git a/Android.mk b/Android.mk
index 30b17f5..6d4a763 100644
--- a/Android.mk
+++ b/Android.mk
@@ -266,6 +266,8 @@
 	wifi/java/android/net/wifi/IWifiManager.aidl \
 	wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
 	packages/services/PacProcessor/com/android/net/IProxyService.aidl \
+	packages/services/Proxy/com/android/net/IProxyCallback.aidl \
+	packages/services/Proxy/com/android/net/IProxyPortListener.aidl \
 
 # FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk
 LOCAL_AIDL_INCLUDES += $(FRAMEWORKS_BASE_JAVA_SRC_DIRS)
diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java
index 76aea9f..648a4b3 100644
--- a/core/java/android/net/ProxyProperties.java
+++ b/core/java/android/net/ProxyProperties.java
@@ -38,7 +38,7 @@
 
     private String mPacFileUrl;
     public static final String LOCAL_EXCL_LIST = "";
-    public static final int LOCAL_PORT = 8182;
+    public static final int LOCAL_PORT = -1;
     public static final String LOCAL_HOST = "localhost";
 
     public ProxyProperties(String host, int port, String exclList) {
@@ -54,6 +54,14 @@
         mPacFileUrl = pacFileUrl;
     }
 
+    // Only used in PacManager after Local Proxy is bound.
+    public ProxyProperties(String pacFileUrl, int localProxyPort) {
+        mHost = LOCAL_HOST;
+        mPort = localProxyPort;
+        setExclusionList(LOCAL_EXCL_LIST);
+        mPacFileUrl = pacFileUrl;
+    }
+
     private ProxyProperties(String host, int port, String exclList, String[] parsedExclList) {
         mHost = host;
         mPort = port;
diff --git a/packages/services/Proxy/com/android/net/IProxyCallback.aidl b/packages/services/Proxy/com/android/net/IProxyCallback.aidl
new file mode 100644
index 0000000..26b2a3f
--- /dev/null
+++ b/packages/services/Proxy/com/android/net/IProxyCallback.aidl
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.net;
+
+/** @hide */
+interface IProxyCallback
+{
+    oneway void getProxyPort(IBinder callback);
+}
diff --git a/packages/services/Proxy/com/android/net/IProxyPortListener.aidl b/packages/services/Proxy/com/android/net/IProxyPortListener.aidl
new file mode 100644
index 0000000..fa4caf3
--- /dev/null
+++ b/packages/services/Proxy/com/android/net/IProxyPortListener.aidl
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.net;
+
+/** @hide */
+interface IProxyPortListener
+{
+    oneway void setProxyPort(int port);
+}
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
index 77f3c8c..4bf1db8 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
@@ -15,8 +15,11 @@
  */
 package com.android.proxyhandler;
 
+import android.net.ProxyProperties;
+import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.net.IProxyPortListener;
 import com.google.android.collect.Lists;
 
 import java.io.IOException;
@@ -49,6 +52,8 @@
     public boolean mIsRunning = false;
 
     private ServerSocket serverSocket;
+    private int mPort;
+    private IProxyPortListener mCallback;
 
     private class ProxyConnection implements Runnable {
         private Socket connection;
@@ -179,33 +184,59 @@
 
     public ProxyServer() {
         threadExecutor = Executors.newCachedThreadPool();
+        mPort = -1;
+        mCallback = null;
     }
 
     @Override
     public void run() {
         try {
-            serverSocket = new ServerSocket(ProxyService.PORT);
+            serverSocket = new ServerSocket(0);
 
-            serverSocket.setReuseAddress(true);
+            if (serverSocket != null) {
+                setPort(serverSocket.getLocalPort());
 
-            while (mIsRunning) {
-                try {
-                    ProxyConnection parser = new ProxyConnection(serverSocket.accept());
+                while (mIsRunning) {
+                    try {
+                        ProxyConnection parser = new ProxyConnection(serverSocket.accept());
 
-                    threadExecutor.execute(parser);
-                } catch (IOException e) {
-                    e.printStackTrace();
+                        threadExecutor.execute(parser);
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
                 }
             }
         } catch (SocketException e) {
-            e.printStackTrace();
-        } catch (IOException e) {
-            e.printStackTrace();
+            Log.e(TAG, "Failed to start proxy server", e);
+        } catch (IOException e1) {
+            Log.e(TAG, "Failed to start proxy server", e1);
         }
 
         mIsRunning = false;
     }
 
+    public synchronized void setPort(int port) {
+        if (mCallback != null) {
+            try {
+                mCallback.setProxyPort(port);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Proxy failed to report port to PacManager", e);
+            }
+        }
+        mPort = port;
+    }
+
+    public synchronized void setCallback(IProxyPortListener callback) {
+        if (mPort != -1) {
+            try {
+                callback.setProxyPort(mPort);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Proxy failed to report port to PacManager", e);
+            }
+        }
+        mCallback = callback;
+    }
+
     public synchronized void startServer() {
         mIsRunning = true;
         start();
@@ -222,4 +253,12 @@
             }
         }
     }
+
+    public boolean isBound() {
+        return (mPort != -1);
+    }
+
+    public int getPort() {
+        return mPort;
+    }
 }
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
index cef3659..109435c 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
@@ -21,8 +21,12 @@
 import android.net.ProxyProperties;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.text.TextUtils;
 
+import com.android.net.IProxyCallback;
+import com.android.net.IProxyPortListener;
+
 /**
  * @hide
  */
@@ -56,6 +60,16 @@
 
     @Override
     public IBinder onBind(Intent intent) {
-        return null;
+        return new IProxyCallback.Stub() {
+            @Override
+            public void getProxyPort(IBinder callback) throws RemoteException {
+                if (server != null) {
+                    IProxyPortListener portListener = IProxyPortListener.Stub.asInterface(callback);
+                    if (portListener != null) {
+                        server.setCallback(portListener);
+                    }
+                }
+            }
+        };
     }
 }
\ No newline at end of file
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 4e3faca..47f18e7 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -114,7 +114,6 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
-import com.android.net.IProxyService;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.connectivity.DataConnectionStats;
 import com.android.server.connectivity.Nat464Xlat;
@@ -3471,7 +3470,7 @@
 
     private void sendProxyBroadcast(ProxyProperties proxy) {
         if (proxy == null) proxy = new ProxyProperties("", 0, "");
-        mPacManager.setCurrentProxyScriptUrl(proxy);
+        if (mPacManager.setCurrentProxyScriptUrl(proxy)) return;
         if (DBG) log("sending Proxy Broadcast for " + proxy);
         Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
diff --git a/services/java/com/android/server/connectivity/PacManager.java b/services/java/com/android/server/connectivity/PacManager.java
index c8cc85e..772921a 100644
--- a/services/java/com/android/server/connectivity/PacManager.java
+++ b/services/java/com/android/server/connectivity/PacManager.java
@@ -24,17 +24,22 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.net.Proxy;
 import android.net.ProxyProperties;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.net.IProxyCallback;
+import com.android.net.IProxyPortListener;
 import com.android.net.IProxyService;
 import com.android.server.IoThread;
 
@@ -79,6 +84,7 @@
     private Context mContext;
 
     private int mCurrentDelay;
+    private int mLastPort;
 
     /**
      * Used for locking when setting mProxyService and all references to mPacUrl or mCurrentPac.
@@ -119,6 +125,7 @@
 
     public PacManager(Context context) {
         mContext = context;
+        mLastPort = -1;
 
         mPacRefreshIntent = PendingIntent.getBroadcast(
                 context, 0, new Intent(ACTION_PAC_REFRESH), 0);
@@ -133,7 +140,16 @@
         return mAlarmManager;
     }
 
-    public synchronized void setCurrentProxyScriptUrl(ProxyProperties proxy) {
+    /**
+     * Updates the PAC Manager with current Proxy information. This is called by
+     * the ConnectivityService directly before a broadcast takes place to allow
+     * the PacManager to indicate that the broadcast should not be sent and the
+     * PacManager will trigger a new broadcast when it is ready.
+     *
+     * @param proxy Proxy information that is about to be broadcast.
+     * @return Returns true when the broadcast should not be sent
+     */
+    public synchronized boolean setCurrentProxyScriptUrl(ProxyProperties proxy) {
         if (!TextUtils.isEmpty(proxy.getPacFileUrl())) {
             synchronized (mProxyLock) {
                 mPacUrl = proxy.getPacFileUrl();
@@ -141,6 +157,7 @@
             mCurrentDelay = DELAY_1;
             getAlarmManager().cancel(mPacRefreshIntent);
             bind();
+            return true;
         } else {
             getAlarmManager().cancel(mPacRefreshIntent);
             synchronized (mProxyLock) {
@@ -156,6 +173,7 @@
                     }
                 }
             }
+            return false;
         }
     }
 
@@ -233,6 +251,16 @@
         }
         Intent intent = new Intent();
         intent.setClassName(PAC_PACKAGE, PAC_SERVICE);
+        // Already bound no need to bind again.
+        if (mProxyConnection != null) {
+            if (mLastPort != -1) {
+                sendPacBroadcast(new ProxyProperties(mPacUrl, mLastPort));
+            } else {
+                Log.e(TAG, "Received invalid port from Local Proxy,"
+                        + " PAC will not be operational");
+            }
+            return;
+        }
         mConnection = new ServiceConnection() {
             @Override
             public void onServiceDisconnected(ComponentName component) {
@@ -277,6 +305,26 @@
 
             @Override
             public void onServiceConnected(ComponentName component, IBinder binder) {
+                IProxyCallback callbackService = IProxyCallback.Stub.asInterface(binder);
+                if (callbackService != null) {
+                    try {
+                        callbackService.getProxyPort(new IProxyPortListener.Stub() {
+                            @Override
+                            public void setProxyPort(int port) throws RemoteException {
+                                mLastPort = port;
+                                if (port != -1) {
+                                    Log.d(TAG, "Local proxy is bound on " + port);
+                                    sendPacBroadcast(new ProxyProperties(mPacUrl, port));
+                                } else {
+                                    Log.e(TAG, "Received invalid port from Local Proxy,"
+                                            + " PAC will not be operational");
+                                }
+                            }
+                        });
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                }
             }
         };
         mContext.bindService(intent, mProxyConnection,
@@ -287,5 +335,19 @@
         mContext.unbindService(mConnection);
         mContext.unbindService(mProxyConnection);
         mConnection = null;
+        mProxyConnection = null;
+    }
+
+    private void sendPacBroadcast(ProxyProperties proxy) {
+        Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
+            Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
     }
 }