├── .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 |
5 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GPS Setter
2 |
3 | 
4 | []()
5 | [](https://github.com/Xposed-Modules-Repo/com.android1500.gpssetter/releases)
6 | [](https://github.com/Android1500/GpsSetter/stargazers)
7 | [](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 | 
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 | |
| 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------