Skip to content

Commit 1529445

Browse files
committed
add hotspot toggle feature (for Android Oreo onwards only, no root required)
1 parent 432ebf8 commit 1529445

File tree

10 files changed

+226
-5
lines changed

10 files changed

+226
-5
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ android {
3636
}
3737

3838
dependencies {
39+
implementation 'com.linkedin.dexmaker:dexmaker:2.28.3'
3940
}

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<uses-permission android:name="android.permission.FLASHLIGHT"/>
1313
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
1414
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
15+
<uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
1516

1617
<application
1718
android:allowBackup="true"

app/src/main/java/it/pgp/currenttoggles/MainActivity.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import android.content.Intent;
88
import android.content.SharedPreferences;
99
import android.hardware.camera2.CameraManager;
10+
import android.net.Uri;
1011
import android.os.Build;
1112
import android.os.Bundle;
1213
import android.os.Handler;
@@ -23,6 +24,8 @@
2324

2425
import it.pgp.currenttoggles.utils.Misc;
2526
import it.pgp.currenttoggles.utils.RootHandler;
27+
import it.pgp.currenttoggles.utils.oreoap.MyOnStartTetheringCallback;
28+
import it.pgp.currenttoggles.utils.oreoap.MyOreoWifiManager;
2629

2730
public class MainActivity extends Activity {
2831

@@ -38,6 +41,13 @@ protected void onCreate(Bundle savedInstanceState) {
3841
setContentView(R.layout.activity_main);
3942
}
4043

44+
public static void launchWriteSettings(Context context) {
45+
Toast.makeText(context, "Please grant system settings write permission in order to use this toggle", Toast.LENGTH_SHORT).show();
46+
Intent i = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, Uri.parse("package:" + context.getPackageName()));
47+
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
48+
context.startActivity(i);
49+
}
50+
4151
public static void toggleDataWifiBluetoothGps(Context context, String channel, II ii) { // channel: "data" or "wifi"
4252
String[][] cmdsAndErrors = {
4353
{channel + " currently DISABLED -> enabling...", "enable"},
@@ -123,10 +133,7 @@ public static void toggleAutoScreenBrightness(Context context) {
123133
}
124134
catch(SecurityException e) {
125135
e.printStackTrace();
126-
Toast.makeText(context, "Please grant system settings write permission in order to use this toggle", Toast.LENGTH_SHORT).show();
127-
Intent i = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
128-
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
129-
context.startActivity(i);
136+
launchWriteSettings(context);
130137
}
131138
}
132139

@@ -231,8 +238,28 @@ public static void toggleEnergySaving(Context context) {
231238
}
232239
}
233240

241+
public static void toggleHotspot(Context context) {
242+
if (!Settings.System.canWrite(context)) {
243+
launchWriteSettings(context);
244+
}
245+
else {
246+
MyOreoWifiManager apManager = new MyOreoWifiManager(context);
247+
if(apManager.isTetherActive()) {
248+
apManager.stopTethering();
249+
Toast.makeText(context, "AP stopped", Toast.LENGTH_SHORT).show();
250+
}
251+
else {
252+
apManager.startTethering(new MyOnStartTetheringCallback());
253+
Toast.makeText(context, "AP started", Toast.LENGTH_SHORT).show();
254+
}
255+
}
256+
}
257+
234258
public void toggle(View v) {
235259
switch(v.getId()) {
260+
case R.id.toggleHotspot:
261+
toggleHotspot(this);
262+
break;
236263
case R.id.toggleData:
237264
toggleDataWifiBluetoothGps(this, "data", Misc::isDataConnectionEnabled);
238265
break;

app/src/main/java/it/pgp/currenttoggles/MainWidget.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class MainWidget extends AppWidgetProvider {
1818

1919
private static final String onDemandWifi = "it.pgp.currenttoggles.appwidget.action.ON_DEMAND_WIFI";
2020
private static final String onDemandData = "it.pgp.currenttoggles.appwidget.action.ON_DEMAND_DATA";
21+
private static final String onDemandHotspot = "it.pgp.currenttoggles.appwidget.action.ON_DEMAND_HOTSPOT";
2122
private static final String onDemandBluetooth = "it.pgp.currenttoggles.appwidget.action.ON_DEMAND_BLUETOOTH";
2223
private static final String onDemandGps = "it.pgp.currenttoggles.appwidget.action.ON_DEMAND_GPS";
2324
private static final String onDemandAutoBrightness = "it.pgp.currenttoggles.appwidget.action.ON_DEMAND_AUTO_BR";
@@ -38,6 +39,14 @@ public static void updateAllDirect(Context context) {
3839
Intent ii;
3940
PendingIntent pi;
4041

42+
ii = new Intent(context, MainWidget.class);
43+
ii.setAction(onDemandHotspot);
44+
ii.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, w_ids);
45+
pi = PendingIntent.getBroadcast(
46+
context, appWidgetId, ii,
47+
PendingIntent.FLAG_UPDATE_CURRENT);
48+
remoteViews.setOnClickPendingIntent(R.id.toggle_hotspot, pi);
49+
4150
ii = new Intent(context, MainWidget.class);
4251
ii.setAction(onDemandData);
4352
ii.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, w_ids);
@@ -122,6 +131,10 @@ public void onReceive(Context context, Intent intent) {
122131
Log.d(LOG_PREFIX,"onDemand Data");
123132
MainActivity.toggleDataWifiBluetoothGps(context, "data", Misc::isDataConnectionEnabled);
124133
break;
134+
case onDemandHotspot:
135+
Log.d(LOG_PREFIX,"onDemand Hotspot");
136+
MainActivity.toggleHotspot(context);
137+
break;
125138
case onDemandBluetooth:
126139
Log.d(LOG_PREFIX,"onDemand Bluetooth");
127140
// MainActivity.toggleDataWifiBluetooth(context, "bluetooth", Misc::isBluetoothEnabled);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package it.pgp.currenttoggles.utils.oreoap;
2+
3+
public class MyOnStartTetheringCallback {
4+
public void onTetheringStarted() {}
5+
6+
public void onTetheringFailed() {}
7+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package it.pgp.currenttoggles.utils.oreoap;
2+
3+
import android.content.Context;
4+
import android.net.ConnectivityManager;
5+
import android.net.wifi.WifiConfiguration;
6+
import android.net.wifi.WifiManager;
7+
import android.os.Handler;
8+
import android.util.Log;
9+
10+
import com.android.dx.stock.ProxyBuilder;
11+
12+
import java.io.File;
13+
import java.lang.reflect.Method;
14+
import java.util.Arrays;
15+
16+
// Source: https://github.com/aegis1980/WifiHotSpot
17+
18+
// @RequiresApi(api = Build.VERSION_CODES.O)
19+
public class MyOreoWifiManager {
20+
private static final String TAG = MyOreoWifiManager.class.getSimpleName();
21+
22+
private Context mContext;
23+
private WifiManager mWifiManager;
24+
private ConnectivityManager mConnectivityManager;
25+
26+
public MyOreoWifiManager(Context c) {
27+
mContext = c;
28+
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
29+
mConnectivityManager = (ConnectivityManager) mContext.getSystemService(ConnectivityManager.class);
30+
}
31+
32+
/**
33+
* This sets the Wifi SSID and password
34+
* Call this before {@code startTethering} if app is a system/privileged app
35+
* Requires: android.permission.TETHER_PRIVILEGED which is only granted to system apps
36+
*/
37+
public void configureHotspot(String name, String password) {
38+
WifiConfiguration apConfig = new WifiConfiguration();
39+
apConfig.SSID = name;
40+
apConfig.preSharedKey = password;
41+
apConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
42+
try {
43+
Method setConfigMethod = mWifiManager.getClass().getMethod("setWifiApConfiguration", WifiConfiguration.class);
44+
boolean status = (boolean) setConfigMethod.invoke(mWifiManager, apConfig);
45+
Log.d(TAG, "setWifiApConfiguration - success? " + status);
46+
} catch (Exception e) {
47+
Log.e(TAG, "Error in configureHotspot");
48+
e.printStackTrace();
49+
}
50+
}
51+
52+
/**
53+
* Checks where tethering is on.
54+
* This is determined by the getTetheredIfaces() method,
55+
* that will return an empty array if not devices are tethered
56+
*
57+
* @return true if a tethered device is found, false if not found
58+
*/
59+
public boolean isTetherActive() {
60+
try {
61+
Method method = mConnectivityManager.getClass().getDeclaredMethod("getTetheredIfaces");
62+
if (method == null) {
63+
Log.e(TAG, "getTetheredIfaces is null");
64+
} else {
65+
String res[] = (String []) method.invoke(mConnectivityManager, null);
66+
Log.d(TAG, "getTetheredIfaces invoked");
67+
Log.d(TAG, Arrays.toString(res));
68+
if (res.length > 0) {
69+
return true;
70+
}
71+
}
72+
} catch (Exception e) {
73+
Log.e(TAG, "Error in getTetheredIfaces");
74+
e.printStackTrace();
75+
}
76+
return false;
77+
}
78+
79+
/**
80+
* This enables tethering using the ssid/password defined in Settings App>Hotspot & tethering
81+
* Does not require app to have system/privileged access
82+
* Credit: Vishal Sharma - https://stackoverflow.com/a/52219887
83+
*/
84+
public boolean startTethering(final MyOnStartTetheringCallback callback) {
85+
86+
// On Pie if we try to start tethering while it is already on, it will
87+
// be disabled. This is needed when startTethering() is called programmatically.
88+
if (isTetherActive()) {
89+
Log.d(TAG, "Tether already active, returning");
90+
return false;
91+
}
92+
93+
File outputDir = mContext.getCodeCacheDir();
94+
Object proxy;
95+
try {
96+
proxy = ProxyBuilder.forClass(OnStartTetheringCallbackClass())
97+
.dexCache(outputDir).handler((proxy1, method, args) -> {
98+
switch (method.getName()) {
99+
case "onTetheringStarted":
100+
callback.onTetheringStarted();
101+
break;
102+
case "onTetheringFailed":
103+
callback.onTetheringFailed();
104+
break;
105+
default:
106+
ProxyBuilder.callSuper(proxy1, method, args);
107+
}
108+
return null;
109+
}).build();
110+
} catch (Exception e) {
111+
Log.e(TAG, "Error in enableTethering ProxyBuilder");
112+
e.printStackTrace();
113+
return false;
114+
}
115+
116+
Method method;
117+
try {
118+
method = mConnectivityManager.getClass().getDeclaredMethod("startTethering", int.class, boolean.class, OnStartTetheringCallbackClass(), Handler.class);
119+
if (method == null) {
120+
Log.e(TAG, "startTetheringMethod is null");
121+
} else {
122+
method.invoke(mConnectivityManager, ConnectivityManager.TYPE_MOBILE, false, proxy, null);
123+
Log.d(TAG, "startTethering invoked");
124+
}
125+
return true;
126+
} catch (Exception e) {
127+
Log.e(TAG, "Error in enableTethering");
128+
e.printStackTrace();
129+
}
130+
return false;
131+
}
132+
133+
public void stopTethering() {
134+
try {
135+
Method method = mConnectivityManager.getClass().getDeclaredMethod("stopTethering", int.class);
136+
if (method == null) {
137+
Log.e(TAG, "stopTetheringMethod is null");
138+
} else {
139+
method.invoke(mConnectivityManager, ConnectivityManager.TYPE_MOBILE);
140+
Log.d(TAG, "stopTethering invoked");
141+
}
142+
} catch (Exception e) {
143+
Log.e(TAG, "stopTethering error: " + e.toString());
144+
e.printStackTrace();
145+
}
146+
}
147+
148+
private Class OnStartTetheringCallbackClass() {
149+
try {
150+
return Class.forName("android.net.ConnectivityManager$OnStartTetheringCallback");
151+
} catch (ClassNotFoundException e) {
152+
Log.e(TAG, "OnStartTetheringCallbackClass error: " + e.toString());
153+
e.printStackTrace();
154+
}
155+
return null;
156+
}
157+
}
158+
2.98 KB
Loading

app/src/main/res/layout/activity_main.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
android:id="@+id/toggleData"
1212
android:layout_width="match_parent"
1313
android:layout_height="wrap_content" />
14+
<Button
15+
android:text="Hotspot"
16+
android:onClick="toggle"
17+
android:id="@+id/toggleHotspot"
18+
android:layout_width="match_parent"
19+
android:layout_height="wrap_content" />
1420
<Button
1521
android:text="Wifi"
1622
android:onClick="toggle"

app/src/main/res/layout/buttons_widget.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
android:layout_width="0dp"
1313
android:layout_height="match_parent" />
1414

15+
<ImageButton
16+
android:id="@+id/toggle_hotspot"
17+
android:src="@drawable/hotspot"
18+
style="@android:style/Widget.Holo.ImageButton"
19+
android:layout_weight="1"
20+
android:layout_width="0dp"
21+
android:layout_height="match_parent" />
22+
1523
<ImageButton
1624
android:id="@+id/toggle_wifi"
1725
android:src="@drawable/wifi"

app/src/main/res/xml/mainwidget_info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
33
android:initialKeyguardLayout="@layout/buttons_widget"
44
android:initialLayout="@layout/buttons_widget"
5-
android:minWidth="110dp"
5+
android:minWidth="300dp"
66
android:resizeMode="horizontal|vertical"
77
android:updatePeriodMillis="86400000"
88
android:widgetCategory="home_screen" />

0 commit comments

Comments
 (0)