diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..b3405b3 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +My Application \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..681f41a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..d291b3d --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..37a7509 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..b433c0a --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,30 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + defaultConfig { + applicationId "com.hareg.myapplication" + minSdkVersion 26 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.linkedin.dexmaker:dexmaker:2.25.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/hareg/myapplication/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/hareg/myapplication/ExampleInstrumentedTest.java new file mode 100644 index 0000000..a11fbb6 --- /dev/null +++ b/app/src/androidTest/java/com/hareg/myapplication/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.hareg.myapplication; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("com.hareg.myapplication", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f60d984 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/hareg/myapplication/MainActivity.java b/app/src/main/java/com/hareg/myapplication/MainActivity.java new file mode 100644 index 0000000..fbd4367 --- /dev/null +++ b/app/src/main/java/com/hareg/myapplication/MainActivity.java @@ -0,0 +1,68 @@ +package com.hareg.myapplication; + +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; + +public class MainActivity extends PermissionsActivity { + private static final String TAG="MainActivity"; + private Button startBtn; + private Button stopBtn; + + @RequiresApi(api = Build.VERSION_CODES.O) + MyOreoWifiManager mMyOreoWifiManager; + + + @Override + void onPermissionsOkay() { + + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + startBtn = (Button)findViewById(R.id.start_btn); + stopBtn = (Button)findViewById(R.id.stop_btn); + + mMyOreoWifiManager = new MyOreoWifiManager(this); + + startBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG,"startBtn onClick" ); + + MyOnStartTetheringCallback callback = new MyOnStartTetheringCallback() { + @Override + public void onTetheringStarted() { + + } + + @Override + public void onTetheringFailed() { + + } + }; + mMyOreoWifiManager.startTethering(callback); + + } + }); + + stopBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG,"stopBtn onClick" ); + mMyOreoWifiManager.stopTethering(); + } + }); + + + + } +} diff --git a/app/src/main/java/com/hareg/myapplication/MyOnStartTetheringCallback.java b/app/src/main/java/com/hareg/myapplication/MyOnStartTetheringCallback.java new file mode 100644 index 0000000..a83e60d --- /dev/null +++ b/app/src/main/java/com/hareg/myapplication/MyOnStartTetheringCallback.java @@ -0,0 +1,14 @@ +package com.hareg.myapplication; + +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/hareg/myapplication/MyOreoWifiManager.java b/app/src/main/java/com/hareg/myapplication/MyOreoWifiManager.java new file mode 100644 index 0000000..44805be --- /dev/null +++ b/app/src/main/java/com/hareg/myapplication/MyOreoWifiManager.java @@ -0,0 +1,131 @@ +package com.hareg.myapplication; + +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; + +/** + * 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(); + } + } + + /** + * 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) { + 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; + } +} diff --git a/app/src/main/java/com/hareg/myapplication/PermissionsActivity.java b/app/src/main/java/com/hareg/myapplication/PermissionsActivity.java new file mode 100644 index 0000000..771876e --- /dev/null +++ b/app/src/main/java/com/hareg/myapplication/PermissionsActivity.java @@ -0,0 +1,116 @@ +package com.hareg.myapplication; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + + +/** + * Created by jonro on 31/03/2018. + */ + +public abstract class PermissionsActivity extends AppCompatActivity { + + static final int MY_PERMISSIONS_MANAGE_WRITE_SETTINGS = 100 ; + static final int MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 69; + + private boolean mLocationPermission = false; + private boolean mSettingPermission = true; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + settingPermission(); + /** + * Locations permission done in onActrivityResult + */ + locationsPermission(); + + if (mLocationPermission && mSettingPermission) onPermissionsOkay(); + } + + + private void settingPermission() { + mSettingPermission = true; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (!Settings.System.canWrite(getApplicationContext())) { + mSettingPermission = false; + Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, Uri.parse("package:" + getPackageName())); + startActivityForResult(intent, MY_PERMISSIONS_MANAGE_WRITE_SETTINGS); + } + } + } + + + private void locationsPermission(){ + mLocationPermission = true; + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) + != PackageManager.PERMISSION_GRANTED) { + mLocationPermission = false; + // Permission is not granted + // Should we show an explanation? + if (ActivityCompat.shouldShowRequestPermissionRationale(this, + Manifest.permission.ACCESS_COARSE_LOCATION)) { + + // Show an explanation to the user *asynchronously* -- don't block + // this thread waiting for the user's response! After the user + // sees the explanation, try again to request the permission. + + } else { + + // No explanation needed; request the permission + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, + MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION); + + // MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION is an + // app-defined int constant. The callback method gets the + // result of the request. + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // Check which request we're responding to + if (requestCode == MY_PERMISSIONS_MANAGE_WRITE_SETTINGS) { + // Make sure the request was successful + if (resultCode == RESULT_OK) { + mSettingPermission = true; + if (!mLocationPermission) locationsPermission(); + } else { + settingPermission(); + } + } + + if (requestCode == MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION) { + // Make sure the request was successful + if (resultCode == RESULT_OK) { + mLocationPermission = true; + if (!mSettingPermission) settingPermission(); + } else { + locationsPermission(); + } + } + + if (mLocationPermission && mSettingPermission) onPermissionsOkay(); + + } + + + + abstract void onPermissionsOkay(); + + +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..2139ace --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,38 @@ + + + + + +