├── .github └── dependabot.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ ├── app-release.apk │ └── output-metadata.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── android1500 │ │ └── gpssetter │ │ └── ExampleInstrumentedTest.kt │ ├── debug │ ├── ic_launcher-playstore.png │ └── res │ │ └── drawable │ │ └── ic_launcher_background.xml │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── xposed_init │ │ └── yukihookapi_init │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── android1500 │ │ │ └── gpssetter │ │ │ ├── App.kt │ │ │ ├── adapter │ │ │ └── FavListAdapter.kt │ │ │ ├── module │ │ │ ├── AppModule.kt │ │ │ └── util │ │ │ │ └── ScopeQualifiers.kt │ │ │ ├── repository │ │ │ └── FavouriteRepository.kt │ │ │ ├── room │ │ │ ├── AppDatabase.kt │ │ │ ├── Favourite.kt │ │ │ └── FavouriteDao.kt │ │ │ ├── ui │ │ │ ├── MapActivity.kt │ │ │ ├── SettingsActivity.kt │ │ │ └── viewmodel │ │ │ │ └── MainViewModel.kt │ │ │ ├── update │ │ │ ├── GitHubRelease.kt │ │ │ ├── GitHubService.kt │ │ │ └── UpdateChecker.kt │ │ │ ├── utils │ │ │ ├── JoystickService.kt │ │ │ ├── NotificationsChannel.kt │ │ │ ├── PrefManager.kt │ │ │ └── ext │ │ │ │ ├── Ext+Context.kt │ │ │ │ ├── Ext+LatLng.kt │ │ │ │ └── Ext+ViewModel.kt │ │ │ └── xposed │ │ │ ├── HookEntry.kt │ │ │ ├── LocationHook.kt │ │ │ └── Xshare.kt │ └── res │ │ ├── drawable │ │ ├── background_menu.xml │ │ ├── bottom_sheet_background.xml │ │ ├── ic_accuracy.xml │ │ ├── ic_advance_hook.xml │ │ ├── ic_back_arrow.xml │ │ ├── ic_baseline_favourite.xml │ │ ├── ic_baseline_menu_24.xml │ │ ├── ic_baseline_my_location_24.xml │ │ ├── ic_baseline_search_24.xml │ │ ├── ic_dark_mode.xml │ │ ├── ic_delete.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_map_type.xml │ │ ├── ic_outline_favourite_list.xml │ │ ├── ic_outline_info_24.xml │ │ ├── ic_outline_settings_24.xml │ │ ├── ic_play.xml │ │ ├── ic_random_position.xml │ │ ├── ic_stop.xml │ │ ├── ic_update.xml │ │ ├── outline_joystick.xml │ │ └── search_box_background.xml │ │ ├── layout │ │ ├── about.xml │ │ ├── activity_map.xml │ │ ├── bottom_sheet.xml │ │ ├── dialog_layout.xml │ │ ├── drawer_header.xml │ │ ├── fav.xml │ │ ├── fav_items.xml │ │ ├── include_search.xml │ │ ├── joystick_layout.xml │ │ ├── map_container.xml │ │ ├── settings_activity.xml │ │ └── update_dialog.xml │ │ ├── menu │ │ └── main_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── values-fr │ │ └── strings.xml │ │ ├── values-night │ │ └── colors.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── attrs.xml │ │ ├── bools.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── overscroll..xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ ├── provider_paths.xml │ │ └── setting.xml │ └── test │ └── java │ └── com │ └── android1500 │ └── gpssetter │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── .gitignore │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | version: 2 6 | updates: 7 | - package-ecosystem: gradle 8 | directory: "/" 9 | schedule: 10 | interval: daily 11 | time: "21:00" 12 | open-pull-requests-limit: 10 13 | target-branch: master 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | GPS Setter -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 45 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GPS Setter 2 | 3 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/Android1500/GpsSetter) 4 | [![Github All Releases](https://img.shields.io/github/downloads/Android1500/GpsSetter/total?label=Release)]() 5 | [![Blank](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.android1500.gpssetter/total?label=LSPosed%20Repo&logo=Android&style=flat&labelColor=F48FB1&logoColor=ffffff)](https://github.com/Xposed-Modules-Repo/com.android1500.gpssetter/releases) 6 | [![GitHub stars](https://img.shields.io/github/stars/Android1500/GpsSetter)](https://github.com/Android1500/GpsSetter/stargazers) 7 | [![GitHub issues](https://img.shields.io/github/issues/Android1500/GpsSetter)](https://github.com/Android1500/GpsSetter/issues) 8 | 9 | 10 | ### ⚠️ Copyright Notice 11 | 12 | What we hate most about is the modification of our texts, code which absolutely means they want others to think all the work is done by THEM instead of others, though they actually helped NOTHING with the module development. 13 | 14 | We also hate someone use our effort and do their modification and sell them without our permission which is wrong. 15 | 16 | 17 | Although our project was open source before, we didn't set up a license, so **ALL COPYRIGHTS RESERVED**. And from now on we decline ANY modification or pre-patched apk. but you should not release your patched apk anywhere or sell. 18 | 19 | 20 | ![](https://github.com/Xposed-Modules-Repo/com.android1500.gpssetter/blob/main/banner.png) 21 | 22 | ### Support/Discussion: [XDA thread](https://forum.xda-developers.com/t/app-xposed-8-1-12x-gps-setter-set-device-location.4454879/) 23 | 24 | 25 | As most of GPS spoof app not working anymore coz some are old and some are not proper implement with current OS so i made myself a GPS setter which called GPS Setter it will help you set location where you want from malicious app coz some bad apps collect user location for advertisement purpose or other purpose.... who knows?? so in such case this app will work like charm for prevent current location## . Its still in beta stage coz its still have some bugs which will be fixed in upcoming updates. 26 | 27 | 28 | 29 | 30 | ## Disclaimer: 31 | 32 | The Author and Contributors of Gps Setter take no responsibility for any loss of data or damage to your device or any other consequences that arise as a result of using this application Use it your own Risk. 33 | 34 | ## Compatibility: 35 | 36 | This Module will Support Android 8.1 + 37 | 38 | ## Features: 39 | 40 | -> Spoof gps location. 41 | 42 | -> Material Design 2 43 | 44 | -> Add Favorite Place 45 | 46 | 47 | ## REQUIREMENT 48 | 49 | -> Rooted Device 50 | 51 | -> Xposed Framework Installed **(Lsposed or Edxposed)** 52 | 53 | 54 | ## Component Use :- 55 | 56 | -> Hilt 57 | 58 | -> Room DataBase 59 | 60 | -> ViewModel 61 | 62 | -> MVVM Architecture 63 | 64 | -> Retrofit 65 | 66 | ## Support :- 67 | 68 | As you know this project does not have advertising anymore so it's hard to alive this project if you want to support further development of this project you can donate by BTC. This form of support is meant to compensate for my time dedicated to the community. 69 | 70 | You can support the project by donating to below addresses. 71 | | Type | Address | 72 | | ------------- | ------------- | 73 | | Bitcoin | 12eai5UkUtBe1gzNYvBvjTNTDvqVBFbQzy | 74 | 75 | 76 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /google-services.json -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' 5 | id 'dagger.hilt.android.plugin' 6 | id 'kotlin-kapt' 7 | id 'kotlin-parcelize' 8 | id 'com.google.devtools.ksp' version '1.8.10-1.0.9' 9 | } 10 | 11 | def tagName = 'v1.3.0-beta' 12 | 13 | android { 14 | compileSdk 33 15 | defaultConfig { 16 | applicationId "com.android1500.gpssetter" 17 | minSdk 27 18 | targetSdk 33 19 | versionCode 1310 20 | versionName tagName 21 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 22 | buildConfigField("String", "TAG_NAME", "\"${tagName}\"") 23 | } 24 | 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_11 34 | targetCompatibility JavaVersion.VERSION_11 35 | } 36 | kotlinOptions { 37 | jvmTarget = '11' 38 | } 39 | buildFeatures { 40 | viewBinding true 41 | buildConfig true 42 | } 43 | namespace 'com.android1500.gpssetter' 44 | buildToolsVersion '33.0.0' 45 | } 46 | 47 | dependencies { 48 | 49 | implementation 'androidx.core:core-ktx:1.9.0' 50 | implementation 'androidx.appcompat:appcompat:1.7.0-alpha01' 51 | implementation 'com.google.android.material:material:1.9.0-alpha01' 52 | implementation 'androidx.activity:activity-ktx:1.6.1' 53 | implementation 'com.google.android.gms:play-services-maps:18.1.0' 54 | implementation 'androidx.constraintlayout:constraintlayout:2.2.0-alpha05' 55 | implementation 'androidx.preference:preference:1.2.0' 56 | implementation 'androidx.datastore:datastore-preferences:1.0.0' 57 | implementation "androidx.drawerlayout:drawerlayout:1.2.0-alpha01" 58 | implementation 'com.google.android.gms:play-services-location:21.0.1' 59 | 60 | 61 | testImplementation 'junit:junit:4.13.2' 62 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 63 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 64 | 65 | //Lifecycle 66 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0-alpha04" 67 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha04" 68 | 69 | //Hilt 70 | implementation "com.google.dagger:hilt-android:2.44.2" 71 | kapt "com.google.dagger:hilt-compiler:2.44.2" 72 | 73 | //Timber 74 | implementation 'com.jakewharton.timber:timber:5.0.1' 75 | 76 | //Room 77 | implementation 'androidx.room:room-runtime:2.5.0' 78 | implementation 'androidx.room:room-ktx:2.5.0' 79 | kapt 'androidx.room:room-compiler:2.5.0' 80 | 81 | //HiddenApiBypass 82 | implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3' 83 | 84 | //Xposed 85 | compileOnly 'de.robv.android.xposed:api:82' 86 | 87 | // Retrofit 88 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 89 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0' 90 | 91 | implementation ("dev.rikka.rikkax.material:material-preference:2.0.0") { 92 | exclude group: "dev.rikka.rikkax.appcompat", module: "appcompat" 93 | //exclude group: "androidx.lifecycle", module: "lifecycle-viewmodel-ktx" 94 | } 95 | 96 | implementation 'com.highcapable.yukihookapi:api:1.1.11' 97 | ksp 'com.highcapable.yukihookapi:ksp-xposed:1.1.11' 98 | implementation 'com.facebook.shimmer:shimmer:0.5.0' 99 | implementation 'com.github.KieronQuinn:MonetCompat:0.4.1' 100 | implementation 'androidx.palette:palette:1.0.0' 101 | 102 | 103 | implementation 'io.github.controlwear:virtualjoystick:1.10.1' 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | 24 | -keep class com.android1500.gpssetter.xposed.XposedHook{*;} 25 | -keep class com.android1500.gpssetter.xposed.Xshare{ *;} 26 | -keepnames class com.android1500.gpssetter.selfhook.XposedSelfHooks{*;} 27 | -keep class de.robv.android.xposed.**{*;} 28 | -keepnames class de.robv.android.xposed.** 29 | 30 | -repackageclasses 31 | -allowaccessmodification 32 | -overloadaggressively -------------------------------------------------------------------------------- /app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/release/app-release.apk -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.android1500.gpssetter", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 1310, 15 | "versionName": "v1.3.0-beta", 16 | "outputFile": "app-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/android1500/gpssetter/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.android1500.gpssetter", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/debug/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/debug/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/debug/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 25 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 49 | 50 | 55 | 58 | 59 | 60 | 63 | 66 | 69 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | com.android1500.gpssetter.xposed.HookEntry_YukiHookXposedInit -------------------------------------------------------------------------------- /app/src/main/assets/yukihookapi_init: -------------------------------------------------------------------------------- 1 | com.android1500.gpssetter.xposed.HookEntry -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/App.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter 2 | 3 | import androidx.appcompat.app.AppCompatDelegate 4 | import com.android1500.gpssetter.utils.PrefManager 5 | import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication 6 | import com.kieronquinn.monetcompat.core.MonetCompat 7 | import dagger.hilt.android.HiltAndroidApp 8 | import kotlinx.coroutines.CoroutineScope 9 | import kotlinx.coroutines.Dispatchers 10 | import timber.log.Timber 11 | 12 | lateinit var gsApp: App 13 | 14 | 15 | @HiltAndroidApp 16 | class App : ModuleApplication() { 17 | val globalScope = CoroutineScope(Dispatchers.Default) 18 | 19 | companion object { 20 | fun commonInit() { 21 | if (BuildConfig.DEBUG) { 22 | Timber.plant(Timber.DebugTree()) 23 | } 24 | } 25 | } 26 | 27 | override fun onCreate() { 28 | super.onCreate() 29 | gsApp = this 30 | commonInit() 31 | MonetCompat.enablePaletteCompat() 32 | AppCompatDelegate.setDefaultNightMode(PrefManager.darkTheme) 33 | 34 | } 35 | 36 | 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/adapter/FavListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import androidx.recyclerview.widget.DiffUtil 9 | import androidx.recyclerview.widget.ListAdapter 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.android1500.gpssetter.R 12 | import com.android1500.gpssetter.room.Favourite 13 | 14 | class FavListAdapter( 15 | ) : ListAdapter(FavListComparetor()) { 16 | 17 | var onItemClick : ((Favourite) -> Unit)? = null 18 | var onItemDelete : ((Favourite) -> Unit)? = null 19 | 20 | inner class ViewHolder(view: View): RecyclerView.ViewHolder(view) { 21 | 22 | private val address: TextView = view.findViewById(R.id.address) 23 | private val delete: ImageView = itemView.findViewById(R.id.del) 24 | 25 | fun bind(favorite: Favourite){ 26 | address.text = favorite.address 27 | delete.setOnClickListener { 28 | onItemDelete?.invoke(favorite) 29 | } 30 | address.setOnClickListener { 31 | onItemClick?.invoke(favorite) 32 | } 33 | } 34 | } 35 | 36 | class FavListComparetor : DiffUtil.ItemCallback() { 37 | override fun areItemsTheSame(oldItem: Favourite, newItem: Favourite): Boolean { 38 | return oldItem.address == newItem.address 39 | } 40 | 41 | override fun areContentsTheSame(oldItem: Favourite, newItem: Favourite): Boolean { 42 | return oldItem == newItem 43 | } 44 | 45 | } 46 | 47 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 48 | val view = LayoutInflater.from(parent.context) 49 | .inflate(R.layout.fav_items, parent, false) 50 | return ViewHolder(view) 51 | } 52 | 53 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 54 | val item = getItem(position) 55 | if (item != null){ 56 | holder.bind(item) 57 | 58 | } 59 | 60 | } 61 | 62 | 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/module/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.module 2 | 3 | import android.app.Application 4 | import android.app.DownloadManager 5 | import android.content.Context 6 | import androidx.room.Room 7 | import com.android1500.gpssetter.module.util.ApplicationScope 8 | import com.android1500.gpssetter.utils.PrefManager 9 | import com.android1500.gpssetter.room.AppDatabase 10 | import com.android1500.gpssetter.room.FavouriteDao 11 | import com.android1500.gpssetter.update.GitHubService 12 | import dagger.Module 13 | import dagger.Provides 14 | import dagger.hilt.InstallIn 15 | import dagger.hilt.components.SingletonComponent 16 | import kotlinx.coroutines.CoroutineScope 17 | import kotlinx.coroutines.SupervisorJob 18 | import retrofit2.Retrofit 19 | import retrofit2.converter.gson.GsonConverterFactory 20 | import javax.inject.Singleton 21 | 22 | 23 | @Module 24 | @InstallIn(SingletonComponent::class) 25 | object AppModule{ 26 | 27 | 28 | 29 | @Singleton 30 | @Provides 31 | fun createGitHubService(): Retrofit = 32 | Retrofit.Builder() 33 | .baseUrl("https://api.github.com/repos/Android1500/GpsSetter/") 34 | .addConverterFactory(GsonConverterFactory.create()) 35 | .build() 36 | 37 | @Singleton 38 | @Provides 39 | fun provideDownloadManger(application: Application) = 40 | application.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager 41 | 42 | 43 | 44 | @Singleton 45 | @Provides 46 | fun provideGithubService(retrofit: Retrofit): GitHubService = 47 | retrofit.create(GitHubService::class.java) 48 | 49 | 50 | @Provides 51 | @Singleton 52 | fun provideDatabase(application: Application, callback: AppDatabase.Callback) 53 | = Room.databaseBuilder(application, AppDatabase::class.java, "user_database") 54 | .allowMainThreadQueries() 55 | .fallbackToDestructiveMigration() 56 | .addCallback(callback) 57 | .build() 58 | 59 | 60 | @Singleton 61 | @Provides 62 | fun providesUserDao(favouriteDatabase: AppDatabase) : FavouriteDao = 63 | favouriteDatabase.favouriteDao() 64 | 65 | @Singleton 66 | @Provides 67 | fun provideSettingRepo() : PrefManager = 68 | PrefManager 69 | 70 | @ApplicationScope 71 | @Provides 72 | @Singleton 73 | fun providesApplicationScope() = CoroutineScope(SupervisorJob()) 74 | 75 | } 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/module/util/ScopeQualifiers.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.module.util 2 | 3 | import javax.inject.Qualifier 4 | 5 | 6 | @Retention(AnnotationRetention.RUNTIME) 7 | @Qualifier 8 | annotation class ApplicationScope 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/repository/FavouriteRepository.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.repository 2 | 3 | 4 | import androidx.annotation.WorkerThread 5 | import com.android1500.gpssetter.room.Favourite 6 | import com.android1500.gpssetter.room.FavouriteDao 7 | import kotlinx.coroutines.flow.Flow 8 | import javax.inject.Inject 9 | 10 | class FavouriteRepository @Inject constructor(private val favouriteDao: FavouriteDao) { 11 | 12 | val getAllFavourites: Flow> 13 | get() = favouriteDao.getAllFavourites() 14 | 15 | @Suppress("RedundantSuspendModifier") 16 | @WorkerThread 17 | suspend fun addNewFavourite(favourite: Favourite) : Long { 18 | return favouriteDao.insertToRoomDatabase(favourite) 19 | } 20 | 21 | suspend fun deleteFavourite(favourite: Favourite) { 22 | favouriteDao.deleteSingleFavourite(favourite) 23 | } 24 | 25 | 26 | fun getSingleFavourite(id: Long) : Favourite { 27 | return favouriteDao.getSingleFavourite(id) 28 | 29 | } 30 | 31 | 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/room/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.room 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.sqlite.db.SupportSQLiteDatabase 6 | import com.android1500.gpssetter.module.util.ApplicationScope 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.launch 9 | import javax.inject.Inject 10 | 11 | @Database(entities = [Favourite::class], version = 1,exportSchema = false) 12 | abstract class AppDatabase : RoomDatabase() { 13 | abstract fun favouriteDao(): FavouriteDao 14 | class Callback @Inject constructor(@ApplicationScope private val applicationScope: CoroutineScope) : RoomDatabase.Callback(){ 15 | override fun onCreate(db: SupportSQLiteDatabase) { 16 | super.onCreate(db) 17 | applicationScope.launch { 18 | 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/room/Favourite.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.room 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | @Entity 7 | data class Favourite( 8 | @PrimaryKey(autoGenerate = false) 9 | val id: Long? = null, 10 | val address: String?, 11 | val lat: Double?, 12 | val lng: Double? 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/room/FavouriteDao.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.room 2 | import androidx.room.* 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | @Dao 6 | interface FavouriteDao { 7 | 8 | //insert data to room database 9 | @Insert(onConflict = OnConflictStrategy.IGNORE) 10 | suspend fun insertToRoomDatabase(favourite: Favourite) : Long 11 | 12 | // for update single favourite 13 | @Update 14 | suspend fun updateUserDetails(favourite: Favourite) 15 | 16 | //delete single favourite 17 | @Delete 18 | suspend fun deleteSingleFavourite(favourite: Favourite) 19 | 20 | //get all Favourite inserted to room database...normally this is supposed to be a list of Favourites 21 | @Transaction 22 | @Query("SELECT * FROM favourite ORDER BY id DESC") 23 | fun getAllFavourites() : Flow> 24 | 25 | //get single favourite inserted to room database 26 | @Transaction 27 | @Query("SELECT * FROM favourite WHERE id = :id ORDER BY id DESC") 28 | fun getSingleFavourite(id: Long) : Favourite 29 | 30 | 31 | 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/ui/MapActivity.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.ui 2 | 3 | import android.Manifest 4 | import android.annotation.SuppressLint 5 | import android.app.Notification 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.content.pm.PackageManager 9 | import android.content.res.ColorStateList 10 | import android.graphics.drawable.GradientDrawable 11 | import android.location.Address 12 | import android.location.Geocoder 13 | import android.location.Location 14 | import android.location.LocationManager 15 | import android.os.Bundle 16 | import android.os.Looper 17 | import android.provider.Settings 18 | import android.view.View 19 | import android.view.ViewGroup.MarginLayoutParams 20 | import android.view.inputmethod.EditorInfo 21 | import android.widget.EditText 22 | import android.widget.TextView 23 | import android.widget.Toast 24 | import androidx.activity.viewModels 25 | import androidx.appcompat.app.ActionBarDrawerToggle 26 | import androidx.appcompat.app.AlertDialog 27 | import androidx.appcompat.widget.AppCompatButton 28 | import androidx.core.app.ActivityCompat 29 | import androidx.core.app.NotificationCompat 30 | import androidx.core.graphics.ColorUtils 31 | import androidx.core.view.* 32 | import androidx.lifecycle.Lifecycle 33 | import androidx.lifecycle.lifecycleScope 34 | import androidx.lifecycle.repeatOnLifecycle 35 | import androidx.recyclerview.widget.LinearLayoutManager 36 | import androidx.recyclerview.widget.RecyclerView 37 | import com.android1500.gpssetter.BuildConfig 38 | import com.android1500.gpssetter.R 39 | import com.android1500.gpssetter.adapter.FavListAdapter 40 | import com.android1500.gpssetter.databinding.ActivityMapBinding 41 | import com.android1500.gpssetter.ui.viewmodel.MainViewModel 42 | import com.android1500.gpssetter.utils.JoystickService 43 | import com.android1500.gpssetter.utils.NotificationsChannel 44 | import com.android1500.gpssetter.utils.PrefManager 45 | import com.android1500.gpssetter.utils.ext.* 46 | import com.google.android.gms.location.* 47 | import com.google.android.gms.maps.CameraUpdateFactory 48 | import com.google.android.gms.maps.GoogleMap 49 | import com.google.android.gms.maps.OnMapReadyCallback 50 | import com.google.android.gms.maps.SupportMapFragment 51 | import com.google.android.gms.maps.model.BitmapDescriptorFactory 52 | import com.google.android.gms.maps.model.LatLng 53 | import com.google.android.gms.maps.model.Marker 54 | import com.google.android.gms.maps.model.MarkerOptions 55 | import com.google.android.material.bottomsheet.BottomSheetBehavior 56 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 57 | import com.google.android.material.elevation.ElevationOverlayProvider 58 | import com.google.android.material.progressindicator.LinearProgressIndicator 59 | import com.kieronquinn.monetcompat.app.MonetCompatActivity 60 | import dagger.hilt.android.AndroidEntryPoint 61 | import kotlinx.coroutines.* 62 | import kotlinx.coroutines.channels.awaitClose 63 | import kotlinx.coroutines.flow.callbackFlow 64 | import java.io.IOException 65 | import java.util.regex.Matcher 66 | import java.util.regex.Pattern 67 | import kotlin.properties.Delegates 68 | 69 | 70 | @AndroidEntryPoint 71 | class MapActivity : MonetCompatActivity(), OnMapReadyCallback, GoogleMap.OnMapClickListener { 72 | 73 | private val binding by lazy { ActivityMapBinding.inflate(layoutInflater) } 74 | private lateinit var mMap: GoogleMap 75 | private val viewModel by viewModels() 76 | private val update by lazy { viewModel.getAvailableUpdate() } 77 | private val notificationsChannel by lazy { NotificationsChannel() } 78 | private var favListAdapter: FavListAdapter = FavListAdapter() 79 | private var mMarker: Marker? = null 80 | private var mLatLng: LatLng? = null 81 | private var lat by Delegates.notNull() 82 | private var lon by Delegates.notNull() 83 | private var xposedDialog: AlertDialog? = null 84 | private lateinit var alertDialog: MaterialAlertDialogBuilder 85 | private lateinit var dialog: AlertDialog 86 | private lateinit var fusedLocationClient: FusedLocationProviderClient 87 | private val PERMISSION_ID = 42 88 | 89 | 90 | 91 | private val elevationOverlayProvider by lazy { 92 | ElevationOverlayProvider(this) 93 | } 94 | 95 | private val headerBackground by lazy { 96 | elevationOverlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded( 97 | resources.getDimension(R.dimen.bottom_sheet_elevation) 98 | ) 99 | } 100 | override val applyBackgroundColorToWindow = true 101 | 102 | 103 | override fun onCreate(savedInstanceState: Bundle?) { 104 | super.onCreate(savedInstanceState) 105 | WindowCompat.setDecorFitsSystemWindows(window, false) 106 | lifecycleScope.launchWhenCreated { 107 | monet.awaitMonetReady() 108 | setContentView(binding.root) 109 | } 110 | setSupportActionBar(binding.toolbar) 111 | initializeMap() 112 | isModuleEnable() 113 | updateChecker() 114 | setBottomSheet() 115 | setUpNavigationView() 116 | setupMonet() 117 | setupButton() 118 | setDrawer() 119 | if (PrefManager.isJoyStickEnable){ 120 | startService(Intent(this, JoystickService::class.java)) 121 | } 122 | 123 | } 124 | 125 | 126 | @SuppressLint("MissingPermission") 127 | private fun setupButton(){ 128 | binding.favourite.setOnClickListener { 129 | addFavouriteDialog() 130 | } 131 | binding.getlocationContainer.setOnClickListener { 132 | getLastLocation() 133 | } 134 | 135 | if (viewModel.isStarted) { 136 | binding.bottomSheetContainer.startSpoofing.visibility = View.GONE 137 | binding.bottomSheetContainer.stopButton.visibility = View.VISIBLE 138 | } 139 | 140 | binding.bottomSheetContainer.startSpoofing.setOnClickListener { 141 | viewModel.update(true, lat, lon) 142 | mLatLng.let { 143 | mMarker?.position = it!! 144 | } 145 | mMarker?.isVisible = true 146 | binding.bottomSheetContainer.startSpoofing.visibility = View.GONE 147 | binding.bottomSheetContainer.stopButton.visibility = View.VISIBLE 148 | lifecycleScope.launch { 149 | mLatLng?.getAddress(this@MapActivity)?.let { address -> 150 | address.collect{ value -> 151 | showStartNotification(value) 152 | } 153 | } 154 | } 155 | showToast(getString(R.string.location_set)) 156 | } 157 | binding.bottomSheetContainer.stopButton.setOnClickListener { 158 | mLatLng.let { 159 | viewModel.update(false, it!!.latitude, it.longitude) 160 | } 161 | mMarker?.isVisible = false 162 | binding.bottomSheetContainer.stopButton.visibility = View.GONE 163 | binding.bottomSheetContainer.startSpoofing.visibility = View.VISIBLE 164 | cancelNotification() 165 | showToast(getString(R.string.location_unset)) 166 | } 167 | 168 | } 169 | 170 | private fun setDrawer() { 171 | supportActionBar?.setDisplayShowTitleEnabled(false) 172 | val mDrawerToggle = object : ActionBarDrawerToggle( 173 | this, 174 | binding.container, 175 | binding.toolbar, 176 | R.string.drawer_open, 177 | R.string.drawer_close 178 | ) { 179 | override fun onDrawerClosed(view: View) { 180 | super.onDrawerClosed(view) 181 | invalidateOptionsMenu() 182 | } 183 | 184 | override fun onDrawerOpened(drawerView: View) { 185 | super.onDrawerOpened(drawerView) 186 | invalidateOptionsMenu() 187 | } 188 | } 189 | binding.container.setDrawerListener(mDrawerToggle) 190 | 191 | } 192 | 193 | private fun setBottomSheet(){ 194 | //val progress = binding.bottomSheetContainer.search.searchProgress 195 | 196 | val bottom = BottomSheetBehavior.from(binding.bottomSheetContainer.bottomSheet) 197 | with(binding.bottomSheetContainer){ 198 | 199 | search.searchBox.setOnEditorActionListener { v, actionId, _ -> 200 | 201 | if (actionId == EditorInfo.IME_ACTION_SEARCH) { 202 | 203 | if (isNetworkConnected()) { 204 | lifecycleScope.launch(Dispatchers.Main) { 205 | val getInput = v.text.toString() 206 | if (getInput.isNotEmpty()){ 207 | getSearchAddress(getInput).let { 208 | it.collect { result -> 209 | when(result) { 210 | is SearchProgress.Progress -> { 211 | // progress.visibility = View.VISIBLE 212 | } 213 | is SearchProgress.Complete -> { 214 | lat = result.lat 215 | lon = result.lon 216 | moveMapToNewLocation(true) 217 | } 218 | 219 | is SearchProgress.Fail -> { 220 | showToast(result.error!!) 221 | } 222 | } 223 | } 224 | } 225 | } 226 | } 227 | } else { 228 | showToast(getString(R.string.no_internet)) 229 | } 230 | return@setOnEditorActionListener true 231 | } 232 | return@setOnEditorActionListener false 233 | } 234 | 235 | } 236 | 237 | 238 | 239 | 240 | 241 | binding.mapContainer.map.setOnApplyWindowInsetsListener { _, insets -> 242 | 243 | val topInset: Int = insets.systemWindowInsetTop 244 | val bottomInset: Int = insets.systemWindowInsetBottom 245 | bottom.peekHeight = binding.bottomSheetContainer.searchLayout.measuredHeight + bottomInset 246 | 247 | val searchParams = binding.bottomSheetContainer.searchLayout.layoutParams as MarginLayoutParams 248 | searchParams.bottomMargin = bottomInset + searchParams.bottomMargin 249 | binding.navView.setPadding(0,topInset,0,0) 250 | 251 | insets.consumeSystemWindowInsets() 252 | } 253 | 254 | bottom.state = BottomSheetBehavior.STATE_COLLAPSED 255 | 256 | } 257 | 258 | private fun setupMonet() { 259 | val secondaryBackground = monet.getBackgroundColorSecondary(this) 260 | val background = monet.getBackgroundColor(this) 261 | binding.bottomSheetContainer.search.searchBox.backgroundTintList = ColorStateList.valueOf(secondaryBackground!!) 262 | val root = binding.bottomSheetContainer.root.background as GradientDrawable 263 | root.setColor(ColorUtils.setAlphaComponent(headerBackground,235)) 264 | binding.getlocationContainer.backgroundTintList = ColorStateList.valueOf(background) 265 | binding.favourite.backgroundTintList = ColorStateList.valueOf(background) 266 | 267 | } 268 | 269 | 270 | 271 | private fun setUpNavigationView() { 272 | binding.navView.setNavigationItemSelectedListener { 273 | when(it.itemId){ 274 | 275 | R.id.get_favourite -> { 276 | openFavouriteListDialog() 277 | } 278 | R.id.settings -> { 279 | startActivity(Intent(this,SettingsActivity::class.java)) 280 | } 281 | R.id.about -> { 282 | aboutDialog() 283 | } 284 | } 285 | binding.container.closeDrawer(GravityCompat.START) 286 | true 287 | } 288 | 289 | } 290 | 291 | 292 | private fun initializeMap() { 293 | val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? 294 | mapFragment?.getMapAsync(this) 295 | } 296 | 297 | private fun isModuleEnable(){ 298 | viewModel.isXposed.observe(this) { isXposed -> 299 | xposedDialog?.dismiss() 300 | xposedDialog = null 301 | if (!isXposed) { 302 | xposedDialog = MaterialAlertDialogBuilder(this).run { 303 | setTitle(R.string.error_xposed_module_missing) 304 | setMessage(R.string.error_xposed_module_missing_desc) 305 | setCancelable(BuildConfig.DEBUG) 306 | show() 307 | } 308 | } 309 | 310 | } 311 | 312 | } 313 | 314 | 315 | 316 | override fun onMapReady(googleMap: GoogleMap) { 317 | mMap = googleMap 318 | with(mMap){ 319 | mapType = viewModel.mapType 320 | val zoom = 12.0f 321 | lat = viewModel.getLat 322 | lon = viewModel.getLng 323 | mLatLng = LatLng(lat, lon) 324 | mLatLng.let { 325 | mMarker = addMarker( 326 | MarkerOptions().position(it!!).draggable(false) 327 | .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)).visible(false) 328 | ) 329 | mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(it, zoom)) 330 | } 331 | setPadding(0,80,0,170) 332 | setOnMapClickListener(this@MapActivity) 333 | if (viewModel.isStarted){ 334 | mMarker?.let { 335 | it.isVisible = true 336 | it.showInfoWindow() 337 | } 338 | } 339 | } 340 | } 341 | 342 | override fun onMapClick(latLng: LatLng) { 343 | mLatLng = latLng 344 | mMarker?.let { marker -> 345 | mLatLng.let { 346 | marker.position = it!! 347 | marker.isVisible = true 348 | mMap.animateCamera(CameraUpdateFactory.newLatLng(it)) 349 | lat = it.latitude 350 | lon = it.longitude 351 | } 352 | } 353 | } 354 | 355 | 356 | private fun moveMapToNewLocation(moveNewLocation: Boolean) { 357 | if (moveNewLocation) { 358 | mLatLng = LatLng(lat, lon) 359 | mLatLng.let { latLng -> 360 | mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng!!, 12.0f)) 361 | mMarker?.apply { 362 | position = latLng 363 | isVisible = true 364 | showInfoWindow() 365 | } 366 | } 367 | } 368 | 369 | } 370 | 371 | 372 | override fun onResume() { 373 | super.onResume() 374 | viewModel.updateXposedState() 375 | } 376 | 377 | 378 | 379 | private fun aboutDialog(){ 380 | alertDialog = MaterialAlertDialogBuilder(this) 381 | layoutInflater.inflate(R.layout.about,null).apply { 382 | val tittle = findViewById(R.id.design_about_title) 383 | val version = findViewById(R.id.design_about_version) 384 | val info = findViewById(R.id.design_about_info) 385 | tittle.text = getString(R.string.app_name) 386 | version.text = BuildConfig.VERSION_NAME 387 | info.text = getString(R.string.about_info) 388 | }.run { 389 | alertDialog.setView(this) 390 | alertDialog.show() 391 | } 392 | } 393 | 394 | 395 | 396 | private fun addFavouriteDialog(){ 397 | alertDialog = MaterialAlertDialogBuilder(this).apply { 398 | val view = layoutInflater.inflate(R.layout.dialog_layout,null) 399 | val editText = view.findViewById(R.id.search_edittxt) 400 | setTitle(getString(R.string.add_fav_dialog_title)) 401 | setPositiveButton(getString(R.string.dialog_button_add)) { _, _ -> 402 | val s = editText.text.toString() 403 | if (!mMarker?.isVisible!!){ 404 | showToast(getString(R.string.location_not_select)) 405 | }else{ 406 | viewModel.storeFavorite(s, lat, lon) 407 | viewModel.response.observe(this@MapActivity){ 408 | if (it == (-1).toLong()) showToast(getString(R.string.cant_save)) else showToast(getString(R.string.save)) 409 | } 410 | } 411 | } 412 | setView(view) 413 | show() 414 | } 415 | 416 | } 417 | 418 | 419 | private fun openFavouriteListDialog() { 420 | getAllUpdatedFavList() 421 | alertDialog = MaterialAlertDialogBuilder(this) 422 | alertDialog.setTitle(getString(R.string.favourites)) 423 | val view = layoutInflater.inflate(R.layout.fav,null) 424 | val rcv = view.findViewById(R.id.favorites_list) 425 | rcv.layoutManager = LinearLayoutManager(this) 426 | rcv.adapter = favListAdapter 427 | favListAdapter.onItemClick = { 428 | it.let { 429 | lat = it.lat!! 430 | lon = it.lng!! 431 | } 432 | moveMapToNewLocation(true) 433 | if (dialog.isShowing) dialog.dismiss() 434 | 435 | } 436 | favListAdapter.onItemDelete = { 437 | viewModel.deleteFavourite(it) 438 | } 439 | alertDialog.setView(view) 440 | dialog = alertDialog.create() 441 | dialog.show() 442 | 443 | } 444 | 445 | 446 | private fun getAllUpdatedFavList(){ 447 | lifecycleScope.launch { 448 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){ 449 | viewModel.doGetUserDetails() 450 | viewModel.allFavList.collect { 451 | favListAdapter.submitList(it) 452 | } 453 | } 454 | } 455 | 456 | } 457 | 458 | 459 | private fun updateDialog(){ 460 | alertDialog = MaterialAlertDialogBuilder(this) 461 | alertDialog.setTitle(R.string.update_available) 462 | alertDialog.setMessage(update?.changelog) 463 | alertDialog.setPositiveButton(getString(R.string.update_button)) { _, _ -> 464 | MaterialAlertDialogBuilder(this).apply { 465 | val view = layoutInflater.inflate(R.layout.update_dialog, null) 466 | val progress = view.findViewById(R.id.update_download_progress) 467 | val cancel = view.findViewById(R.id.update_download_cancel) 468 | setView(view) 469 | cancel.setOnClickListener { 470 | viewModel.cancelDownload(this@MapActivity) 471 | dialog.dismiss() 472 | } 473 | lifecycleScope.launch { 474 | viewModel.downloadState.collect { 475 | when (it) { 476 | is MainViewModel.State.Downloading -> { 477 | if (it.progress > 0) { 478 | progress.isIndeterminate = false 479 | progress.progress = it.progress 480 | } 481 | } 482 | is MainViewModel.State.Done -> { 483 | viewModel.openPackageInstaller(this@MapActivity, it.fileUri) 484 | viewModel.clearUpdate() 485 | dialog.dismiss() 486 | } 487 | 488 | is MainViewModel.State.Failed -> { 489 | Toast.makeText( 490 | this@MapActivity, 491 | R.string.bs_update_download_failed, 492 | Toast.LENGTH_LONG 493 | ).show() 494 | dialog.dismiss() 495 | 496 | } 497 | else -> {} 498 | } 499 | } 500 | } 501 | update?.let { it -> 502 | viewModel.startDownload(this@MapActivity, it) 503 | } ?: run { 504 | dialog.dismiss() 505 | } 506 | }.run { 507 | dialog = create() 508 | dialog.show() 509 | } 510 | } 511 | dialog = alertDialog.create() 512 | dialog.show() 513 | 514 | } 515 | 516 | private fun updateChecker(){ 517 | lifecycleScope.launchWhenResumed { 518 | viewModel.update.collect{ 519 | if (it!= null){ 520 | updateDialog() 521 | } 522 | } 523 | } 524 | } 525 | 526 | 527 | private suspend fun getSearchAddress(address: String) = callbackFlow { 528 | withContext(Dispatchers.IO){ 529 | trySend(SearchProgress.Progress) 530 | val matcher: Matcher = 531 | Pattern.compile("[-+]?\\d{1,3}([.]\\d+)?, *[-+]?\\d{1,3}([.]\\d+)?").matcher(address) 532 | 533 | if (matcher.matches()){ 534 | delay(3000) 535 | trySend(SearchProgress.Complete(matcher.group().split(",")[0].toDouble(),matcher.group().split(",")[1].toDouble())) 536 | }else { 537 | val geocoder = Geocoder(this@MapActivity) 538 | val addressList: List
? = geocoder.getFromLocationName(address,3) 539 | 540 | try { 541 | addressList?.let { 542 | if (it.size == 1){ 543 | trySend(SearchProgress.Complete(addressList[0].latitude, addressList[0].longitude)) 544 | }else { 545 | trySend(SearchProgress.Fail(getString(R.string.address_not_found))) 546 | } 547 | } 548 | } catch (io : IOException){ 549 | trySend(SearchProgress.Fail(getString(R.string.no_internet))) 550 | } 551 | } 552 | } 553 | 554 | awaitClose { this.cancel() } 555 | } 556 | 557 | 558 | 559 | 560 | private fun showStartNotification(address: String){ 561 | notificationsChannel.showNotification(this){ 562 | it.setSmallIcon(R.drawable.ic_stop) 563 | it.setContentTitle(getString(R.string.location_set)) 564 | it.setContentText(address) 565 | it.setAutoCancel(true) 566 | it.setCategory(Notification.CATEGORY_EVENT) 567 | it.priority = NotificationCompat.PRIORITY_HIGH 568 | } 569 | 570 | } 571 | 572 | 573 | private fun cancelNotification(){ 574 | notificationsChannel.cancelAllNotifications(this) 575 | } 576 | 577 | 578 | // Get current location 579 | @SuppressLint("MissingPermission") 580 | private fun getLastLocation() { 581 | fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) 582 | if (checkPermissions()) { 583 | if (isLocationEnabled()) { 584 | fusedLocationClient.lastLocation.addOnCompleteListener(this) { task -> 585 | val location: Location? = task.result 586 | if (location == null) { 587 | requestNewLocationData() 588 | } else { 589 | lat = location.latitude 590 | lon = location.longitude 591 | moveMapToNewLocation(true) 592 | } 593 | } 594 | } else { 595 | showToast("Turn on location") 596 | val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) 597 | startActivity(intent) 598 | } 599 | } else { 600 | requestPermissions() 601 | } 602 | } 603 | 604 | 605 | @SuppressLint("MissingPermission") 606 | private fun requestNewLocationData() { 607 | val mLocationRequest = LocationRequest() 608 | mLocationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY 609 | mLocationRequest.interval = 0 610 | mLocationRequest.fastestInterval = 0 611 | mLocationRequest.numUpdates = 1 612 | 613 | fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) 614 | fusedLocationClient.requestLocationUpdates( 615 | mLocationRequest, mLocationCallback, 616 | Looper.myLooper() 617 | ) 618 | } 619 | 620 | private val mLocationCallback = object : LocationCallback() { 621 | override fun onLocationResult(locationResult: LocationResult) { 622 | val mLastLocation: Location = locationResult.lastLocation!! 623 | lat = mLastLocation.latitude 624 | lon = mLastLocation.longitude 625 | } 626 | } 627 | 628 | private fun isLocationEnabled(): Boolean { 629 | val locationManager: LocationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager 630 | return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled( 631 | LocationManager.NETWORK_PROVIDER 632 | ) 633 | } 634 | 635 | private fun checkPermissions(): Boolean { 636 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && 637 | ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED 638 | ) { 639 | return true 640 | } 641 | return false 642 | } 643 | 644 | private fun requestPermissions() { 645 | ActivityCompat.requestPermissions( 646 | this, 647 | arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION), 648 | PERMISSION_ID 649 | ) 650 | } 651 | 652 | override fun onRequestPermissionsResult( 653 | requestCode: Int, 654 | permissions: Array, 655 | grantResults: IntArray 656 | ) { 657 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 658 | 659 | if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { 660 | getLastLocation() 661 | } 662 | } 663 | 664 | 665 | 666 | 667 | 668 | 669 | } 670 | 671 | 672 | sealed class SearchProgress { 673 | object Progress : SearchProgress() 674 | data class Complete(val lat: Double , val lon : Double) : SearchProgress() 675 | data class Fail(val error: String?) : SearchProgress() 676 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/ui/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.ui 2 | 3 | 4 | import android.app.ActivityManager 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.net.Uri 8 | import android.os.Bundle 9 | import android.provider.Settings 10 | import android.text.Editable 11 | import android.text.InputType 12 | import android.text.TextWatcher 13 | import android.text.method.DigitsKeyListener 14 | import android.view.MenuItem 15 | import android.widget.EditText 16 | import android.widget.Toast 17 | import androidx.activity.OnBackPressedCallback 18 | import androidx.appcompat.app.AppCompatDelegate 19 | import androidx.core.content.ContextCompat.getSystemService 20 | import androidx.preference.EditTextPreference 21 | import androidx.preference.Preference 22 | import androidx.preference.PreferenceDataStore 23 | import com.android1500.gpssetter.R 24 | import com.android1500.gpssetter.databinding.SettingsActivityBinding 25 | import com.android1500.gpssetter.utils.JoystickService 26 | import com.android1500.gpssetter.utils.PrefManager 27 | import com.android1500.gpssetter.utils.ext.showToast 28 | import com.highcapable.yukihookapi.hook.xposed.prefs.ui.ModulePreferenceFragment 29 | import com.kieronquinn.monetcompat.app.MonetCompatActivity 30 | import rikka.preference.SimpleMenuPreference 31 | 32 | 33 | class SettingsActivity : MonetCompatActivity() { 34 | 35 | 36 | 37 | private val binding by lazy { 38 | SettingsActivityBinding.inflate(layoutInflater) 39 | } 40 | 41 | class SettingPreferenceDataStore() : PreferenceDataStore() { 42 | override fun getBoolean(key: String?, defValue: Boolean): Boolean { 43 | return when (key) { 44 | "isHookedSystem" -> PrefManager.isHookSystem 45 | "random_position" -> PrefManager.isRandomPosition 46 | "disable_update" -> PrefManager.disableUpdate 47 | "isJoyStickEnable" -> PrefManager.isJoyStickEnable 48 | else -> throw IllegalArgumentException("Invalid key $key") 49 | } 50 | } 51 | 52 | override fun putBoolean(key: String?, value: Boolean) { 53 | return when (key) { 54 | "isHookedSystem" -> PrefManager.isHookSystem = value 55 | "random_position" -> PrefManager.isRandomPosition = value 56 | "disable_update" -> PrefManager.disableUpdate = value 57 | "isJoyStickEnable" -> PrefManager.isJoyStickEnable = value 58 | else -> throw IllegalArgumentException("Invalid key $key") 59 | } 60 | } 61 | 62 | override fun getString(key: String?, defValue: String?): String? { 63 | return when (key) { 64 | "accuracy_settings" -> PrefManager.accuracy 65 | "map_type" -> PrefManager.mapType.toString() 66 | "darkTheme" -> PrefManager.darkTheme.toString() 67 | else -> throw IllegalArgumentException("Invalid key $key") 68 | } 69 | } 70 | 71 | override fun putString(key: String?, value: String?) { 72 | return when (key) { 73 | "accuracy_settings" -> PrefManager.accuracy = value 74 | "map_type" -> PrefManager.mapType = value!!.toInt() 75 | "darkTheme" -> PrefManager.darkTheme = value!!.toInt() 76 | else -> throw IllegalArgumentException("Invalid key $key") 77 | } 78 | } 79 | } 80 | 81 | 82 | 83 | 84 | 85 | 86 | override fun onCreate(savedInstanceState: Bundle?) { 87 | super.onCreate(savedInstanceState) 88 | setContentView(binding.root) 89 | theme.applyStyle(rikka.material.preference.R.style.ThemeOverlay_Rikka_Material3_Preference, true); 90 | setSupportActionBar(binding.toolbar) 91 | if (savedInstanceState == null) { 92 | supportFragmentManager 93 | .beginTransaction() 94 | .replace(R.id.settings_container, SettingsPreferenceFragment()) 95 | .commit() 96 | } 97 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 98 | 99 | 100 | onBackPressedDispatcher.addCallback( 101 | this, 102 | object : OnBackPressedCallback(true) { 103 | override fun handleOnBackPressed() { 104 | finish() 105 | } 106 | } 107 | ) 108 | 109 | } 110 | 111 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 112 | if (item.itemId == android.R.id.home) { 113 | onBackPressedDispatcher.onBackPressed() 114 | } 115 | return super.onOptionsItemSelected(item) 116 | } 117 | 118 | 119 | class SettingsPreferenceFragment : ModulePreferenceFragment() { 120 | 121 | 122 | override fun onCreatePreferencesInModuleApp(savedInstanceState: Bundle?, rootKey: String?) { 123 | preferenceManager?.preferenceDataStore = SettingPreferenceDataStore() 124 | setPreferencesFromResource(R.xml.setting, rootKey) 125 | 126 | 127 | 128 | findPreference("accuracy_settings")?.let { 129 | it.summary = "${PrefManager.accuracy} m." 130 | it.setOnBindEditTextListener { editText -> 131 | editText.inputType = InputType.TYPE_CLASS_NUMBER; 132 | editText.keyListener = DigitsKeyListener.getInstance("0123456789.,"); 133 | editText.addTextChangedListener(getCommaReplacerTextWatcher(editText)); 134 | } 135 | 136 | it.setOnPreferenceChangeListener { preference, newValue -> 137 | try { 138 | newValue as String? 139 | preference.summary = "$newValue m." 140 | } catch (n: NumberFormatException) { 141 | n.printStackTrace() 142 | Toast.makeText( 143 | requireContext(), 144 | getString(R.string.enter_valid_input), 145 | Toast.LENGTH_SHORT 146 | ).show() 147 | } 148 | true 149 | } 150 | } 151 | 152 | findPreference("darkTheme")?.setOnPreferenceChangeListener { _, newValue -> 153 | val newMode = (newValue as String).toInt() 154 | if (PrefManager.darkTheme != newMode) { 155 | AppCompatDelegate.setDefaultNightMode(newMode) 156 | activity?.recreate() 157 | } 158 | true 159 | } 160 | 161 | findPreference("isJoyStickEnable")?.let { 162 | it.setOnPreferenceClickListener { 163 | if (askOverlayPermission()){ 164 | if (isJoystickRunning()){ 165 | requireContext().stopService(Intent(context,JoystickService::class.java)) 166 | it.summary = "Joystick running" 167 | }else if (PrefManager.isStarted){ 168 | requireContext().startService(Intent(context,JoystickService::class.java)) 169 | it.summary = "Joystick not running" 170 | }else { 171 | requireContext().showToast(requireContext().getString(R.string.location_not_select)) 172 | } 173 | } 174 | true 175 | } 176 | 177 | } 178 | 179 | 180 | 181 | } 182 | 183 | private fun isJoystickRunning(): Boolean { 184 | var isRunning = false 185 | val manager = requireContext().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager? ?: return false 186 | for (service in manager.getRunningServices(Int.MAX_VALUE)) { 187 | if ("com.android1500.gpssetter.utils.JoystickService" == service.service.className) { 188 | isRunning = true 189 | } 190 | } 191 | return isRunning 192 | } 193 | 194 | 195 | private fun askOverlayPermission() : Boolean { 196 | if (Settings.canDrawOverlays(context)){ 197 | return true 198 | } 199 | val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:${context?.applicationContext?.packageName}" )) 200 | requireContext().startActivity(intent) 201 | return false 202 | } 203 | 204 | 205 | private fun getCommaReplacerTextWatcher(editText: EditText): TextWatcher { 206 | return object : TextWatcher { 207 | override fun beforeTextChanged( 208 | charSequence: CharSequence, 209 | i: Int, 210 | i1: Int, 211 | i2: Int 212 | ) { 213 | } 214 | 215 | override fun onTextChanged( 216 | charSequence: CharSequence, 217 | i: Int, 218 | i1: Int, 219 | i2: Int 220 | ) { 221 | } 222 | 223 | override fun afterTextChanged(editable: Editable) { 224 | val text = editable.toString() 225 | if (text.contains(",")) { 226 | editText.setText(text.replace(",", ".")) 227 | editText.setSelection(editText.text.length) 228 | } 229 | } 230 | } 231 | } 232 | 233 | } 234 | 235 | 236 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/ui/viewmodel/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.ui.viewmodel 2 | 3 | 4 | import android.app.DownloadManager 5 | import android.content.BroadcastReceiver 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.content.IntentFilter 9 | import android.database.ContentObserver 10 | import android.database.Cursor 11 | import android.net.Uri 12 | import android.os.Handler 13 | import android.os.Looper 14 | import androidx.core.content.FileProvider 15 | import androidx.lifecycle.LiveData 16 | import androidx.lifecycle.MutableLiveData 17 | import androidx.lifecycle.ViewModel 18 | import androidx.lifecycle.viewModelScope 19 | import com.android1500.gpssetter.BuildConfig 20 | import com.android1500.gpssetter.R 21 | import com.android1500.gpssetter.utils.ext.onIO 22 | import com.android1500.gpssetter.utils.ext.onMain 23 | import com.android1500.gpssetter.repository.FavouriteRepository 24 | import com.android1500.gpssetter.utils.PrefManager 25 | import com.android1500.gpssetter.room.Favourite 26 | import com.android1500.gpssetter.update.UpdateChecker 27 | import com.android1500.gpssetter.utils.ext.showToast 28 | import com.highcapable.yukihookapi.YukiHookAPI 29 | import dagger.hilt.android.lifecycle.HiltViewModel 30 | import dagger.hilt.android.qualifiers.ApplicationContext 31 | import kotlinx.coroutines.Dispatchers 32 | import kotlinx.coroutines.flow.* 33 | import kotlinx.coroutines.launch 34 | import kotlinx.coroutines.withContext 35 | import timber.log.Timber 36 | import java.io.File 37 | import javax.inject.Inject 38 | import kotlin.math.roundToInt 39 | 40 | 41 | @HiltViewModel 42 | class MainViewModel @Inject constructor( 43 | private val favouriteRepository: FavouriteRepository, 44 | private val prefManger: PrefManager, 45 | private val updateChecker: UpdateChecker, 46 | private val downloadManager: DownloadManager, 47 | @ApplicationContext context: Context 48 | ) : ViewModel() { 49 | 50 | 51 | val getLat = prefManger.getLat 52 | val getLng = prefManger.getLng 53 | val isStarted = prefManger.isStarted 54 | val mapType = prefManger.mapType 55 | 56 | 57 | private val _allFavList = MutableStateFlow>(emptyList()) 58 | val allFavList : StateFlow> = _allFavList 59 | fun doGetUserDetails(){ 60 | onIO { 61 | favouriteRepository.getAllFavourites 62 | .catch { e -> 63 | Timber.tag("Error in getting all save favourite").d(e.message.toString()) 64 | } 65 | .collectLatest { 66 | _allFavList.emit(it) 67 | } 68 | } 69 | } 70 | 71 | 72 | fun update(start: Boolean, la: Double, ln: Double) { 73 | prefManger.update(start,la,ln) 74 | } 75 | 76 | private val _response = MutableLiveData() 77 | val response: LiveData = _response 78 | 79 | 80 | private fun insertNewFavourite(favourite: Favourite) = onIO { 81 | _response.postValue(favouriteRepository.addNewFavourite(favourite)) 82 | 83 | } 84 | 85 | 86 | val isXposed = MutableLiveData() 87 | fun updateXposedState() { 88 | onMain { 89 | isXposed.value = YukiHookAPI.Status.isModuleActive 90 | } 91 | } 92 | 93 | 94 | fun deleteFavourite(favourite: Favourite) = onIO { 95 | favouriteRepository.deleteFavourite(favourite) 96 | } 97 | 98 | private fun getFavouriteSingle(i : Int) : Favourite { 99 | return favouriteRepository.getSingleFavourite(i.toLong()) 100 | } 101 | 102 | 103 | private val _update = MutableStateFlow(null).apply { 104 | viewModelScope.launch { 105 | withContext(Dispatchers.IO){ 106 | updateChecker.clearCachedDownloads(context) 107 | } 108 | updateChecker.getLatestRelease().collect { 109 | emit(it) 110 | } 111 | } 112 | } 113 | 114 | val update = _update.asStateFlow() 115 | 116 | fun getAvailableUpdate(): UpdateChecker.Update? { 117 | return _update.value 118 | } 119 | 120 | fun clearUpdate() { 121 | viewModelScope.launch { 122 | _update.emit(null) 123 | } 124 | } 125 | 126 | 127 | private var requestId: Long? = null 128 | private var _downloadState = MutableStateFlow(State.Idle) 129 | private var downloadFile: File? = null 130 | val downloadState = _downloadState.asStateFlow() 131 | 132 | 133 | // Got idea from https://github.com/KieronQuinn/DarQ for Check Update 134 | 135 | fun startDownload(context: Context, update: UpdateChecker.Update) { 136 | if(_downloadState.value is State.Idle) { 137 | downloadUpdate(context, update.assetUrl, update.assetName) 138 | } 139 | } 140 | 141 | private val downloadStateReceiver = object: BroadcastReceiver() { 142 | override fun onReceive(context: Context, intent: Intent?) { 143 | viewModelScope.launch { 144 | var success = false 145 | val query = DownloadManager.Query().apply { 146 | setFilterById(requestId ?: return@apply) 147 | } 148 | val cursor = downloadManager.query(query) 149 | if (cursor.moveToFirst()) { 150 | val columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS) 151 | if (cursor.getInt(columnIndex) == DownloadManager.STATUS_SUCCESSFUL) { 152 | success = true 153 | } 154 | } 155 | if (success && downloadFile != null) { 156 | val outputUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", downloadFile!!) 157 | _downloadState.emit(State.Done(outputUri)) 158 | } else { 159 | _downloadState.emit(State.Failed) 160 | } 161 | } 162 | } 163 | } 164 | 165 | private val downloadObserver = object: ContentObserver(Handler(Looper.getMainLooper())) { 166 | override fun onChange(selfChange: Boolean, uri: Uri?) { 167 | super.onChange(selfChange, uri) 168 | viewModelScope.launch { 169 | val query = DownloadManager.Query() 170 | query.setFilterById(requestId ?: return@launch) 171 | val c: Cursor = downloadManager.query(query) 172 | var progress = 0.0 173 | if (c.moveToFirst()) { 174 | val sizeIndex: Int = 175 | c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES) 176 | val downloadedIndex: Int = 177 | c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR) 178 | val size = c.getInt(sizeIndex) 179 | val downloaded = c.getInt(downloadedIndex) 180 | if (size != -1) progress = downloaded * 100.0 / size 181 | } 182 | _downloadState.emit(State.Downloading(progress.roundToInt())) 183 | } 184 | } 185 | } 186 | 187 | private fun downloadUpdate(context: Context, url: String, fileName: String) = viewModelScope.launch { 188 | val downloadFolder = File(context.externalCacheDir, "updates").apply { 189 | mkdirs() 190 | } 191 | downloadFile = File(downloadFolder, fileName) 192 | context.registerReceiver(downloadStateReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) 193 | context.contentResolver.registerContentObserver(Uri.parse("content://downloads/my_downloads"), true, downloadObserver) 194 | requestId = DownloadManager.Request(Uri.parse(url)).apply { 195 | setDescription(context.getString(R.string.download_manager_description)) 196 | setTitle(context.getString(R.string.app_name)) 197 | setDestinationUri(Uri.fromFile(downloadFile!!)) 198 | }.run { 199 | downloadManager.enqueue(this) 200 | } 201 | } 202 | 203 | fun openPackageInstaller(context: Context, uri: Uri){ 204 | runCatching { 205 | Intent(Intent.ACTION_VIEW, uri).apply { 206 | putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) 207 | setDataAndType(uri, "application/vnd.android.package-archive") 208 | flags = Intent.FLAG_ACTIVITY_NEW_TASK 209 | addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 210 | }.also { 211 | context.startActivity(it) 212 | } 213 | }.onFailure { 214 | it.printStackTrace() 215 | context.showToast(context.getString(R.string.app_update_failed)) 216 | } 217 | 218 | } 219 | 220 | fun cancelDownload(context: Context) { 221 | viewModelScope.launch { 222 | requestId?.let { 223 | downloadManager.remove(it) 224 | } 225 | context.unregisterReceiver(downloadStateReceiver) 226 | context.contentResolver.unregisterContentObserver(downloadObserver) 227 | _downloadState.emit(State.Idle) 228 | } 229 | } 230 | 231 | sealed class State { 232 | object Idle: State() 233 | data class Downloading(val progress: Int): State() 234 | data class Done(val fileUri: Uri): State() 235 | object Failed: State() 236 | } 237 | 238 | 239 | 240 | fun storeFavorite( 241 | address: String, 242 | lat: Double, 243 | lon: Double 244 | ) = onIO { 245 | 246 | val slot: Int 247 | var i = 0 248 | while (true) { 249 | if(getFavouriteSingle(i) == null) { 250 | slot = i 251 | break 252 | } else { 253 | i++ 254 | } 255 | } 256 | insertNewFavourite(Favourite(id = slot.toLong(), address = address, lat = lat, lng = lon)) 257 | } 258 | 259 | 260 | 261 | 262 | 263 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/update/GitHubRelease.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.update 2 | 3 | import com.google.gson.annotations.Expose 4 | import com.google.gson.annotations.SerializedName 5 | 6 | class GitHubRelease { 7 | 8 | 9 | @SerializedName("id") 10 | @Expose 11 | var id: Int? = null 12 | 13 | 14 | @SerializedName("tag_name") 15 | @Expose 16 | var tagName: String? = null 17 | 18 | 19 | @SerializedName("name") 20 | @Expose 21 | var name: String? = null 22 | 23 | 24 | @SerializedName("published_at") 25 | @Expose 26 | var publishedAt: String? = null 27 | 28 | @SerializedName("assets") 29 | @Expose 30 | var assets: List? = null 31 | 32 | @SerializedName("body") 33 | @Expose 34 | var body: String? = null 35 | 36 | class Asset { 37 | 38 | @SerializedName("id") 39 | @Expose 40 | var id: Int? = null 41 | 42 | @SerializedName("name") 43 | @Expose 44 | var name: String? = null 45 | 46 | @SerializedName("browser_download_url") 47 | @Expose 48 | var browserDownloadUrl: String? = null 49 | } 50 | 51 | 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/update/GitHubService.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.update 2 | 3 | import retrofit2.Call 4 | import retrofit2.http.GET 5 | 6 | 7 | interface GitHubService { 8 | 9 | @GET("releases/latest") 10 | fun getReleases(): Call 11 | 12 | } 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/update/UpdateChecker.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.update 2 | 3 | import android.content.Context 4 | import android.os.Parcelable 5 | import com.android1500.gpssetter.BuildConfig 6 | import com.android1500.gpssetter.utils.PrefManager 7 | import kotlinx.android.parcel.Parcelize 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.channels.awaitClose 10 | import kotlinx.coroutines.flow.callbackFlow 11 | import kotlinx.coroutines.withContext 12 | import java.io.File 13 | import javax.inject.Inject 14 | 15 | 16 | class UpdateChecker @Inject constructor(private val apiResponse : GitHubService) { 17 | 18 | 19 | fun getLatestRelease() = callbackFlow { 20 | withContext(Dispatchers.IO){ 21 | getReleaseList()?.let { gitHubReleaseResponse -> 22 | val currentTag = gitHubReleaseResponse.tagName 23 | 24 | if (currentTag != null && (currentTag != BuildConfig.TAG_NAME && PrefManager.disableUpdate)) { 25 | //New update available! 26 | val asset = 27 | gitHubReleaseResponse.assets?.firstOrNull { it.name?.endsWith(".apk") == true } 28 | val releaseUrl = 29 | asset?.browserDownloadUrl?.replace("/download/", "/tag/")?.apply { 30 | substring(0, lastIndexOf("/")) 31 | } 32 | val name = gitHubReleaseResponse.name ?: run { 33 | this@callbackFlow.trySend(null).isSuccess 34 | return@let 35 | } 36 | val body = gitHubReleaseResponse.body ?: run { 37 | this@callbackFlow.trySend(null).isSuccess 38 | return@let 39 | } 40 | val publishedAt = gitHubReleaseResponse.publishedAt ?: run { 41 | this@callbackFlow.trySend(null).isSuccess 42 | return@let 43 | } 44 | this@callbackFlow.trySend( 45 | Update( 46 | name, 47 | body, 48 | publishedAt, 49 | asset?.browserDownloadUrl 50 | ?: "https://github.com/Android1500/GpsSetter/releases", 51 | asset?.name ?: "app-release.apk", 52 | releaseUrl ?: "https://github.com/Android1500/GpsSetter/releases" 53 | ) 54 | ).isSuccess 55 | } 56 | } ?: run { 57 | this@callbackFlow.trySend(null).isSuccess 58 | } 59 | } 60 | awaitClose { } 61 | } 62 | 63 | 64 | private fun getReleaseList(): GitHubRelease? { 65 | 66 | runCatching { 67 | apiResponse.getReleases().execute().body() 68 | }.onSuccess { 69 | return it 70 | }.onFailure { 71 | return null 72 | } 73 | return null 74 | } 75 | 76 | fun clearCachedDownloads(context: Context){ 77 | File(context.externalCacheDir, "updates").deleteRecursively() 78 | } 79 | 80 | 81 | 82 | 83 | @Parcelize 84 | data class Update(val name: String, val changelog: String, val timestamp: String, val assetUrl: String, val assetName: String, val releaseUrl: String): 85 | Parcelable 86 | } 87 | 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/utils/JoystickService.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Service 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.graphics.PixelFormat 8 | import android.os.IBinder 9 | import android.util.Log 10 | import android.view.* 11 | import com.android1500.gpssetter.R 12 | import com.android1500.gpssetter.utils.ext.showToast 13 | import io.github.controlwear.virtual.joystick.android.JoystickView 14 | import kotlin.math.cos 15 | import kotlin.math.sin 16 | 17 | 18 | class JoystickService : Service(),View.OnTouchListener,View.OnClickListener { 19 | 20 | 21 | private var wm: WindowManager? = null 22 | private var mJoystickContainerView: View? = null 23 | private var mJoystickView: JoystickView? = null 24 | private var mJoystickLayoutParams: WindowManager.LayoutParams? = null 25 | private var lat : Double = PrefManager.getLat 26 | private var lon : Double = PrefManager.getLng 27 | 28 | 29 | 30 | @SuppressLint("ClickableViewAccessibility") 31 | override fun onCreate() { 32 | super.onCreate() 33 | wm = getSystemService(WINDOW_SERVICE) as WindowManager 34 | val mInflater :LayoutInflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater 35 | mJoystickContainerView = mInflater.inflate(R.layout.joystick_layout, null as ViewGroup?) as View 36 | mJoystickView = mJoystickContainerView!!.findViewById(R.id.joystickView_right) 37 | mJoystickView?.setOnTouchListener { v, event -> 38 | if (event.action == 1){ 39 | try { 40 | lat = PrefManager.getLat 41 | lon = PrefManager.getLng 42 | updateLocation(lat, lon) 43 | } catch (e: Exception) { 44 | e.printStackTrace() 45 | } 46 | 47 | } 48 | false 49 | } 50 | mJoystickView?.setOnMoveListener { angle, strength -> 51 | val radians = Math.toRadians(angle.toDouble()) 52 | try { 53 | val factorX: Double = cos(radians) / 100000.0 * (strength / 30) 54 | val factorY: Double = sin(radians) / 100000.0 * (strength / 30) 55 | lat = PrefManager.getLat + factorX 56 | lon = PrefManager.getLng + factorY 57 | updateLocation(lat, lon) 58 | 59 | }catch (e : Exception){ 60 | e.printStackTrace() 61 | } 62 | 63 | 64 | } 65 | mJoystickLayoutParams = WindowManager.LayoutParams( 66 | WindowManager.LayoutParams.WRAP_CONTENT, 67 | WindowManager.LayoutParams.WRAP_CONTENT, 68 | WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, 69 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 70 | PixelFormat.TRANSLUCENT 71 | ) 72 | mJoystickLayoutParams?.let { 73 | it.gravity = Gravity.LEFT 74 | } 75 | 76 | wm!!.addView(mJoystickContainerView,mJoystickLayoutParams) 77 | 78 | 79 | 80 | } 81 | 82 | 83 | override fun onBind(intent: Intent?): IBinder? { 84 | return null 85 | } 86 | 87 | override fun onTouch(v: View?, event: MotionEvent?): Boolean { 88 | TODO("Not yet implemented") 89 | 90 | } 91 | 92 | override fun onClick(v: View?) { 93 | TODO("Not yet implemented") 94 | } 95 | 96 | override fun onDestroy() { 97 | super.onDestroy() 98 | if (this.mJoystickContainerView != null) { 99 | this.wm!!.removeView(mJoystickContainerView); 100 | this.mJoystickContainerView = null; 101 | } 102 | 103 | 104 | } 105 | 106 | private fun updateLocation(lat : Double,lon : Double){ 107 | PrefManager.update(start = PrefManager.isStarted, la = lat, ln = lon) 108 | 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/utils/NotificationsChannel.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.utils 2 | 3 | import android.app.Notification 4 | import android.app.NotificationManager 5 | import android.content.Context 6 | import androidx.core.app.NotificationChannelCompat 7 | import androidx.core.app.NotificationCompat 8 | import androidx.core.app.NotificationManagerCompat 9 | import com.android1500.gpssetter.R 10 | class NotificationsChannel{ 11 | 12 | private fun createChannelIfNeeded(context: Context) { 13 | NotificationChannelCompat.Builder("set.location", NotificationManager.IMPORTANCE_DEFAULT).apply { 14 | setName(context.getString(R.string.title)) 15 | setDescription(context.getString(R.string.des)) 16 | }.build().also { 17 | NotificationManagerCompat.from(context).createNotificationChannel(it) 18 | } 19 | } 20 | 21 | private fun createNotification(context: Context, options: (NotificationCompat.Builder) -> Unit): Notification { 22 | createChannelIfNeeded(context) 23 | return NotificationCompat.Builder(context, "set.location").apply { options(this) }.build() 24 | } 25 | 26 | fun showNotification(context: Context, options: (NotificationCompat.Builder) -> Unit): Notification { 27 | val notification = createNotification(context, options) 28 | val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 29 | notificationManager.notify(123, notification) 30 | return notification 31 | } 32 | 33 | fun cancelAllNotifications(context: Context) { 34 | val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 35 | notificationManager.cancelAll() 36 | } 37 | 38 | 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/utils/PrefManager.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.content.SharedPreferences 6 | import com.android1500.gpssetter.BuildConfig 7 | import com.android1500.gpssetter.gsApp 8 | import kotlinx.coroutines.DelicateCoroutinesApi 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.GlobalScope 11 | import kotlinx.coroutines.launch 12 | import rikka.material.app.DayNightDelegate 13 | 14 | 15 | @SuppressLint("WorldReadableFiles") 16 | object PrefManager { 17 | 18 | private const val START = "start" 19 | private const val LATITUDE = "latitude" 20 | private const val LONGITUDE = "longitude" 21 | private const val HOOKED_SYSTEM = "isHookedSystem" 22 | private const val RANDOM_POSITION = "random_position" 23 | private const val ACCURACY_SETTING = "accuracy_settings" 24 | private const val MAP_TYPE = "map_type" 25 | private const val DARK_THEME = "dark_theme" 26 | private const val DISABLE_UPDATE = "disable_update" 27 | 28 | 29 | private val pref: SharedPreferences by lazy { 30 | try { 31 | val prefsFile = "${BuildConfig.APPLICATION_ID}_prefs" 32 | gsApp.getSharedPreferences( 33 | prefsFile, 34 | Context.MODE_WORLD_READABLE 35 | ) 36 | }catch (e:SecurityException){ 37 | val prefsFile = "${BuildConfig.APPLICATION_ID}_prefs" 38 | gsApp.getSharedPreferences( 39 | prefsFile, 40 | Context.MODE_PRIVATE 41 | ) 42 | } 43 | 44 | } 45 | 46 | 47 | val isStarted : Boolean 48 | get() = pref.getBoolean(START, false) 49 | 50 | val getLat : Double 51 | get() = pref.getFloat(LATITUDE, 40.7128F).toDouble() 52 | 53 | val getLng : Double 54 | get() = pref.getFloat(LONGITUDE, -74.0060F).toDouble() 55 | 56 | var isHookSystem : Boolean 57 | get() = pref.getBoolean(HOOKED_SYSTEM, false) 58 | set(value) { pref.edit().putBoolean(HOOKED_SYSTEM,value).apply() } 59 | 60 | var isRandomPosition :Boolean 61 | get() = pref.getBoolean(RANDOM_POSITION, false) 62 | set(value) { pref.edit().putBoolean(RANDOM_POSITION, value).apply() } 63 | 64 | var accuracy : String? 65 | get() = pref.getString(ACCURACY_SETTING,"10") 66 | set(value) { pref.edit().putString(ACCURACY_SETTING,value).apply()} 67 | 68 | var mapType : Int 69 | get() = pref.getInt(MAP_TYPE,1) 70 | set(value) { pref.edit().putInt(MAP_TYPE,value).apply()} 71 | 72 | var darkTheme: Int 73 | get() = pref.getInt(DARK_THEME, DayNightDelegate.MODE_NIGHT_FOLLOW_SYSTEM) 74 | set(value) = pref.edit().putInt(DARK_THEME, value).apply() 75 | 76 | var disableUpdate: Boolean 77 | get() = pref.getBoolean(DISABLE_UPDATE, false) 78 | set(value) = pref.edit().putBoolean(DISABLE_UPDATE, value).apply() 79 | 80 | var isJoyStickEnable: Boolean 81 | get() = pref.getBoolean("isJoyStickEnable",false) 82 | set(value) = pref.edit().putBoolean("isJoyStickEnable",value).apply() 83 | 84 | 85 | 86 | fun update(start:Boolean, la: Double, ln: Double) { 87 | runInBackground { 88 | val prefEditor = pref.edit() 89 | prefEditor.putFloat(LATITUDE, la.toFloat()) 90 | prefEditor.putFloat(LONGITUDE, ln.toFloat()) 91 | prefEditor.putBoolean(START, start) 92 | prefEditor.apply() 93 | } 94 | 95 | } 96 | 97 | 98 | 99 | 100 | @OptIn(DelicateCoroutinesApi::class) 101 | private fun runInBackground(method: suspend () -> Unit){ 102 | GlobalScope.launch(Dispatchers.IO) { 103 | method.invoke() 104 | } 105 | } 106 | 107 | 108 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/utils/ext/Ext+Context.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.utils.ext 2 | 3 | 4 | import android.content.Context 5 | import android.net.ConnectivityManager 6 | import android.net.NetworkCapabilities 7 | import android.widget.Toast 8 | 9 | 10 | 11 | 12 | fun Context.showToast(msg : String){ 13 | Toast.makeText(this,msg, Toast.LENGTH_LONG).show() 14 | } 15 | 16 | fun Context.isNetworkConnected(): Boolean { 17 | val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 18 | val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) 19 | val capabilities = arrayOf( 20 | NetworkCapabilities.TRANSPORT_BLUETOOTH, 21 | NetworkCapabilities.TRANSPORT_CELLULAR, 22 | NetworkCapabilities.TRANSPORT_ETHERNET, 23 | NetworkCapabilities.TRANSPORT_LOWPAN, 24 | NetworkCapabilities.TRANSPORT_VPN, 25 | NetworkCapabilities.TRANSPORT_WIFI, 26 | NetworkCapabilities.TRANSPORT_WIFI_AWARE 27 | ) 28 | return capabilities.any { networkCapabilities?.hasTransport(it) ?: false } 29 | } 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/utils/ext/Ext+LatLng.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.utils.ext 2 | 3 | 4 | import android.content.Context 5 | import android.location.Geocoder 6 | import androidx.lifecycle.lifecycleScope 7 | import com.google.android.gms.maps.model.LatLng 8 | import kotlinx.coroutines.* 9 | import kotlinx.coroutines.channels.awaitClose 10 | import kotlinx.coroutines.flow.callbackFlow 11 | import java.util.* 12 | 13 | suspend fun LatLng.getAddress(context: Context) = callbackFlow { 14 | withContext(Dispatchers.IO){ 15 | val addresses = 16 | Geocoder(context, Locale.getDefault()).getFromLocation(latitude, longitude, 1) 17 | val sb = StringBuilder() 18 | if (addresses!!.size > 0) { 19 | val address = addresses[0].getAddressLine(0) 20 | val strs = address.split(",".toRegex()).toTypedArray() 21 | if (strs.size > 1) { 22 | sb.append(strs[0]) 23 | val index = address.indexOf(",") + 2 24 | if (index > 1 && address.length > index) { 25 | sb.append("\n").append(address.substring(index)) 26 | } 27 | } else { 28 | sb.append(address) 29 | } 30 | } 31 | trySend(sb.toString()) 32 | } 33 | awaitClose { this.cancel() } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/utils/ext/Ext+ViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.utils.ext 2 | 3 | import android.content.Context 4 | import android.location.Geocoder 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.google.android.gms.maps.model.LatLng 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.Job 10 | import kotlinx.coroutines.launch 11 | import java.util.* 12 | 13 | fun ViewModel.onIO(body: suspend () -> Unit): Job { 14 | return viewModelScope.launch(Dispatchers.IO) { 15 | body() 16 | } 17 | } 18 | 19 | fun ViewModel.onDefault(body: suspend () -> Unit): Job { 20 | return viewModelScope.launch(Dispatchers.Default) { 21 | body() 22 | } 23 | } 24 | 25 | fun ViewModel.onMain(body: suspend () -> Unit): Job { 26 | return viewModelScope.launch(Dispatchers.Main) { 27 | body() 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/xposed/HookEntry.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.xposed 2 | 3 | import com.android1500.gpssetter.BuildConfig 4 | import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed 5 | import com.highcapable.yukihookapi.hook.factory.configs 6 | import com.highcapable.yukihookapi.hook.factory.encase 7 | import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit 8 | import de.robv.android.xposed.XposedBridge 9 | 10 | @InjectYukiHookWithXposed(modulePackageName = BuildConfig.APPLICATION_ID) 11 | class HookEntry : IYukiHookXposedInit { 12 | 13 | 14 | 15 | override fun onInit() = configs { 16 | isEnableHookSharedPreferences = true 17 | isEnableModulePrefsCache = true 18 | 19 | 20 | } 21 | 22 | 23 | override fun onHook() = encase { 24 | loadHooker(LocationHook) 25 | } 26 | 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/xposed/LocationHook.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.xposed 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.AndroidAppHelper 5 | import android.app.PendingIntent 6 | import android.content.Context 7 | import android.location.Location 8 | import android.location.LocationListener 9 | import android.location.LocationManager 10 | import android.location.LocationRequest 11 | import com.android1500.gpssetter.BuildConfig 12 | import com.android1500.gpssetter.gsApp 13 | import com.android1500.gpssetter.xposed.LocationHook.hook 14 | import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker 15 | import com.highcapable.yukihookapi.hook.log.loggerW 16 | import com.highcapable.yukihookapi.hook.type.java.BooleanType 17 | import com.highcapable.yukihookapi.hook.type.java.DoubleType 18 | import com.highcapable.yukihookapi.hook.type.java.FloatType 19 | import de.robv.android.xposed.XposedBridge 20 | import de.robv.android.xposed.XposedHelpers 21 | import kotlinx.coroutines.Dispatchers 22 | import kotlinx.coroutines.GlobalScope 23 | import kotlinx.coroutines.launch 24 | import org.lsposed.hiddenapibypass.HiddenApiBypass 25 | import timber.log.Timber 26 | import java.util.* 27 | import kotlin.math.cos 28 | 29 | object LocationHook : YukiBaseHooker() { 30 | 31 | 32 | var newlat: Double = 40.7128 33 | var newlng: Double = 74.0060 34 | private const val pi = 3.14159265359 35 | private var accuracy : Float = 0.0f 36 | private val rand: Random = Random() 37 | private const val earth = 6378137.0 38 | private val settings = Xshare() 39 | var mLastUpdated: Long = 0 40 | private const val className = "android.location.Location" 41 | private val ignorePkg = arrayListOf("com.android.location.fused",BuildConfig.APPLICATION_ID) 42 | 43 | private val context by lazy { AndroidAppHelper.currentApplication() as Context } 44 | 45 | 46 | 47 | private fun updateLocation() { 48 | try { 49 | mLastUpdated = System.currentTimeMillis() 50 | val x = (rand.nextInt(50) - 15).toDouble() 51 | val y = (rand.nextInt(50) - 15).toDouble() 52 | val dlat = x / earth 53 | val dlng = y / (earth * cos(pi * settings.getLat / 180.0)) 54 | newlat = if (settings.isRandomPosition) settings.getLat + (dlat * 180.0 / pi) else settings.getLat 55 | newlng = if (settings.isRandomPosition) settings.getLng + (dlng * 180.0 / pi) else settings.getLng 56 | accuracy = settings.accuracy!!.toFloat() 57 | 58 | }catch (e: Exception) { 59 | Timber.tag("GPS Setter").e(e, "Failed to get XposedSettings for %s", context.packageName) 60 | } 61 | 62 | } 63 | 64 | 65 | @SuppressLint("NewApi") 66 | override fun onHook() { 67 | 68 | loadSystem { 69 | if (settings.isStarted && (settings.isHookedSystem && !ignorePkg.contains(packageName))) { 70 | if (System.currentTimeMillis() - mLastUpdated > 200){ 71 | updateLocation() 72 | } 73 | 74 | findClass( "com.android.server.LocationManagerService").hook { 75 | injectMember { 76 | method { 77 | name = "getLastLocation" 78 | param( 79 | LocationRequest::class.java, 80 | String::class.java 81 | ) 82 | } 83 | beforeHook { 84 | val location = Location(LocationManager.GPS_PROVIDER) 85 | location.time = System.currentTimeMillis() - 300 86 | location.latitude = newlat 87 | location.longitude = newlng 88 | location.altitude = 0.0 89 | location.speed = 0F 90 | location.accuracy = accuracy 91 | location.speedAccuracyMetersPerSecond = 0F 92 | result = location 93 | } 94 | } 95 | 96 | injectMember { 97 | method { 98 | name = "addGnssBatchingCallback" 99 | returnType = BooleanType 100 | } 101 | replaceToFalse() 102 | } 103 | injectMember { 104 | method { 105 | name = "addGnssMeasurementsListener" 106 | returnType = BooleanType 107 | } 108 | replaceToFalse() 109 | } 110 | injectMember { 111 | method { 112 | name = "addGnssNavigationMessageListener" 113 | returnType = BooleanType 114 | } 115 | replaceToFalse() 116 | } 117 | 118 | } 119 | findClass("com.android.server.LocationManagerService.Receiver").hook { 120 | injectMember { 121 | method { 122 | name = "callLocationChangedLocked" 123 | param(Location::class.java) 124 | } 125 | beforeHook { 126 | lateinit var location: Location 127 | lateinit var originLocation: Location 128 | if (args[0] == null){ 129 | location = Location(LocationManager.GPS_PROVIDER) 130 | location.time = System.currentTimeMillis() - 300 131 | }else { 132 | originLocation = args(0).any() as Location 133 | location = Location(originLocation.provider) 134 | location.time = originLocation.time 135 | location.accuracy = accuracy 136 | location.bearing = originLocation.bearing 137 | location.bearingAccuracyDegrees = originLocation.bearingAccuracyDegrees 138 | location.elapsedRealtimeNanos = originLocation.elapsedRealtimeNanos 139 | location.verticalAccuracyMeters = originLocation.verticalAccuracyMeters 140 | } 141 | 142 | location.latitude = newlat 143 | location.longitude = newlng 144 | location.altitude = 0.0 145 | location.speed = 0F 146 | location.speedAccuracyMetersPerSecond = 0F 147 | XposedBridge.log("GS: lat: ${location.latitude}, lon: ${location.longitude}") 148 | try { 149 | HiddenApiBypass.invoke(location.javaClass, location, "setIsFromMockProvider", false) 150 | } catch (e: Exception) { 151 | loggerW("LocationHook:- ","GS: Not possible to mock $e") 152 | } 153 | args[0] = location 154 | 155 | 156 | } 157 | } 158 | } 159 | 160 | 161 | } 162 | } 163 | 164 | findClass(className).hook { 165 | injectMember { 166 | method { 167 | name = "getLatitude" 168 | returnType = DoubleType 169 | } 170 | beforeHook { 171 | if (System.currentTimeMillis() - mLastUpdated > 200){ 172 | updateLocation() 173 | } 174 | if (settings.isStarted && !ignorePkg.contains(packageName)){ 175 | result = newlat 176 | } 177 | } 178 | } 179 | 180 | injectMember { 181 | method { 182 | name = "getLongitude" 183 | returnType = DoubleType 184 | } 185 | beforeHook { 186 | if (System.currentTimeMillis() - mLastUpdated > 200){ 187 | updateLocation() 188 | } 189 | if (settings.isStarted && !ignorePkg.contains(packageName)){ 190 | result = newlng 191 | } 192 | } 193 | } 194 | 195 | injectMember { 196 | method { 197 | name = "getAccuracy" 198 | returnType = FloatType 199 | } 200 | beforeHook { 201 | if (System.currentTimeMillis() - mLastUpdated > 200){ 202 | updateLocation() 203 | } 204 | if (settings.isStarted && !ignorePkg.contains(packageName)){ 205 | result = accuracy 206 | } 207 | } 208 | } 209 | 210 | 211 | injectMember { 212 | method { 213 | name = "set" 214 | param(Location::class.java) 215 | } 216 | beforeHook { 217 | if (System.currentTimeMillis() - mLastUpdated > 200){ 218 | updateLocation() 219 | } 220 | if (settings.isStarted && !ignorePkg.contains(packageName)){ 221 | lateinit var location: Location 222 | lateinit var originLocation: Location 223 | if (args[0] == null){ 224 | location = Location(LocationManager.GPS_PROVIDER) 225 | location.time = System.currentTimeMillis() - 300 226 | }else { 227 | originLocation = args(0).any() as Location 228 | location = Location(originLocation.provider) 229 | location.time = originLocation.time 230 | location.accuracy = accuracy 231 | location.bearing = originLocation.bearing 232 | location.bearingAccuracyDegrees = originLocation.bearingAccuracyDegrees 233 | location.elapsedRealtimeNanos = originLocation.elapsedRealtimeNanos 234 | location.verticalAccuracyMeters = originLocation.verticalAccuracyMeters 235 | } 236 | 237 | location.latitude = newlat 238 | location.longitude = newlng 239 | location.altitude = 0.0 240 | location.speed = 0F 241 | location.speedAccuracyMetersPerSecond = 0F 242 | XposedBridge.log("GS: lat: ${location.latitude}, lon: ${location.longitude}") 243 | try { 244 | HiddenApiBypass.invoke(location.javaClass, location, "setIsFromMockProvider", false) 245 | } catch (e: Exception) { 246 | loggerW("LocationHook:- ","GS: Not possible to mock $e") 247 | } 248 | args[0] = location 249 | 250 | } 251 | 252 | } 253 | } 254 | 255 | } 256 | 257 | findClass("android.location.LocationManager").hook { 258 | injectMember { 259 | method { 260 | name = "getLastKnownLocation" 261 | param(String::class.java) 262 | } 263 | beforeHook { 264 | if (System.currentTimeMillis() - mLastUpdated > 200){ 265 | updateLocation() 266 | } 267 | if (settings.isStarted && !ignorePkg.contains(packageName)) { 268 | val provider = args[0] as String 269 | val location = Location(provider) 270 | location.time = System.currentTimeMillis() - 300 271 | location.latitude = newlat 272 | location.longitude = newlng 273 | location.altitude = 0.0 274 | location.speed = 0F 275 | location.speedAccuracyMetersPerSecond = 0F 276 | XposedBridge.log("GS: lat: ${location.latitude}, lon: ${location.longitude}") 277 | try { 278 | HiddenApiBypass.invoke(location.javaClass, location, "setIsFromMockProvider", false) 279 | } catch (e: Exception) { 280 | XposedBridge.log("GS: Not possible to mock (Pre Q)! $e") 281 | } 282 | result = location 283 | 284 | 285 | } 286 | 287 | } 288 | } 289 | } 290 | 291 | } 292 | 293 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android1500/gpssetter/xposed/Xshare.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter.xposed 2 | import com.android1500.gpssetter.BuildConfig 3 | import de.robv.android.xposed.XSharedPreferences 4 | 5 | class Xshare { 6 | 7 | private var xPref: XSharedPreferences? = null 8 | 9 | private fun pref() : XSharedPreferences { 10 | xPref = XSharedPreferences(BuildConfig.APPLICATION_ID,"${BuildConfig.APPLICATION_ID}_prefs") 11 | return xPref as XSharedPreferences 12 | } 13 | 14 | 15 | val isStarted : Boolean 16 | get() = pref().getBoolean( 17 | "start", 18 | false 19 | ) 20 | 21 | val getLat: Double 22 | get() = pref().getFloat( 23 | "latitude", 24 | 22.2855200.toFloat() 25 | ).toDouble() 26 | 27 | 28 | val getLng : Double 29 | get() = pref().getFloat( 30 | "longitude", 31 | 114.1576900.toFloat() 32 | ).toDouble() 33 | 34 | val isHookedSystem : Boolean 35 | get() = pref().getBoolean( 36 | "isHookedSystem", 37 | false 38 | ) 39 | 40 | val isRandomPosition :Boolean 41 | get() = pref().getBoolean( 42 | "random_position", 43 | false 44 | ) 45 | 46 | val accuracy : String? 47 | get() = pref().getString("accuracy_settings","10") 48 | 49 | val reload = pref().reload() 50 | 51 | 52 | 53 | 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bottom_sheet_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_accuracy.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_advance_hook.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back_arrow.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_favourite.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_menu_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_my_location_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_search_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dark_mode.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_map_type.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_favourite_list.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_info_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_settings_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_random_position.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stop.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_update.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_joystick.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/search_box_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/about.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 22 | 23 | 29 | 30 | 35 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_map.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 33 | 54 | 55 | 76 | 77 | 78 | 79 | 80 | 84 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | 24 | 25 | 32 | 33 | 41 | 42 | 43 | 44 | 45 | 46 | 63 | 64 | 65 | 66 | 67 | 84 | 85 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/drawer_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 25 | 26 | 27 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fav.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fav_items.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 23 | 24 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/include_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 29 | 30 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/joystick_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/map_container.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/settings_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 27 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/update_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 22 | 23 | 33 | 34 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 19 | 20 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Régulateur GPS 6 | ActivitéMaps 7 | Définir localisation 8 | Rechercher 9 | Lieu non sélectionné 10 | sauvegarder 11 | Etats Unis 12 | Ajouter le favoris 13 | Ajouter favoris à la base de données 14 | Afficher la liste des favoris 15 | Favoris 16 | Addresse introuvable 17 | Enregistrement impossible 18 | Définir localisation 19 | Localisation non définie 20 | Pas d\’internet 21 | "rechercher par exemple new york ou 37.7749,-122.4194" 22 | Notification de l\’emplacement définie 23 | Afficher une notification lorsce que la localisation sera définie 24 | 25 | 26 | 27 | 28 | 29 | Le module n\’est pas activé 30 | Activer ce module dans LSPosed Manager.\n\nVous n\’avez pas LSPosed?\n\t1. Installer le module Riru ou Zygisk depuis from Magisk Manager\n\t2. Installer le module “Riru ou Zygisk - LSPosed” depuis Magisk Manager\n\t3. Ouvrir à nouveau cette application 31 | Pas d\’accès raçine 32 | L\’accès raçine est nécessaire pour que cette application puisse gérrer les surcouches et fonctionner correctement. 33 | 34 | 35 | 36 | 37 | A propos 38 | Régulateur GPS 39 | Un module qui définira votre localisation où que vous le vouliez sans activer la localisation. 40 | 41 | 42 | 43 | Téléchargement de la mise à jour… 44 | Mise à jour 45 | Mise à jour disponible 46 | Échec 47 | 48 | 49 | 50 | 51 | Xposed 52 | Hammeçonner la localisation du système 53 | Réglages 54 | Précision 55 | Général 56 | Entrer un numéro valide 57 | Supprimer favoris 58 | Réglges 59 | Vérifier les mises à jour 60 | Vérifier automatiquement la dernière version sur Github 61 | Système de crochet pour un meilleur ensemble de localisation, vous devez sélectionner le système 62 | Position aléatoire 63 | La position de l\'emplacement sera aléatoire autour de l\'emplacement défini 64 | 65 | 66 | 67 | 68 | 69 | Recherche… 70 | OK 71 | Ajouter favoris 72 | Ajouter 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | android 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Normal 13 | Satellite 14 | Terrain 15 | Hybrid 16 | 17 | 18 | 19 | 1 20 | 2 21 | 3 22 | 4 23 | 24 | 25 | 26 | 27 | 28 | Always off 29 | Always on 30 | Follow system 31 | 32 | 33 | 34 | 1 35 | 2 36 | -1 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #ff3e5e98 5 | #ff24457f 6 | #BBDEFB 7 | @android:color/white 8 | #FFFFFFFF 9 | #000000 10 | #17000000 11 | #FBE9E7 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 100dp 4 | 8dp 5 | 16dp 6 | 24dp 7 | 32dp 8 | 9 | 10 | 56dp 11 | 48dp 12 | 24dp 13 | 36dp 14 | 1dp 15 | 16 | 17 | 18 | 5dp 19 | 16dp 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #5EAEE8 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/overscroll..xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GPS Setter 6 | MapsActivity 7 | Set location 8 | Search 9 | United States 10 | Add favourite 11 | Add fav in database 12 | Show fav list 13 | Favourites 14 | Address not found 15 | Can\'t save 16 | Location set 17 | Location set notification 18 | Show notification when location will set 19 | Location unset 20 | No internet 21 | "search eg. new york or 37.7749,-122.4194" 22 | 23 | 24 | 25 | 26 | Xposed module is not enabled 27 | Enable this module in LSPosed Manager.\n\nDon’t have LSPosed?\n\t1. Install the Riru or Zygisk module from Magisk Manager\n\t2. Install the “Riru or Zygisk - LSPosed” module from Magisk Manager\n\t3. Open this app again 28 | No root access 29 | Root access is necessary in order for this app to manage overlays and work properly. 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | About 38 | GPS Setter 39 | A module which will set your location where you want without enable mock location. 40 | 41 | 42 | 43 | Downloading update… 44 | Update 45 | Update available 46 | Failed 47 | 48 | 49 | 50 | 51 | Add favourite 52 | Add 53 | OK 54 | Location not select 55 | Save 56 | Searching... 57 | 58 | 59 | 60 | 61 | 62 | Xposed 63 | Hook system location 64 | Settings 65 | Accuracy 66 | General 67 | Enter valid number 68 | Delete favourite 69 | Settings 70 | Check for updates 71 | Automatically check for latest version on Github 72 | Hook system for better Location set (Need Reboot) 73 | Random position 74 | Location position will be random around set location 75 | open 76 | close 77 | Stop 78 | Set location 79 | current address 80 | App update fail 81 | 82 | 83 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/xml/setting.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 15 | 16 | 25 | 26 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 53 | 54 | 61 | 62 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/test/java/com/android1500/gpssetter/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.android1500.gpssetter 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | dependencies { 4 | classpath 'com.google.dagger:hilt-android-gradle-plugin:2.44.2' 5 | } 6 | } 7 | plugins { 8 | id 'com.android.application' version '7.4.1' apply false 9 | id 'com.android.library' version '7.4.1' apply false 10 | id 'org.jetbrains.kotlin.android' version '1.8.0' apply false 11 | id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1' apply false 12 | } 13 | 14 | task clean(type: Delete) { 15 | delete rootProject.buildDir 16 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | # When configured, Gradle will run in incubating parallel mode. 10 | # This option should only be used with decoupled projects. More details, visit 11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 12 | # org.gradle.parallel=true 13 | # AndroidX package structure to make it clearer which packages are bundled with the 14 | # Android operating system, and which are packaged with your app"s APK 15 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 16 | android.useAndroidX=true 17 | # Kotlin code style for this project: "official" or "obsolete": 18 | kotlin.code.style=official 19 | # Enables namespacing of each library's R class so that its R class includes only the 20 | # resources declared in the library itself and none from the library's dependencies, 21 | # thereby reducing the size of the R class for that library 22 | android.nonTransitiveRClass=true 23 | android.enableJetifier=true 24 | org.gradle.jvmargs=-Xmx1536m -------------------------------------------------------------------------------- /gradle/wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | ### Android template 2 | # Built application files 3 | *.apk 4 | *.aar 5 | *.ap_ 6 | *.aab 7 | 8 | # Files for the ART/Dalvik VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | out/ 18 | # Uncomment the following line in case you need and you don't have the release build type files in your app 19 | # release/ 20 | 21 | # Gradle files 22 | .gradle/ 23 | build/ 24 | 25 | # Local configuration file (sdk path, etc) 26 | local.properties 27 | 28 | # Proguard folder generated by Eclipse 29 | proguard/ 30 | 31 | # Log Files 32 | *.log 33 | 34 | # Android Studio Navigation editor temp files 35 | .navigation/ 36 | 37 | # Android Studio captures folder 38 | captures/ 39 | 40 | # IntelliJ 41 | *.iml 42 | .idea/workspace.xml 43 | .idea/tasks.xml 44 | .idea/gradle.xml 45 | .idea/assetWizardSettings.xml 46 | .idea/dictionaries 47 | .idea/libraries 48 | # Android Studio 3 in .gitignore file. 49 | .idea/caches 50 | .idea/modules.xml 51 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 52 | .idea/navEditor.xml 53 | 54 | # Keystore files 55 | # Uncomment the following lines if you do not want to check your keystore files in. 56 | #*.jks 57 | #*.keystore 58 | 59 | # External native build folder generated in Android Studio 2.2 and later 60 | .externalNativeBuild 61 | .cxx/ 62 | 63 | # Google Services (e.g. APIs or Firebase) 64 | # google-services.json 65 | 66 | # Freeline 67 | freeline.py 68 | freeline/ 69 | freeline_project_description.json 70 | 71 | # fastlane 72 | fastlane/report.xml 73 | fastlane/Preview.html 74 | fastlane/screenshots 75 | fastlane/test_output 76 | fastlane/readme.md 77 | 78 | # Version control 79 | vcs.xml 80 | 81 | # lint 82 | lint/intermediates/ 83 | lint/generated/ 84 | lint/outputs/ 85 | lint/tmp/ 86 | # lint/reports/ 87 | 88 | # Android Profiling 89 | *.hprof 90 | 91 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android1500/GpsSetter/102613a2972f6a71d09318a0997bcb398f1b4166/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jan 14 09:35:56 PST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | jcenter() 6 | mavenCentral() 7 | } 8 | } 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | jcenter() 15 | maven { 16 | url("https://api.xposed.info/") 17 | } 18 | maven { url 'https://jitpack.io' } 19 | 20 | } 21 | } 22 | rootProject.name = "GPS Setter" 23 | include ':app' 24 | --------------------------------------------------------------------------------