vendor 'cc.mvdan.accesspoint:library:0.2.0' to remove jcenter entirely
Code was in https://github.com/mvdan/libaccesspoint 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: * https://github.com/shinilms/direct-net-share * https://github.com/geekywoman/direct-net-share * https://github.com/aegis1980/WifiHotSpot
This commit is contained in:
parent
d5e4519b97
commit
80a50dcfd4
|
@ -181,7 +181,6 @@ dependencies {
|
|||
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
|
||||
fullImplementation 'org.bouncycastle:bcpkix-jdk15on:1.65'
|
||||
fullImplementation 'cc.mvdan.accesspoint:library:0.2.0'
|
||||
fullImplementation 'org.jmdns:jmdns:3.5.5'
|
||||
fullImplementation 'org.nanohttpd:nanohttpd:2.3.1'
|
||||
|
||||
|
|
|
@ -0,0 +1,451 @@
|
|||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* 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 <a href="https://github.com/shinilms/direct-net-share">shinilms/direct-net-share</a>
|
||||
* @see <a href="https://github.com/geekywoman/direct-net-share">geekywoman/direct-net-share</a>
|
||||
* @see <a href="https://github.com/aegis1980/WifiHotSpot">aegis1980/WifiHotSpot</a>
|
||||
*/
|
||||
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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !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;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
|
||||
private static String getDeviceName(WifiManager wifiManager) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
|
||||
Log.w(TAG, "Older device - falling back to the default device name: " + FALLBACK_DEVICE);
|
||||
return FALLBACK_DEVICE;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Log.w(TAG, "6.0 or later, unaccessible MAC - falling back to the default device name: " + FALLBACK_DEVICE);
|
||||
return FALLBACK_DEVICE;
|
||||
}
|
||||
|
||||
@SuppressLint("HardwareIds")
|
||||
String macString = wifiManager.getConnectionInfo().getMacAddress();
|
||||
if (macString == null) {
|
||||
Log.w(TAG, "MAC Address not found - Wi-Fi disabled? Falling back to the default device name: " + FALLBACK_DEVICE);
|
||||
return FALLBACK_DEVICE;
|
||||
}
|
||||
byte[] macBytes = macAddressToByteArray(macString);
|
||||
|
||||
try {
|
||||
Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (ifaces.hasMoreElements()) {
|
||||
NetworkInterface iface = ifaces.nextElement();
|
||||
|
||||
byte[] hardwareAddress = iface.getHardwareAddress();
|
||||
if (hardwareAddress != null && Arrays.equals(macBytes, hardwareAddress)) {
|
||||
return iface.getName();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
|
||||
Log.w(TAG, "None found - 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 <T extends InetAddress> T getInetAddress(Class<T> addressType) {
|
||||
try {
|
||||
Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (ifaces.hasMoreElements()) {
|
||||
NetworkInterface iface = ifaces.nextElement();
|
||||
|
||||
if (!iface.getName().equals(deviceName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Enumeration<InetAddress> addrs = iface.getInetAddresses();
|
||||
while (addrs.hasMoreElements()) {
|
||||
InetAddress addr = addrs.nextElement();
|
||||
|
||||
if (addressType.isInstance(addr)) {
|
||||
return addressType.cast(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Client describes a Wi-Fi AP device connected to the network.
|
||||
public static class Client {
|
||||
|
||||
// ipAddr is the raw string of the IP Address client
|
||||
public String ipAddr;
|
||||
|
||||
// hwAddr is the raw string of the MAC of the client
|
||||
public String hwAddr;
|
||||
|
||||
public Client(String ipAddr, String hwAddr) {
|
||||
this.ipAddr = ipAddr;
|
||||
this.hwAddr = hwAddr;
|
||||
}
|
||||
}
|
||||
|
||||
// getClients returns a list of all clients connected to the network.
|
||||
// Since the information is pulled from ARP, which is cached for up to
|
||||
// five minutes, this method may yield clients that disconnected
|
||||
// minutes ago.
|
||||
public List<Client> getClients() {
|
||||
if (!isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
List<Client> result = new ArrayList<>();
|
||||
|
||||
// Basic sanity checks
|
||||
Pattern macPattern = Pattern.compile("..:..:..:..:..:..");
|
||||
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new FileReader("/proc/net/arp"));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
String[] parts = line.split(" +");
|
||||
if (parts.length < 6) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String ipAddr = parts[0];
|
||||
String hwAddr = parts[3];
|
||||
String device = parts[5];
|
||||
|
||||
if (!device.equals(deviceName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!macPattern.matcher(parts[3]).find()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.add(new Client(ipAddr, hwAddr));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "", e);
|
||||
} finally {
|
||||
try {
|
||||
if (br != null) {
|
||||
br.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ReachableClientListener is an interface to collect the results
|
||||
// provided by getReachableClients via callbacks.
|
||||
public interface ReachableClientListener {
|
||||
|
||||
// onReachableClient is called each time a reachable client is
|
||||
// found.
|
||||
void onReachableClient(Client c);
|
||||
|
||||
// onComplete is called when we are done looking for reachable
|
||||
// clients
|
||||
void onComplete();
|
||||
}
|
||||
|
||||
// getReachableClients fetches the clients connected to the network
|
||||
// much like getClients, but only those which are reachable. Since
|
||||
// checking for reachability requires network I/O, the reachable
|
||||
// clients are returned via callbacks. All the clients are returned
|
||||
// like in getClients so that the callback returns a subset of the
|
||||
// same objects.
|
||||
public List<Client> getReachableClients(final int timeout,
|
||||
final ReachableClientListener listener) {
|
||||
List<Client> clients = getClients();
|
||||
if (clients == null) {
|
||||
return null;
|
||||
}
|
||||
final CountDownLatch latch = new CountDownLatch(clients.size());
|
||||
ExecutorService es = Executors.newCachedThreadPool();
|
||||
for (final Client c : clients) {
|
||||
es.submit(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
InetAddress ip = InetAddress.getByName(c.ipAddr);
|
||||
if (ip.isReachable(timeout)) {
|
||||
listener.onReachableClient(c);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
new Thread() {
|
||||
public void run() {
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
listener.onComplete();
|
||||
}
|
||||
}.start();
|
||||
return clients;
|
||||
}
|
||||
}
|
|
@ -11,10 +11,5 @@ allprojects {
|
|||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://maven.google.com/' }
|
||||
jcenter() {
|
||||
content {
|
||||
includeModule("cc.mvdan.accesspoint", "library")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ apply plugin: "net.ltgt.errorprone"
|
|||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs += [
|
||||
'-XepAllDisabledChecksAsWarnings',
|
||||
'-XepExcludedPaths:.*/cc/mvdan/accesspoint/.*',
|
||||
'-Xep:CatchFail:OFF',
|
||||
'-Xep:ClassCanBeStatic:OFF',
|
||||
'-Xep:DateFormatConstant:OFF',
|
||||
|
|
|
@ -828,14 +828,6 @@
|
|||
<sha256 value="770471090ca40a17b9e436ee2ec00819be42042da6f4085ece1d37916dc08ff9" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="cc.mvdan.accesspoint" name="library" version="0.2.0">
|
||||
<artifact name="library-0.2.0.aar">
|
||||
<sha256 value="0837b38adb48b66bb1385adb6ade8ecce7002ad815c55abf13517c82193458ea" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
<artifact name="library-0.2.0.pom">
|
||||
<sha256 value="a766792a50a732a57829016da33e45f84d10f1eb95b1898067a25ae4a18db740" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="ch.acra" name="acra" version="4.9.1">
|
||||
<artifact name="acra-4.9.1.aar">
|
||||
<pgp value="0394681addddb4f6388a64d295123567c1886c47"/>
|
||||
|
|
Loading…
Reference in New Issue