diff --git a/app/build.gradle b/app/build.gradle index 220075c..0da3bc4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -70,6 +70,9 @@ dependencies { apkFlavorImplementation 'com.google.firebase:firebase-appindexing:19.2.0' apkFlavorImplementation 'com.google.android.gms:play-services-location:17.1.0' + + implementation 'com.linkedin.dexmaker:dexmaker:2.25.0' + implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.android.material:material:1.3.0' testImplementation 'junit:junit:4.+' diff --git a/app/src/main/java/com/jens/automation2/actions/wifi_router/HotSpotIntentService.java b/app/src/main/java/com/jens/automation2/actions/wifi_router/HotSpotIntentService.java new file mode 100644 index 0000000..8dff812 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/actions/wifi_router/HotSpotIntentService.java @@ -0,0 +1,255 @@ +package com.jens.automation2.actions.wifi_router; + +import android.Manifest; +import android.app.IntentService; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; +import androidx.core.content.ContextCompat; + +import java.lang.reflect.Method; + +import static android.content.ContentValues.TAG; + +/** + * An {@link IntentService} subclass for handling asynchronous task requests in + * a service on a separate handler thread. + */ +public class HotSpotIntentService extends IntentService +{ + /** + Id for running service in foreground + */ + private static int FOREGROUND_ID=1338; + private static final String CHANNEL_ID = "control_app"; + + // Action names...assigned in manifest. + private String ACTION_TURNON; + private String ACTION_TURNOFF; + private String DATAURI_TURNON; + private String DATAURI_TURNOFF; + private Intent mStartIntent; + + @RequiresApi(api = Build.VERSION_CODES.O) + MyOreoWifiManager mMyOreoWifiManager; + + + /** + * Creates an IntentService. Invoked by your subclass's constructor. + * + + */ + public HotSpotIntentService() + { + super("HotSpotIntentService"); + } + + /** + * Helper method to start this intent from {@link HotSpotIntentReceiver} + * @param context + * @param intent + */ + public static void start(Context context,Intent intent) + { + Intent i = new Intent(context, HotSpotIntentService.class); + i.setAction(intent.getAction()); + i.setData(intent.getData()); + context.startService(i); + } + + @Override + protected void onHandleIntent(@Nullable Intent intent) + { + ACTION_TURNON = getString(R.string.intent_action_turnon); + ACTION_TURNOFF = getString(R.string.intent_action_turnoff); + + DATAURI_TURNON = getString(R.string.intent_data_host_turnon); + DATAURI_TURNOFF = getString(R.string.intent_data_host_turnoff); + + // Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); + Log.i(TAG,"Received start intent"); + + mStartIntent = intent; + + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) + { + + } + else + { + carryOn(); + } + + } + + + private void carryOn() + { + boolean turnOn = true; + if (mStartIntent != null) + { + final String action = mStartIntent.getAction(); + final String data = mStartIntent.getDataString(); + if (ACTION_TURNON.equals(action) || (data!=null && data.contains(DATAURI_TURNON))) + { + turnOn = true; + Log.i(TAG,"Action/data to turn on hotspot"); + } + else if (ACTION_TURNOFF.equals(action)|| (data!=null && data.contains(DATAURI_TURNOFF))) + { + turnOn = false; + Log.i(TAG,"Action/data to turn off hotspot"); + } + + if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) + { + hotspotOreo(turnOn); + } + else + { + turnOnHotspotPreOreo(turnOn); + } + } + } + + private boolean turnOnHotspotPreOreo(boolean turnOn) + { + { + WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); + + Method[] methods = wifiManager.getClass().getDeclaredMethods(); + for (Method method : methods) + { + if (method.getName().equals("setWifiApEnabled")) + { + try + { + if (turnOn) + { + wifiManager.setWifiEnabled(false); //Turning off wifi because tethering requires wifi to be off + method.invoke(wifiManager, null, true); //Activating tethering + return true; + } + else + { + method.invoke(wifiManager, null, false); //Deactivating tethering + wifiManager.setWifiEnabled(true); //Turning on wifi ...should probably be done from a saved setting + return true; + } + } + catch (Exception e) + { + return false; + } + } + } + + //Error setWifiApEnabled not found + return false; + } + } + + /** + * + */ + @RequiresApi(api = Build.VERSION_CODES.O) + private void hotspotOreo(boolean turnOn){ + + if (mMyOreoWifiManager ==null) + { + mMyOreoWifiManager = new MyOreoWifiManager(this); + } + + if (turnOn) + { + //this dont work + MyOnStartTetheringCallback callback = new MyOnStartTetheringCallback() + { + @Override + public void onTetheringStarted() + { + startForeground(FOREGROUND_ID, buildForegroundNotification()); + } + + @Override + public void onTetheringFailed() + { + + } + }; + + mMyOreoWifiManager.startTethering(callback); + } + else + { + mMyOreoWifiManager.stopTethering(); + stopForeground(true); + stopSelf(); + } + } + + //**************************************************************************************** + + + /** + * Build low priority notification for running this service as a foreground service. + * @return + */ + private Notification buildForegroundNotification() + { + registerNotifChnnl(this); + + Intent stopIntent = new Intent(this, HotSpotIntentService.class); + stopIntent.setAction(getString(R.string.intent_action_turnoff)); + + PendingIntent pendingIntent = PendingIntent.getService(this,0, stopIntent, 0); + + NotificationCompat.Builder b=new NotificationCompat.Builder(this,CHANNEL_ID); + b.setOngoing(true) + .setContentTitle("WifiHotSpot is On") + .addAction(new NotificationCompat.Action( + R.drawable.turn_off, + "TURN OFF HOTSPOT", + pendingIntent + )) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setCategory(Notification.CATEGORY_SERVICE) + .setSmallIcon(R.drawable.notif_hotspot_black_24dp); + + + return(b.build()); + } + + private static void registerNotifChnnl(Context context) + { + if (Build.VERSION.SDK_INT >= 26) + { + NotificationManager mngr = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); + if (mngr.getNotificationChannel(CHANNEL_ID) != null) + { + return; + } + // + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID, + context.getString(R.string.notification_chnnl), + NotificationManager.IMPORTANCE_LOW); + // Configure the notification channel. + channel.setDescription(context.getString(R.string.notification_chnnl_location_descr)); + channel.enableLights(false); + channel.enableVibration(false); + mngr.createNotificationChannel(channel); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/actions/wifi_router/MagicActivity.java b/app/src/main/java/com/jens/automation2/actions/wifi_router/MagicActivity.java new file mode 100644 index 0000000..5ee99c3 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/actions/wifi_router/MagicActivity.java @@ -0,0 +1,58 @@ +package com.jens.automation2.actions.wifi_router; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +public class MagicActivity extends Activity +{ + public static void useMagicActivityToTurnOn(Context c) + { + Uri uri = new Uri.Builder().scheme(c.getString(R.string.intent_data_scheme)).authority(c.getString(R.string.intent_data_host_turnon)).build(); + Toast.makeText(c,"Turn on. Uri: "+uri.toString(),Toast.LENGTH_LONG).show(); + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(uri); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + c.startActivity(i); + } + + public static void useMagicActivityToTurnOff(Context c) + { + Uri uri = new Uri.Builder().scheme(c.getString(R.string.intent_data_scheme)).authority(c.getString(R.string.intent_data_host_turnoff)).build(); + Toast.makeText(c,"Turn off. Uri: "+uri.toString(),Toast.LENGTH_LONG).show(); + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(uri); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + c.startActivity(i); + } + + private static final String TAG = MagicActivity.class.getSimpleName(); + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + Log.e(TAG, "onCreate"); + } + + @Override + void onPermissionsOkay() + { + carryOnWithHotSpotting(); + } + + /** + * The whole purpose of this activity - to start {@link HotSpotIntentService} + * This may be called straright away in {@code onCreate} or after permissions granted. + */ + private void carryOnWithHotSpotting() + { + Intent intent = getIntent(); + HotSpotIntentService.start(this, intent); + finish(); + } +} diff --git a/app/src/main/java/com/jens/automation2/actions/wifi_router/MyOnStartTetheringCallback.java b/app/src/main/java/com/jens/automation2/actions/wifi_router/MyOnStartTetheringCallback.java new file mode 100644 index 0000000..0d26ebd --- /dev/null +++ b/app/src/main/java/com/jens/automation2/actions/wifi_router/MyOnStartTetheringCallback.java @@ -0,0 +1,15 @@ +package com.jens.automation2.actions.wifi_router; + +public abstract class MyOnStartTetheringCallback +{ + /** + * Called when tethering has been successfully started. + */ + public abstract void onTetheringStarted(); + + /** + * Called when starting tethering failed. + */ + public abstract void onTetheringFailed(); + +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/actions/wifi_router/MyOreoWifiManager.java b/app/src/main/java/com/jens/automation2/actions/wifi_router/MyOreoWifiManager.java new file mode 100644 index 0000000..a00b4c2 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/actions/wifi_router/MyOreoWifiManager.java @@ -0,0 +1,205 @@ +package com.jens.automation2.actions.wifi_router; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.os.Handler; +import android.util.Log; +import androidx.annotation.RequiresApi; + +import com.android.dx.stock.ProxyBuilder; + +import java.io.File; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * Created by jonro on 19/03/2018. + */ + +@RequiresApi(api = Build.VERSION_CODES.O) +public class MyOreoWifiManager +{ + private static final String TAG = MyOreoWifiManager.class.getSimpleName(); + + private Context mContext; + private WifiManager mWifiManager; + private ConnectivityManager mConnectivityManager; + + public MyOreoWifiManager(Context c) + { + mContext = c; + mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + mConnectivityManager = (ConnectivityManager) mContext.getSystemService(ConnectivityManager.class); + } + + /** + * This sets the Wifi SSID and password + * Call this before {@code startTethering} if app is a system/privileged app + * Requires: android.permission.TETHER_PRIVILEGED which is only granted to system apps + */ + public void configureHotspot(String name, String password) + { + WifiConfiguration apConfig = new WifiConfiguration(); + apConfig.SSID = name; + apConfig.preSharedKey = password; + apConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + try + { + Method setConfigMethod = mWifiManager.getClass().getMethod("setWifiApConfiguration", WifiConfiguration.class); + boolean status = (boolean) setConfigMethod.invoke(mWifiManager, apConfig); + Log.d(TAG, "setWifiApConfiguration - success? " + status); + } + catch (Exception e) + { + Log.e(TAG, "Error in configureHotspot"); + e.printStackTrace(); + } + } + + /** + * Checks where tethering is on. + * This is determined by the getTetheredIfaces() method, + * that will return an empty array if not devices are tethered + * + * @return true if a tethered device is found, false if not found + */ + public boolean isTetherActive() + { + try + { + Method method = mConnectivityManager.getClass().getDeclaredMethod("getTetheredIfaces"); + if (method == null) + { + Log.e(TAG, "getTetheredIfaces is null"); + } + else + { + String res[] = (String []) method.invoke(mConnectivityManager, null); + Log.d(TAG, "getTetheredIfaces invoked"); + Log.d(TAG, Arrays.toString(res)); + if (res.length > 0) + { + return true; + } + } + } + catch (Exception e) + { + Log.e(TAG, "Error in getTetheredIfaces"); + e.printStackTrace(); + } + return false; + } + + /** + * This enables tethering using the ssid/password defined in Settings App>Hotspot & tethering + * Does not require app to have system/privileged access + * Credit: Vishal Sharma - https://stackoverflow.com/a/52219887 + */ + public boolean startTethering(final MyOnStartTetheringCallback callback) + { + + // On Pie if we try to start tethering while it is already on, it will + // be disabled. This is needed when startTethering() is called programmatically. + if (isTetherActive()) + { + Log.d(TAG, "Tether already active, returning"); + return false; + } + + File outputDir = mContext.getCodeCacheDir(); + Object proxy; + try + { + proxy = ProxyBuilder.forClass(OnStartTetheringCallbackClass()) + .dexCache(outputDir).handler(new InvocationHandler() + { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + switch (method.getName()) + { + case "onTetheringStarted": + callback.onTetheringStarted(); + break; + case "onTetheringFailed": + callback.onTetheringFailed(); + break; + default: + ProxyBuilder.callSuper(proxy, method, args); + } + return null; + } + + }).build(); + } + catch (Exception e) + { + Log.e(TAG, "Error in enableTethering ProxyBuilder"); + e.printStackTrace(); + return false; + } + + Method method = null; + try + { + method = mConnectivityManager.getClass().getDeclaredMethod("startTethering", int.class, boolean.class, OnStartTetheringCallbackClass(), Handler.class); + if (method == null) + { + Log.e(TAG, "startTetheringMethod is null"); + } + else + { + method.invoke(mConnectivityManager, ConnectivityManager.TYPE_MOBILE, false, proxy, null); + Log.d(TAG, "startTethering invoked"); + } + return true; + } + catch (Exception e) + { + Log.e(TAG, "Error in enableTethering"); + e.printStackTrace(); + } + return false; + } + + public void stopTethering() + { + try + { + Method method = mConnectivityManager.getClass().getDeclaredMethod("stopTethering", int.class); + if (method == null) + { + Log.e(TAG, "stopTetheringMethod is null"); + } + else + { + method.invoke(mConnectivityManager, ConnectivityManager.TYPE_MOBILE); + Log.d(TAG, "stopTethering invoked"); + } + } + catch (Exception e) + { + Log.e(TAG, "stopTethering error: " + e.toString()); + e.printStackTrace(); + } + } + + private Class OnStartTetheringCallbackClass() + { + try + { + return Class.forName("android.net.ConnectivityManager$OnStartTetheringCallback"); + } + catch (ClassNotFoundException e) + { + Log.e(TAG, "OnStartTetheringCallbackClass error: " + e.toString()); + e.printStackTrace(); + } + return null; + } +}