/** * Copyright 2015 Daniel Martà * * 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 cc.mvdan.accesspoint; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Build; import android.provider.Settings; import android.util.Log; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Pattern; /** * WifiApControl provides control over Wi-Fi APs using the singleton pattern. * Even though isSupported should be reliable, the underlying hidden APIs that * are obtained via reflection to provide the main features may not work as * expected. *
* TODO Note that this project is **abandoned** since its method doesn't work on Android
* 7.1 or later. Have a look at these newer alternatives that have been tested to
* work on Android 8.0:
*
* @see shinilms/direct-net-share
* @see geekywoman/direct-net-share
* @see aegis1980/WifiHotSpot
*/
final public class WifiApControl {
private static final String TAG = "WifiApControl";
private static Method getWifiApConfigurationMethod;
private static Method getWifiApStateMethod;
private static Method isWifiApEnabledMethod;
private static Method setWifiApEnabledMethod;
static {
for (Method method : WifiManager.class.getDeclaredMethods()) {
switch (method.getName()) {
case "getWifiApConfiguration":
getWifiApConfigurationMethod = method;
break;
case "getWifiApState":
getWifiApStateMethod = method;
break;
case "isWifiApEnabled":
isWifiApEnabledMethod = method;
break;
case "setWifiApEnabled":
setWifiApEnabledMethod = method;
break;
}
}
}
public static final int WIFI_AP_STATE_DISABLING = 10;
public static final int WIFI_AP_STATE_DISABLED = 11;
public static final int WIFI_AP_STATE_ENABLING = 12;
public static final int WIFI_AP_STATE_ENABLED = 13;
public static final int WIFI_AP_STATE_FAILED = 14;
public static final int STATE_DISABLING = WIFI_AP_STATE_DISABLING;
public static final int STATE_DISABLED = WIFI_AP_STATE_DISABLED;
public static final int STATE_ENABLING = WIFI_AP_STATE_ENABLING;
public static final int STATE_ENABLED = WIFI_AP_STATE_ENABLED;
public static final int STATE_FAILED = WIFI_AP_STATE_FAILED;
private static boolean isSoftwareSupported() {
return (getWifiApStateMethod != null
&& isWifiApEnabledMethod != null
&& setWifiApEnabledMethod != null
&& getWifiApConfigurationMethod != null);
}
private static boolean isHardwareSupported() {
// TODO: implement via native code
return true;
}
// isSupported reports whether Wi-Fi APs are supported by this device.
public static boolean isSupported() {
return isSoftwareSupported() && isHardwareSupported();
}
private static final String FALLBACK_DEVICE = "wlan0";
private final WifiManager wm;
private final String deviceName;
private static WifiApControl instance = null;
private WifiApControl(Context context) {
wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
deviceName = getDeviceName(wm);
}
// getInstance is a standard singleton instance getter, constructing
// the actual class when first called.
public static WifiApControl getInstance(Context context) {
if (instance == null) {
if (!Settings.System.canWrite(context)) {
Log.e(TAG, "6.0 or later, but haven't been granted WRITE_SETTINGS!");
return null;
}
instance = new WifiApControl(context);
}
return instance;
}
private static String getDeviceName(WifiManager wifiManager) {
Log.w(TAG, "6.0 or later, unaccessible MAC - falling back to the default device name: " + FALLBACK_DEVICE);
return FALLBACK_DEVICE;
}
private static byte[] macAddressToByteArray(String macString) {
String[] mac = macString.split("[:\\s-]");
byte[] macAddress = new byte[6];
for (int i = 0; i < mac.length; i++) {
macAddress[i] = Integer.decode("0x" + mac[i]).byteValue();
}
return macAddress;
}
private static Object invokeQuietly(Method method, Object receiver, Object... args) {
try {
return method.invoke(receiver, args);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
Log.e(TAG, "", e);
}
return null;
}
// isWifiApEnabled returns whether the Wi-Fi AP is currently enabled.
// If an error occured invoking the method via reflection, false is
// returned.
public boolean isWifiApEnabled() {
Object result = invokeQuietly(isWifiApEnabledMethod, wm);
if (result == null) {
return false;
}
return (Boolean) result;
}
// isEnabled is a commodity function alias for isWifiApEnabled.
public boolean isEnabled() {
return isWifiApEnabled();
}
// newStateNumber adapts the state constants to the current values in
// the SDK. They were changed on 4.0 to have higher integer values.
public static int newStateNumber(int state) {
if (state < 10) {
return state + 10;
}
return state;
}
// getWifiApState returns the current Wi-Fi AP state.
// If an error occured invoking the method via reflection, -1 is
// returned.
public int getWifiApState() {
Object result = invokeQuietly(getWifiApStateMethod, wm);
if (result == null) {
return -1;
}
return newStateNumber((Integer) result);
}
// getState is a commodity function alias for getWifiApState.
public int getState() {
return getWifiApState();
}
// getWifiApConfiguration returns the current Wi-Fi AP configuration.
// If an error occured invoking the method via reflection, null is
// returned.
public WifiConfiguration getWifiApConfiguration() {
Object result = invokeQuietly(getWifiApConfigurationMethod, wm);
if (result == null) {
return null;
}
return (WifiConfiguration) result;
}
// getConfiguration is a commodity function alias for
// getWifiApConfiguration.
public WifiConfiguration getConfiguration() {
return getWifiApConfiguration();
}
// setWifiApEnabled starts a Wi-Fi AP with the specified
// configuration. If one is already running, start using the new
// configuration. You should call WifiManager.setWifiEnabled(false)
// yourself before calling this method.
// If an error occured invoking the method via reflection, false is
// returned.
public boolean setWifiApEnabled(WifiConfiguration config, boolean enabled) {
Object result = invokeQuietly(setWifiApEnabledMethod, wm, config, enabled);
if (result == null) {
return false;
}
return (Boolean) result;
}
// setEnabled is a commodity function alias for setWifiApEnabled.
public boolean setEnabled(WifiConfiguration config, boolean enabled) {
return setWifiApEnabled(config, enabled);
}
// enable starts the currently configured Wi-Fi AP.
public boolean enable() {
return setEnabled(getConfiguration(), true);
}
// disable stops any currently running Wi-Fi AP.
public boolean disable() {
return setEnabled(null, false);
}
// getInet6Address returns the IPv6 address that the device has in its
// own Wi-Fi AP local network. Will return null if no Wi-Fi AP is
// currently enabled.
public Inet6Address getInet6Address() {
if (!isEnabled()) {
return null;
}
return getInetAddress(Inet6Address.class);
}
// getInet4Address returns the IPv4 address that the device has in its
// own Wi-Fi AP local network. Will return null if no Wi-Fi AP is
// currently enabled.
public Inet4Address getInet4Address() {
if (!isEnabled()) {
return null;
}
return getInetAddress(Inet4Address.class);
}
private