Wifi AP framework changes first pass

Bug: 2421638
Change-Id: Ic5ea8f7560a7fe5e1b0769daa5d92cc33eefc692
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 92041d8..d1d8b0a 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -162,4 +162,15 @@
      * Check the status of USB RNDIS support
      */
     boolean isUsbRNDISStarted();
+
+    /**
+     * Start Wifi Access Point
+     */
+    void startAccessPoint();
+
+    /**
+     * Stop Wifi Access Point
+     */
+    void stopAccessPoint();
+
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 18e2647..8f410a9 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2386,6 +2386,13 @@
         public static final String WIFI_ON = "wifi_on";
 
         /**
+         * Whether the Wi-Fi AP should be on.
+         *
+         * @hide
+         */
+        public static final String WIFI_AP_ON = "wifi_ap_on";
+
+        /**
          * The acceptable packet loss percentage (range 0 - 100) before trying
          * another AP on the same network.
          */
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 53076de..aa9ced8 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -456,4 +456,23 @@
         }
         throw new IllegalStateException("Got an empty response");
     }
+
+    public void startAccessPoint()
+             throws IllegalStateException {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+        mContext.enforceCallingOrSelfPermission(
+            android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService");
+        mConnector.doCommand(String.format("softap set"));
+        mConnector.doCommand(String.format("softap start"));
+    }
+
+    public void stopAccessPoint() throws IllegalStateException {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+        mContext.enforceCallingOrSelfPermission(
+            android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService");
+        mConnector.doCommand("softap stop");
+    }
+
 }
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 1455973..7378333f 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -22,6 +22,12 @@
 import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
 import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
 
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothA2dp;
@@ -40,6 +46,8 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.SupplicantState;
+import android.net.ConnectivityManager;
+import android.net.InterfaceConfiguration;
 import android.net.NetworkStateTracker;
 import android.net.DhcpInfo;
 import android.net.NetworkUtils;
@@ -47,6 +55,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
@@ -67,6 +76,7 @@
 import java.util.regex.Pattern;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.net.UnknownHostException;
 
 import com.android.internal.app.IBatteryStats;
 import android.backup.IBackupManager;
@@ -87,6 +97,7 @@
 
     private Context mContext;
     private int mWifiState;
+    private int mWifiApState;
 
     private AlarmManager mAlarmManager;
     private PendingIntent mIdleIntent;
@@ -112,6 +123,10 @@
 
     private final IBatteryStats mBatteryStats;
 
+    private INetworkManagementService nwService;
+    ConnectivityManager mCm;
+    private String[] mWifiRegexs;
+
     /**
      * See {@link Settings.Secure#WIFI_IDLE_MS}. This is the default value if a
      * Settings.Secure value is not present. This timeout value is chosen as
@@ -145,6 +160,9 @@
     private static final int MESSAGE_START_WIFI       = 3;
     private static final int MESSAGE_RELEASE_WAKELOCK = 4;
     private static final int MESSAGE_UPDATE_STATE     = 5;
+    private static final int MESSAGE_START_ACCESS_POINT = 6;
+    private static final int MESSAGE_STOP_ACCESS_POINT  = 7;
+
 
     private final  WifiHandler mWifiHandler;
 
@@ -180,6 +198,9 @@
         mWifiStateTracker.enableRssiPolling(true);
         mBatteryStats = BatteryStatsService.getService();
 
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        nwService = INetworkManagementService.Stub.asInterface(b);
+
         mScanResultCache = new LinkedHashMap<String, ScanResult>(
             SCAN_RESULT_CACHE_SIZE, 0.75f, true) {
                 /*
@@ -196,7 +217,9 @@
         mWifiHandler = new WifiHandler(wifiThread.getLooper());
 
         mWifiState = WIFI_STATE_DISABLED;
+        mWifiApState = WIFI_AP_STATE_DISABLED;
         boolean wifiEnabled = getPersistedWifiEnabled();
+        boolean wifiAPEnabled = wifiEnabled ? false : getPersistedWifiApEnabled();
 
         mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
         Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
@@ -232,7 +255,70 @@
                 },
                 new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
 
+        mContext.registerReceiver(
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+
+                  ArrayList<String> available = intent.getStringArrayListExtra(
+                          ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+                  ArrayList<String> active = intent.getStringArrayListExtra(
+                          ConnectivityManager.EXTRA_ACTIVE_TETHER);
+                  updateTetherState(available, active);
+
+                }
+            },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
+
         setWifiEnabledBlocking(wifiEnabled, false, Process.myUid());
+        setWifiApEnabledBlocking(wifiAPEnabled, true, Process.myUid(), null);
+    }
+
+    private void updateTetherState(ArrayList<String> available, ArrayList<String> tethered) {
+
+        boolean wifiTethered = false;
+        boolean wifiAvailable = false;
+
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+
+        mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        mWifiRegexs = mCm.getTetherableWifiRegexs();
+
+        for (String intf : available) {
+            for (String regex : mWifiRegexs) {
+                if (intf.matches(regex)) {
+
+                    InterfaceConfiguration ifcg = null;
+                    try {
+                        ifcg = service.getInterfaceConfig(intf);
+                        if (ifcg != null) {
+                            /* IP/netmask: 169.254.2.1/255.255.255.0 */
+                            ifcg.ipAddr = (169 << 24) + (254 << 16) + (2 << 8) + 1;
+                            ifcg.netmask = (255 << 24) + (255 << 16) + (255 << 8) + 0;
+                            ifcg.interfaceFlags = "up";
+
+                            service.setInterfaceConfig(intf, ifcg);
+                        }
+                    } catch (Exception e) {
+                        /**
+                         * TODO: Add broadcast to indicate tether failed
+                         */
+                        Slog.e(TAG, "Error configuring interface " + intf + ", :" + e);
+                        return;
+                    }
+
+                    /**
+                     * TODO: Add broadcast to indicate tether failed
+                     */
+                    if(mCm.tether(intf) == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                        Slog.d(TAG, "Tethered "+intf);
+                    } else {
+                        Slog.e(TAG, "Error tethering "+intf);
+                    }
+                    break;
+                }
+            }
+        }
     }
 
     private boolean getPersistedWifiEnabled() {
@@ -337,12 +423,16 @@
          * Avoid doing a disable when the current Wifi state is UNKNOWN
          * TODO: Handle driver load fail and supplicant lost as seperate states
          */
-        if (mWifiState == WIFI_STATE_UNKNOWN && !enable) {
+        if ((mWifiState == WIFI_STATE_UNKNOWN) && !enable) {
             return false;
         }
 
         setWifiEnabledState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING, uid);
 
+        if ((mWifiApState == WIFI_AP_STATE_ENABLED) && enable) {
+            setWifiApEnabledBlocking(false, true, Process.myUid(), null);
+        }
+
         if (enable) {
             synchronized (mWifiStateTracker) {
                 if (!WifiNative.loadDriver()) {
@@ -490,6 +580,154 @@
         }
     }
 
+    private boolean getPersistedWifiApEnabled() {
+        final ContentResolver cr = mContext.getContentResolver();
+        try {
+            return Settings.Secure.getInt(cr, Settings.Secure.WIFI_AP_ON) == 1;
+        } catch (Settings.SettingNotFoundException e) {
+            Settings.Secure.putInt(cr, Settings.Secure.WIFI_AP_ON, 0);
+            return false;
+        }
+    }
+
+    private void persistWifiApEnabled(boolean enabled) {
+      final ContentResolver cr = mContext.getContentResolver();
+      Settings.Secure.putInt(cr, Settings.Secure.WIFI_AP_ON, enabled ? 1 : 0);
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#startAccessPoint(WifiConfiguration)}
+     * @param wifiConfig SSID, security and channel details as
+     *        part of WifiConfiguration
+     * @return {@code true} if the start operation was
+     *         started or is already in the queue.
+     */
+    public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
+        enforceChangePermission();
+        if (mWifiHandler == null) return false;
+
+        synchronized (mWifiHandler) {
+
+            long ident = Binder.clearCallingIdentity();
+            sWakeLock.acquire();
+            Binder.restoreCallingIdentity(ident);
+
+            mLastEnableUid = Binder.getCallingUid();
+
+            sendAccessPointMessage(enabled, wifiConfig, Binder.getCallingUid());
+        }
+
+        return true;
+    }
+
+    /**
+     * Enables/disables Wi-Fi AP synchronously. The driver is loaded
+     * and soft access point configured as a single operation.
+     * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off.
+     * @param persist {@code true} if the setting should be persisted.
+     * @param uid The UID of the process making the request.
+     * @param config The WifiConfiguration for AP
+     * @return {@code true} if the operation succeeds (or if the existing state
+     *         is the same as the requested state)
+     */
+    /**
+     * TODO: persist needs to go away in WifiService
+     * This will affect all persist related functions
+     * for Access Point
+     */
+    private boolean setWifiApEnabledBlocking(boolean enable,
+                        boolean persist, int uid, WifiConfiguration wifiConfig) {
+        final int eventualWifiApState = enable ? WIFI_AP_STATE_ENABLED : WIFI_AP_STATE_DISABLED;
+
+        if (mWifiApState == eventualWifiApState) {
+            return true;
+        }
+
+        setWifiApEnabledState(enable ? WIFI_AP_STATE_ENABLING : WIFI_AP_STATE_DISABLING, uid);
+
+        if (enable && (mWifiState == WIFI_STATE_ENABLED)) {
+            setWifiEnabledBlocking(false, true, Process.myUid());
+        }
+
+        if (enable) {
+            synchronized (mWifiStateTracker) {
+                if (!WifiNative.loadDriver()) {
+                    Slog.e(TAG, "Failed to load Wi-Fi driver for AP mode");
+                    setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid);
+                    return false;
+                }
+            }
+
+            try {
+                nwService.startAccessPoint();
+            } catch(Exception e) {
+                Slog.e(TAG, "Exception in startAccessPoint()");
+            }
+
+        } else {
+
+            try {
+                nwService.stopAccessPoint();
+            } catch(Exception e) {
+                Slog.e(TAG, "Exception in stopAccessPoint()");
+            }
+
+            synchronized (mWifiStateTracker) {
+                if (!WifiNative.unloadDriver()) {
+                    Slog.e(TAG, "Failed to unload Wi-Fi driver for AP mode");
+                    setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid);
+                    return false;
+                }
+            }
+        }
+
+        // Success!
+        if (persist) {
+            persistWifiApEnabled(enable);
+        }
+        setWifiApEnabledState(eventualWifiApState, uid);
+        return true;
+    }
+
+    /**
+     * see {@link WifiManager#getWifiApState()}
+     * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_FAILED}
+     */
+    public int getWifiApEnabledState() {
+        enforceAccessPermission();
+        return mWifiApState;
+    }
+
+    private void setWifiApEnabledState(int wifiAPState, int uid) {
+        final int previousWifiApState = mWifiApState;
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (wifiAPState == WIFI_AP_STATE_ENABLED) {
+                mBatteryStats.noteWifiOn(uid);
+            } else if (wifiAPState == WIFI_AP_STATE_DISABLED) {
+                mBatteryStats.noteWifiOff(uid);
+            }
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        // Update state
+        mWifiApState = wifiAPState;
+
+        // Broadcast
+        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiAPState);
+        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
+        mContext.sendStickyBroadcast(intent);
+    }
+
     /**
      * see {@link android.net.wifi.WifiManager#getConfiguredNetworks()}
      * @return the list of configured networks
@@ -1472,6 +1710,12 @@
         Message.obtain(mWifiHandler, MESSAGE_START_WIFI, scanOnlyMode ? 1 : 0, 0).sendToTarget();
     }
 
+    private void sendAccessPointMessage(boolean enable, WifiConfiguration wifiConfig, int uid) {
+        Message.obtain(mWifiHandler,
+                (enable ? MESSAGE_START_ACCESS_POINT : MESSAGE_STOP_ACCESS_POINT),
+                0, uid, wifiConfig).sendToTarget();
+    }
+
     private void updateWifiState() {
         // send a message so it's all serialized
         Message.obtain(mWifiHandler, MESSAGE_UPDATE_STATE, 0, 0).sendToTarget();
@@ -1606,6 +1850,21 @@
                         }
                     }
                     break;
+
+                case MESSAGE_START_ACCESS_POINT:
+                    setWifiApEnabledBlocking(true,
+                                             msg.arg1 == 1,
+                                             msg.arg2,
+                                             (WifiConfiguration) msg.obj);
+                    break;
+
+                case MESSAGE_STOP_ACCESS_POINT:
+                    setWifiApEnabledBlocking(false,
+                                             msg.arg1 == 1,
+                                             msg.arg2,
+                                             (WifiConfiguration) msg.obj);
+                    sWakeLock.release();
+                    break;
             }
         }
     }
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index f3738e30..d833e33 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -29,7 +29,7 @@
 interface IWifiManager
 {
     List<WifiConfiguration> getConfiguredNetworks();
-    
+
     int addOrUpdateNetwork(in WifiConfiguration config);
 
     boolean removeNetwork(int netId);
@@ -47,7 +47,7 @@
     boolean disconnect();
 
     boolean reconnect();
-    
+
     boolean reassociate();
 
     WifiInfo getConnectionInfo();
@@ -61,7 +61,7 @@
     boolean setNumAllowedChannels(int numChannels, boolean persist);
 
     int[] getValidChannelCounts();
-    
+
     boolean saveConfiguration();
 
     DhcpInfo getDhcpInfo();
@@ -77,5 +77,9 @@
     void acquireMulticastLock(IBinder binder, String tag);
 
     void releaseMulticastLock();
+
+    boolean setWifiApEnabled(in WifiConfiguration wifiConfig, boolean enable);
+
+    int getWifiApEnabledState();
 }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 178f76e..9ef8ba1 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -123,7 +123,88 @@
      * @see #getWifiState()
      */
     public static final int WIFI_STATE_UNKNOWN = 4;
-    
+
+    /**
+     * Broadcast intent action indicating that Wi-Fi AP has been enabled, disabled,
+     * enabling, disabling, or failed.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String WIFI_AP_STATE_CHANGED_ACTION =
+        "android.net.wifi.WIFI_AP_STATE_CHANGED";
+
+    /**
+     * The lookup key for an int that indicates whether Wi-Fi AP is enabled,
+     * disabled, enabling, disabling, or failed.  Retrieve it with
+     * {@link android.content.Intent#getIntExtra(String,int)}.
+     *
+     * @see #WIFI_AP_STATE_DISABLED
+     * @see #WIFI_AP_STATE_DISABLING
+     * @see #WIFI_AP_STATE_ENABLED
+     * @see #WIFI_AP_STATE_ENABLING
+     * @see #WIFI_AP_STATE_FAILED
+     *
+     * @hide
+     */
+    public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
+    /**
+     * The previous Wi-Fi state.
+     *
+     * @see #EXTRA_WIFI_AP_STATE
+     *
+     * @hide
+     */
+    public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
+    /**
+     * Wi-Fi AP is currently being disabled. The state will change to
+     * {@link #WIFI_AP_STATE_DISABLED} if it finishes successfully.
+     *
+     * @see #WIFI_AP_STATE_CHANGED_ACTION
+     * @see #getWifiApState()
+     *
+     * @hide
+     */
+    public static final int WIFI_AP_STATE_DISABLING = 0;
+    /**
+     * Wi-Fi AP is disabled.
+     *
+     * @see #WIFI_AP_STATE_CHANGED_ACTION
+     * @see #getWifiState()
+     *
+     * @hide
+     */
+    public static final int WIFI_AP_STATE_DISABLED = 1;
+    /**
+     * Wi-Fi AP is currently being enabled. The state will change to
+     * {@link #WIFI_AP_STATE_ENABLED} if it finishes successfully.
+     *
+     * @see #WIFI_AP_STATE_CHANGED_ACTION
+     * @see #getWifiApState()
+     *
+     * @hide
+     */
+    public static final int WIFI_AP_STATE_ENABLING = 2;
+    /**
+     * Wi-Fi AP is enabled.
+     *
+     * @see #WIFI_AP_STATE_CHANGED_ACTION
+     * @see #getWifiApState()
+     *
+     * @hide
+     */
+    public static final int WIFI_AP_STATE_ENABLED = 3;
+    /**
+     * Wi-Fi AP is in a failed state. This state will occur when an error occurs during
+     * enabling or disabling
+     *
+     * @see #WIFI_AP_STATE_CHANGED_ACTION
+     * @see #getWifiApState()
+     *
+     * @hide
+     */
+    public static final int WIFI_AP_STATE_FAILED = 4;
+
     /**
      * Broadcast intent action indicating that a connection to the supplicant has
      * been established (and it is now possible
@@ -681,6 +762,54 @@
     }
 
     /**
+     * Start AccessPoint mode with the specified
+     * configuration. If the radio is already running in
+     * AP mode, update the new configuration
+     * Note that starting in access point mode disables station
+     * mode operation
+     * @param wifiConfig SSID, security and channel details as
+     *        part of WifiConfiguration
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
+     *
+     * @hide Dont open up yet
+     */
+    public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
+        try {
+            return mService.setWifiApEnabled(wifiConfig, enabled);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Gets the Wi-Fi enabled state.
+     * @return One of {@link #WIFI_AP_STATE_DISABLED},
+     *         {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
+     *         {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
+     * @see #isWifiApEnabled()
+     *
+     * @hide Dont open yet
+     */
+    public int getWifiApState() {
+        try {
+            return mService.getWifiApEnabledState();
+        } catch (RemoteException e) {
+            return WIFI_AP_STATE_FAILED;
+        }
+    }
+
+    /**
+     * Return whether Wi-Fi AP is enabled or disabled.
+     * @return {@code true} if Wi-Fi AP is enabled
+     * @see #getWifiApState()
+     *
+     * @hide Dont open yet
+     */
+    public boolean isWifiApEnabled() {
+        return getWifiApState() == WIFI_AP_STATE_ENABLED;
+    }
+
+    /**
      * Allows an application to keep the Wi-Fi radio awake.
      * Normally the Wi-Fi radio may turn off when the user has not used the device in a while.
      * Acquiring a WifiLock will keep the radio on until the lock is released.  Multiple