├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── debug.keystore ├── libs │ └── XposedBridgeApi-54.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── acgmiao │ │ └── dev │ │ └── fuckrunning │ │ └── ExampleInstrumentedTest.java │ ├── debug │ └── res │ │ └── values │ │ └── google_maps_api.xml │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── xposed_init │ ├── java │ │ └── com │ │ │ └── acgmiao │ │ │ └── dev │ │ │ └── fuckrunning │ │ │ ├── MainHook.java │ │ │ ├── MyLocationApplication.java │ │ │ ├── activity │ │ │ ├── MainActivity.java │ │ │ └── MapsActivity.java │ │ │ └── util │ │ │ ├── CoordTrans.java │ │ │ ├── Distance.java │ │ │ ├── PermissionUtils.java │ │ │ └── Rectangle.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── activity_maps.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── release │ └── res │ │ └── values │ │ └── google_maps_api.xml │ └── test │ └── java │ └── com │ └── acgmiao │ └── dev │ └── fuckrunning │ └── ExampleUnitTest.java ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /gradle.properties 5 | /.idea 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FuckRunning 2 | 3 | More infomation please visit this [article](https://acgmiao.com/post/fuckrunning/). 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "26.0.2" 6 | defaultConfig { 7 | applicationId "com.acgmiao.dev.fuckrunning" 8 | minSdkVersion 23 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.1" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | 15 | signingConfigs { 16 | debug { 17 | storeFile file("./debug.keystore") 18 | } 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 25 | } 26 | debug { 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation 'com.google.android.gms:play-services-maps:11.8.0' 33 | compileOnly fileTree(include: ['*.jar'], dir: 'libs') 34 | compileOnly files('libs/XposedBridgeApi-54.jar') 35 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { 36 | exclude group: 'com.android.support', module: 'support-annotations' 37 | }) 38 | implementation 'com.android.support:appcompat-v7:25.4.0' 39 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 40 | testImplementation 'junit:junit:4.12' 41 | } 42 | -------------------------------------------------------------------------------- /app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LIznzn/FuckRunning/2229321911bd8b665f8673de384182c9f016c2f9/app/debug.keystore -------------------------------------------------------------------------------- /app/libs/XposedBridgeApi-54.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LIznzn/FuckRunning/2229321911bd8b665f8673de384182c9f016c2f9/app/libs/XposedBridgeApi-54.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Wenxin/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/acgmiao/dev/fuckrunning/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.acgmiao.dev.fuckrunning; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.acgmiao.dev.fuckrunning", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/debug/res/values/google_maps_api.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | AIzaSyDWjlbBqHGUilN0u0XrY149ZjawzWEK95U 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 13 | 14 | 17 | 18 | 26 | 29 | 32 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 55 | 58 | 59 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | com.acgmiao.dev.fuckrunning.MainHook -------------------------------------------------------------------------------- /app/src/main/java/com/acgmiao/dev/fuckrunning/MainHook.java: -------------------------------------------------------------------------------- 1 | package com.acgmiao.dev.fuckrunning; 2 | 3 | import android.content.Context; 4 | import android.hardware.Sensor; 5 | import android.hardware.SensorEvent; 6 | import android.util.SparseArray; 7 | 8 | import java.lang.reflect.Field; 9 | 10 | import de.robv.android.xposed.IXposedHookLoadPackage; 11 | import de.robv.android.xposed.XC_MethodHook; 12 | import de.robv.android.xposed.XposedBridge; 13 | import de.robv.android.xposed.XposedHelpers; 14 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 15 | 16 | public class MainHook implements IXposedHookLoadPackage { 17 | private static float count = 0; 18 | private static final int max = 100000; 19 | 20 | @Override 21 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { 22 | //测试是否成功生效 23 | if (loadPackageParam.packageName.equals("com.acgmiao.dev.fuckrunning")) { 24 | XposedBridge.log("找到测试应用"); 25 | Class clazz = loadPackageParam.classLoader.loadClass("com.acgmiao.dev.fuckrunning.activity.MainActivity"); 26 | XposedHelpers.findAndHookMethod(clazz, "toastMessage", new XC_MethodHook() { 27 | @Override 28 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 29 | super.beforeHookedMethod(param); 30 | } 31 | 32 | @Override 33 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 34 | super.afterHookedMethod(param); 35 | param.setResult("你已被劫持"); 36 | } 37 | }); 38 | } 39 | //开始拦截目标应用 40 | if (loadPackageParam.packageName.equals("com.zjwh.android_wh_physicalfitness")) { 41 | XposedBridge.log("找到目标应用"); 42 | XposedHelpers.findAndHookMethod("com.stub.StubApp", loadPackageParam.classLoader, "getNewAppInstance", Context.class, new XC_MethodHook() { 43 | @Override 44 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 45 | super.afterHookedMethod(param); 46 | XposedBridge.log("找到防反编译壳"); 47 | Context context = (Context) param.args[0]; 48 | ClassLoader classLoader = context.getClassLoader(); 49 | XposedHelpers.findAndHookMethod("com.zjwh.android_wh_physicalfitness.emulator.c", classLoader, "a", new XC_MethodHook() { 50 | @Override 51 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 52 | super.beforeHookedMethod(param); 53 | } 54 | 55 | @Override 56 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 57 | super.afterHookedMethod(param); 58 | XposedBridge.log("成功劫持检测方法a()"); 59 | param.setResult(false); 60 | } 61 | }); 62 | XposedHelpers.findAndHookMethod("com.zjwh.android_wh_physicalfitness.emulator.c", classLoader, "b", new XC_MethodHook() { 63 | @Override 64 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 65 | super.beforeHookedMethod(param); 66 | } 67 | 68 | @Override 69 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 70 | super.afterHookedMethod(param); 71 | XposedBridge.log("成功劫持检测方法b()"); 72 | param.setResult(false); 73 | } 74 | }); 75 | XposedHelpers.findAndHookMethod("com.zjwh.android_wh_physicalfitness.emulator.c", classLoader, "c", Context.class, new XC_MethodHook() { 76 | @Override 77 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 78 | super.beforeHookedMethod(param); 79 | } 80 | 81 | @Override 82 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 83 | super.afterHookedMethod(param); 84 | XposedBridge.log("成功劫持检测方法c(Context)"); 85 | param.setResult(false); 86 | } 87 | }); 88 | //劫持传感器 89 | final Class sensorEL = XposedHelpers.findClass("android.hardware.SystemSensorManager$SensorEventQueue", classLoader); 90 | XposedBridge.hookAllMethods(sensorEL, "dispatchSensorEvent", new XC_MethodHook() { 91 | @Override 92 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 93 | int handle = (Integer) param.args[0]; 94 | Field field = param.thisObject.getClass().getDeclaredField("mSensorsEvents"); 95 | field.setAccessible(true); 96 | Sensor ss = ((SparseArray) field.get(param.thisObject)).get(handle).sensor; 97 | if (ss == null) { 98 | XposedBridge.log("未劫持到传感器"); 99 | return; 100 | } 101 | if (ss.getType() == Sensor.TYPE_ACCELEROMETER) { 102 | count += 1; 103 | //步频100算法 104 | if (count % 3 == 0) { 105 | ((float[]) param.args[1])[0] = ((float[]) param.args[1])[0] * 100; 106 | ((float[]) param.args[1])[1] += (float) -10; 107 | } else if (count % 2 == 0) { 108 | ((float[]) param.args[1])[0] = ((float[]) param.args[1])[0] * 1000; 109 | ((float[]) param.args[1])[2] += (float) -20; 110 | ((float[]) param.args[1])[1] += (float) -5; 111 | } else { 112 | ((float[]) param.args[1])[0] = ((float[]) param.args[1])[0] * 10; 113 | ((float[]) param.args[1])[2] += (float) 20; 114 | ((float[]) param.args[1])[1] += (float) -15; 115 | } 116 | XposedBridge.log("传感器类型:" + ss.getType() + "加速度传感器,数据" + ((float[]) param.args[1])[0] + "," + ((float[]) param.args[1])[1] + "," + ((float[]) param.args[1])[2]); 117 | } 118 | if (ss.getType() == Sensor.TYPE_STEP_COUNTER || ss.getType() == Sensor.TYPE_STEP_DETECTOR) { 119 | if (10000 * count <= max) { 120 | ((float[]) param.args[1])[0] = ((float[]) param.args[1])[0] + 10000 * count; 121 | count += 1; 122 | } else { 123 | count = 0; 124 | } 125 | XposedBridge.log("传感器类型:" + ss.getType() + "计步器,数据" + ((float[]) param.args[1])[0]); 126 | } 127 | } 128 | }); 129 | } 130 | }); 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /app/src/main/java/com/acgmiao/dev/fuckrunning/MyLocationApplication.java: -------------------------------------------------------------------------------- 1 | package com.acgmiao.dev.fuckrunning; 2 | 3 | import android.app.Application; 4 | 5 | 6 | public class MyLocationApplication extends Application { 7 | 8 | private static MyLocationApplication mApplication; 9 | 10 | @Override 11 | public void onCreate() { 12 | mApplication = this; 13 | super.onCreate(); 14 | } 15 | 16 | public static MyLocationApplication getApplication() { 17 | return mApplication; 18 | } 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/acgmiao/dev/fuckrunning/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.acgmiao.dev.fuckrunning.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.Toast; 9 | 10 | import com.acgmiao.dev.fuckrunning.R; 11 | 12 | 13 | public class MainActivity extends AppCompatActivity { 14 | 15 | @Override 16 | public void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_main); 19 | Button testbutton = (Button) findViewById(R.id.testButton); 20 | testbutton.setOnClickListener(new View.OnClickListener() { 21 | @Override 22 | public void onClick(View v) { 23 | Toast.makeText(MainActivity.this, toastMessage(), Toast.LENGTH_SHORT).show(); 24 | } 25 | }); 26 | Button mapbutton = (Button) findViewById(R.id.mapButton); 27 | mapbutton.setOnClickListener(new View.OnClickListener() { 28 | @Override 29 | public void onClick(View v) { 30 | Intent intent = new Intent(MainActivity.this, MapsActivity.class); 31 | startActivity(intent); 32 | } 33 | }); 34 | } 35 | 36 | private String toastMessage() { 37 | return "我未被劫持"; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/acgmiao/dev/fuckrunning/activity/MapsActivity.java: -------------------------------------------------------------------------------- 1 | package com.acgmiao.dev.fuckrunning.activity; 2 | 3 | import android.Manifest; 4 | import android.annotation.SuppressLint; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.content.pm.PackageManager; 10 | import android.location.Location; 11 | import android.location.LocationListener; 12 | import android.location.LocationManager; 13 | import android.os.Bundle; 14 | import android.support.annotation.NonNull; 15 | import android.support.v4.app.ActivityCompat; 16 | import android.support.v4.content.ContextCompat; 17 | import android.support.v7.app.AppCompatActivity; 18 | import android.util.Log; 19 | import android.widget.Toast; 20 | 21 | import com.acgmiao.dev.fuckrunning.R; 22 | import com.acgmiao.dev.fuckrunning.util.PermissionUtils; 23 | import com.google.android.gms.maps.CameraUpdateFactory; 24 | import com.google.android.gms.maps.GoogleMap; 25 | import com.google.android.gms.maps.LocationSource; 26 | import com.google.android.gms.maps.OnMapReadyCallback; 27 | import com.google.android.gms.maps.SupportMapFragment; 28 | import com.google.android.gms.maps.model.LatLng; 29 | import com.google.android.gms.maps.model.Marker; 30 | import com.google.android.gms.maps.model.MarkerOptions; 31 | 32 | import java.util.List; 33 | 34 | import static com.acgmiao.dev.fuckrunning.util.CoordTrans.GPS2GCJ; 35 | 36 | public class MapsActivity extends AppCompatActivity implements 37 | OnMapReadyCallback, 38 | GoogleMap.OnMyLocationButtonClickListener, 39 | GoogleMap.OnMyLocationClickListener, 40 | GoogleMap.OnMapClickListener, 41 | GoogleMap.OnMapLongClickListener, 42 | ActivityCompat.OnRequestPermissionsResultCallback, 43 | LocationListener { 44 | 45 | private static final long LOCATION_REFRESH_TIME_INTERVAL = 1000; 46 | 47 | private static final float LOCATION_MIN_DISTANCE = 1.0f; 48 | 49 | private static final long BAD_ACCURACY_UPDATE_TIME = 2 * 60 * 1000; 50 | 51 | private GoogleMap mMap; 52 | 53 | private LocationManager mLocationManager; 54 | 55 | private boolean mIsAddListener; 56 | 57 | private boolean mIsPermissionGranted; 58 | 59 | private BroadcastReceiver broadcastReceiver; 60 | 61 | private boolean mIsResume; 62 | 63 | private Location mLastKnownLocation; 64 | 65 | private long mLastUpdateLocationTime; 66 | 67 | private LocationSource.OnLocationChangedListener myLocationListener = null; 68 | 69 | private int MARKER_NUMBER_CONTER = 0; 70 | 71 | private Marker[] mMakerArray = new Marker[5]; 72 | 73 | @Override 74 | protected void onCreate(Bundle savedInstanceState) { 75 | super.onCreate(savedInstanceState); 76 | setContentView(R.layout.activity_maps); 77 | broadcastReceiver = new BroadcastReceiver() { 78 | @Override 79 | public void onReceive(Context context, Intent intent) { 80 | if (intent.getAction().equalsIgnoreCase(LocationManager.PROVIDERS_CHANGED_ACTION)) { 81 | if (mIsPermissionGranted && mIsResume) { 82 | boolean isGPSOn = isGPSOn(); 83 | if (isGPSOn) { 84 | startGetLocation(); 85 | } else { 86 | stopGetLocation(); 87 | } 88 | } 89 | } 90 | } 91 | }; 92 | SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() 93 | .findFragmentById(R.id.map); 94 | mapFragment.getMapAsync(this); 95 | } 96 | 97 | @Override 98 | protected void onResume() { 99 | super.onResume(); 100 | mIsResume = true; 101 | registerBroadcastReceiver(); 102 | if (mIsPermissionGranted) { 103 | startGetLocation(); 104 | } else { 105 | if (checkLocationPermission()) { 106 | mIsPermissionGranted = true; 107 | startGetLocation(); 108 | } 109 | } 110 | } 111 | 112 | @Override 113 | protected void onPause() { 114 | super.onPause(); 115 | mIsResume = true; 116 | unregisterBroadcastReceiver(); 117 | if (mIsPermissionGranted) { 118 | stopGetLocation(); 119 | } 120 | } 121 | 122 | @Override 123 | public void onMapReady(GoogleMap googleMap) { 124 | mMap = googleMap; 125 | boolean isGotLocationPermission = checkLocationPermission(); 126 | if (isGotLocationPermission) { 127 | // goto next method 128 | mIsPermissionGranted = true; 129 | startGetLocation(); 130 | } else { 131 | // need to get location permission 132 | requestLocationPermission(); 133 | } 134 | 135 | mMap.setOnMyLocationButtonClickListener(this); 136 | mMap.setOnMyLocationClickListener(this); 137 | mMap.setOnMapClickListener(this); 138 | mMap.setOnMapLongClickListener(this); 139 | } 140 | 141 | @Override 142 | public void onMapClick(LatLng point) { 143 | addMarker(point); 144 | Toast.makeText(this, "tapped, point=" + point, Toast.LENGTH_SHORT).show(); 145 | } 146 | 147 | @Override 148 | public void onMapLongClick(LatLng point) { 149 | Toast.makeText(this, "long pressed, point=" + point, Toast.LENGTH_SHORT).show(); 150 | } 151 | 152 | public void addMarker(LatLng point) { 153 | if(MARKER_NUMBER_CONTER<5) {//最大5个点 154 | mMakerArray[MARKER_NUMBER_CONTER++] = mMap.addMarker(new MarkerOptions() 155 | .position(point) 156 | .draggable(true) 157 | .alpha(0.7f) 158 | .title(String.valueOf(MARKER_NUMBER_CONTER))); 159 | } else { 160 | Toast.makeText(this, "数组越界,不加Marker", Toast.LENGTH_SHORT).show(); 161 | } 162 | } 163 | 164 | 165 | private void registerBroadcastReceiver() { 166 | IntentFilter intentFilter = new IntentFilter(); 167 | intentFilter.addAction(LocationManager.PROVIDERS_CHANGED_ACTION); 168 | registerReceiver(broadcastReceiver, intentFilter); 169 | } 170 | 171 | private void unregisterBroadcastReceiver() { 172 | unregisterReceiver(broadcastReceiver); 173 | } 174 | 175 | private void initLocationManager() { 176 | if (mLocationManager == null) { 177 | mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); 178 | } 179 | } 180 | 181 | @SuppressLint("MissingPermission") 182 | private void startGetLocation() { 183 | initLocationManager(); 184 | if (!mIsAddListener) { 185 | List providers = mLocationManager.getProviders(true); 186 | for (int i = 0; i < providers.size(); i++) { 187 | String provider = providers.get(i); 188 | Log.e("test", "startGetLocation:" + provider); 189 | mLocationManager.requestLocationUpdates( 190 | provider, LOCATION_REFRESH_TIME_INTERVAL, LOCATION_MIN_DISTANCE, this); 191 | } 192 | mIsAddListener = true; 193 | } 194 | } 195 | 196 | private void stopGetLocation() { 197 | initLocationManager(); 198 | if (mIsAddListener) { 199 | Log.e("test", "stopGetLocation"); 200 | mLocationManager.removeUpdates(this); 201 | mIsAddListener = false; 202 | //mMap.setLocationSource(this); 203 | } 204 | } 205 | 206 | @SuppressLint("MissingPermission") 207 | @Override 208 | public void onLocationChanged(Location location) { 209 | long nowTime = System.currentTimeMillis(); 210 | boolean isUpdateMap = false; 211 | double lot = location.getLongitude(); 212 | double lat = location.getLatitude(); 213 | LatLng mgLatLng = GPS2GCJ(new LatLng(lat, lot)); 214 | location.setLatitude(mgLatLng.latitude); 215 | location.setLongitude(mgLatLng.longitude); 216 | mMap.setLocationSource(new MyLocationSource()); 217 | if (mLastKnownLocation == null) {//第一次定到位 218 | mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mgLatLng, 15)); 219 | } 220 | if (mLastKnownLocation == null || location.getAccuracy() <= mLastKnownLocation.getAccuracy()) { 221 | mLastKnownLocation = location; 222 | mLastUpdateLocationTime = nowTime; 223 | if (!mMap.isMyLocationEnabled()) { 224 | mMap.setMyLocationEnabled(true); 225 | } 226 | isUpdateMap = true; 227 | } 228 | // force update location 229 | if(mLastUpdateLocationTime > 0 && nowTime - mLastUpdateLocationTime >= BAD_ACCURACY_UPDATE_TIME) { 230 | if(location.getAccuracy() >= mLastKnownLocation.getAccuracy()) { 231 | mLastKnownLocation = location; 232 | mLastUpdateLocationTime = nowTime; 233 | isUpdateMap = true; 234 | } 235 | } 236 | if(isUpdateMap){ 237 | myLocationListener.onLocationChanged(mLastKnownLocation); 238 | Log.e("test","onLocationChanged Performed"); 239 | } 240 | Log.e("test", "onLocationChanged:" + location.toString() + ",Provider:" + location.getProvider()); 241 | } 242 | 243 | @Override 244 | public void onStatusChanged(String s, int i, Bundle bundle) { 245 | Log.e("test", "onStatusChanged:" + s + "," + i); 246 | } 247 | 248 | @Override 249 | public void onProviderEnabled(String s) { 250 | Log.e("test", "onProviderEnabled:" + s); 251 | } 252 | 253 | @Override 254 | public void onProviderDisabled(String s) { 255 | Log.e("test", "onProviderDisabled:" + s); 256 | } 257 | 258 | private boolean isGPSOn() { 259 | initLocationManager(); 260 | return mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); 261 | } 262 | 263 | private boolean checkLocationPermission() { 264 | boolean locationPermission = false; 265 | int permissionStatus = ContextCompat. 266 | checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION); 267 | if (permissionStatus == PackageManager.PERMISSION_GRANTED) { 268 | locationPermission = true; 269 | } 270 | return locationPermission; 271 | } 272 | 273 | private void requestLocationPermission() { 274 | PermissionUtils.requestPermission(this, 275 | PermissionUtils.LOCATION_PERMISSION_REQUEST_CODE, 276 | android.Manifest.permission.ACCESS_FINE_LOCATION, false); 277 | } 278 | 279 | @Override 280 | public boolean onMyLocationButtonClick() { 281 | Toast.makeText(this, "MyLocationApplication button clicked", Toast.LENGTH_SHORT).show(); 282 | // Return false so that we don't consume the event and the default behavior still occurs 283 | return false; 284 | } 285 | 286 | @Override 287 | public void onMyLocationClick(@NonNull Location location) { 288 | Toast.makeText(this, "Current location:\n" + location, Toast.LENGTH_LONG).show(); 289 | } 290 | 291 | @Override 292 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 293 | @NonNull int[] grantResults) { 294 | 295 | if (requestCode != PermissionUtils.LOCATION_PERMISSION_REQUEST_CODE) { 296 | return; 297 | } 298 | if (PermissionUtils.isPermissionGranted(permissions, grantResults, 299 | Manifest.permission.ACCESS_FINE_LOCATION)) { 300 | mIsPermissionGranted = true; 301 | initLocationManager(); 302 | startGetLocation(); 303 | } else { 304 | mIsPermissionGranted = false; 305 | Log.e("test", "not get location permission"); 306 | } 307 | } 308 | 309 | @Override 310 | protected void onResumeFragments() { 311 | super.onResumeFragments(); 312 | } 313 | 314 | // private void showMissingPermissionError() { 315 | // PermissionUtils.PermissionDeniedDialog.newInstance(false).show(getSupportFragmentManager(), "dialog"); 316 | // } 317 | 318 | private class MyLocationSource implements LocationSource { 319 | @Override 320 | public void activate(OnLocationChangedListener listener) { 321 | myLocationListener = listener; 322 | } 323 | 324 | @Override 325 | public void deactivate() { 326 | myLocationListener = null; 327 | } 328 | } 329 | } -------------------------------------------------------------------------------- /app/src/main/java/com/acgmiao/dev/fuckrunning/util/CoordTrans.java: -------------------------------------------------------------------------------- 1 | package com.acgmiao.dev.fuckrunning.util; 2 | 3 | import com.google.android.gms.maps.model.LatLng; 4 | 5 | import static com.acgmiao.dev.fuckrunning.util.Rectangle.IsInsideChina; 6 | 7 | public class CoordTrans { 8 | 9 | private static final double pi = 3.1415926535897932384626; 10 | private static final double a = 6378245.0; 11 | private static final double ee = 0.00669342162296594323; 12 | 13 | public static LatLng GPS2GCJ(LatLng location) { 14 | double lat = location.latitude; 15 | double lon = location.longitude; 16 | if (!IsInsideChina(location)) { 17 | return new LatLng(lat, lon); 18 | } 19 | double dLat = transformLat(lon - 105.0, lat - 35.0); 20 | double dLon = transformLon(lon - 105.0, lat - 35.0); 21 | double radLat = lat / 180.0 * pi; 22 | double magic = Math.sin(radLat); 23 | magic = 1 - ee * magic * magic; 24 | double sqrtMagic = Math.sqrt(magic); 25 | dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); 26 | dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi); 27 | double mgLat = lat + dLat; 28 | double mgLon = lon + dLon; 29 | return new LatLng(mgLat, mgLon); 30 | } 31 | 32 | public static LatLng GCJ2GPS(LatLng location) { 33 | double lat = location.latitude; 34 | double lon = location.longitude; 35 | if (!IsInsideChina(location)) { 36 | return new LatLng(lat, lon); 37 | } 38 | double dLat = transformLat(lon - 105.0, lat - 35.0); 39 | double dLon = transformLon(lon - 105.0, lat - 35.0); 40 | double radLat = lat / 180.0 * pi; 41 | double magic = Math.sin(radLat); 42 | magic = 1 - ee * magic * magic; 43 | double sqrtMagic = Math.sqrt(magic); 44 | dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); 45 | dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi); 46 | double mgLat = lat + dLat; 47 | double mgLon = lon + dLon; 48 | double longitude = lon * 2 - mgLon; 49 | double latitude = lat * 2 - mgLat; 50 | return new LatLng(latitude, longitude); 51 | } 52 | 53 | private static double transformLat(double x, double y) { 54 | double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y 55 | + 0.2 * Math.sqrt(Math.abs(x)); 56 | ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; 57 | ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0; 58 | ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0; 59 | return ret; 60 | } 61 | 62 | private static double transformLon(double x, double y) { 63 | double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 64 | * Math.sqrt(Math.abs(x)); 65 | ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; 66 | ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0; 67 | ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 68 | * pi)) * 2.0 / 3.0; 69 | return ret; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/acgmiao/dev/fuckrunning/util/Distance.java: -------------------------------------------------------------------------------- 1 | package com.acgmiao.dev.fuckrunning.util; 2 | 3 | import com.google.android.gms.maps.model.LatLng; 4 | 5 | public class Distance { 6 | 7 | private static final double EARTH_RADIUS = 6378137; 8 | 9 | //根据两点坐标,计算两点间距离,单位为m 10 | public static double GetDistance(LatLng location1, LatLng location2) { 11 | double lat1 = location1.latitude; 12 | double lng1 = location1.longitude; 13 | double lat2 = location2.latitude; 14 | double lng2 = location2.longitude; 15 | double radLat1 = rad(lat1); 16 | double radLat2 = rad(lat2); 17 | double a = radLat1 - radLat2; 18 | double b = rad(lng1) - rad(lng2); 19 | double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + 20 | Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))); 21 | s = s * EARTH_RADIUS; 22 | s = Math.round(s * 10000) / 10000; 23 | return s; 24 | } 25 | 26 | private static double rad(double d) { 27 | return d * Math.PI / 180.0; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/acgmiao/dev/fuckrunning/util/PermissionUtils.java: -------------------------------------------------------------------------------- 1 | package com.acgmiao.dev.fuckrunning.util; 2 | 3 | import android.Manifest; 4 | import android.app.AlertDialog; 5 | import android.app.Dialog; 6 | import android.content.DialogInterface; 7 | import android.content.pm.PackageManager; 8 | import android.os.Bundle; 9 | import android.support.v4.app.ActivityCompat; 10 | import android.support.v4.app.DialogFragment; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.widget.Toast; 13 | 14 | import com.acgmiao.dev.fuckrunning.R; 15 | 16 | public abstract class PermissionUtils { 17 | 18 | public static final int LOCATION_PERMISSION_REQUEST_CODE = 1; 19 | 20 | /** 21 | * Requests the fine location permission. If a rationale with an additional explanation should 22 | * be shown to the user, displays a dialog that triggers the request. 23 | */ 24 | public static void requestPermission(AppCompatActivity activity, int requestId, 25 | String permission, boolean finishActivity) { 26 | if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { 27 | // Display a dialog with rationale. 28 | PermissionUtils.RationaleDialog.newInstance(requestId, finishActivity) 29 | .show(activity.getSupportFragmentManager(), "dialog"); 30 | } else { 31 | // Location permission has not been granted yet, request it. 32 | ActivityCompat.requestPermissions(activity, new String[]{permission}, requestId); 33 | 34 | } 35 | } 36 | 37 | /** 38 | * Checks if the result contains a {@link PackageManager#PERMISSION_GRANTED} result for a 39 | * permission from a runtime permissions request. 40 | * 41 | * @see android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback 42 | */ 43 | public static boolean isPermissionGranted(String[] grantPermissions, int[] grantResults, 44 | String permission) { 45 | for (int i = 0; i < grantPermissions.length; i++) { 46 | if (permission.equals(grantPermissions[i])) { 47 | return grantResults[i] == PackageManager.PERMISSION_GRANTED; 48 | } 49 | } 50 | return false; 51 | } 52 | 53 | /** 54 | * A dialog that displays a permission denied message. 55 | */ 56 | public static class PermissionDeniedDialog extends DialogFragment { 57 | 58 | private static final String ARGUMENT_FINISH_ACTIVITY = "finish"; 59 | 60 | private boolean mFinishActivity = false; 61 | 62 | /** 63 | * Creates a new instance of this dialog and optionally finishes the calling Activity 64 | * when the 'Ok' button is clicked. 65 | */ 66 | public static PermissionDeniedDialog newInstance(boolean finishActivity) { 67 | Bundle arguments = new Bundle(); 68 | arguments.putBoolean(ARGUMENT_FINISH_ACTIVITY, finishActivity); 69 | 70 | PermissionDeniedDialog dialog = new PermissionDeniedDialog(); 71 | dialog.setArguments(arguments); 72 | return dialog; 73 | } 74 | 75 | @Override 76 | public Dialog onCreateDialog(Bundle savedInstanceState) { 77 | mFinishActivity = getArguments().getBoolean(ARGUMENT_FINISH_ACTIVITY); 78 | 79 | return new AlertDialog.Builder(getActivity()) 80 | .setMessage(R.string.location_permission_denied) 81 | .setPositiveButton(android.R.string.ok, null) 82 | .create(); 83 | } 84 | 85 | @Override 86 | public void onDismiss(DialogInterface dialog) { 87 | super.onDismiss(dialog); 88 | if (mFinishActivity) { 89 | Toast.makeText(getActivity(), 90 | "不给权限就自杀", 91 | Toast.LENGTH_SHORT) 92 | .show(); 93 | getActivity().finish(); 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * A dialog that explains the use of the location permission and requests the necessary 100 | * permission. 101 | *

102 | * The activity should implement 103 | * {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback} 104 | * to handle permit or denial of this permission request. 105 | */ 106 | public static class RationaleDialog extends DialogFragment { 107 | 108 | private static final String ARGUMENT_PERMISSION_REQUEST_CODE = "requestCode"; 109 | 110 | private static final String ARGUMENT_FINISH_ACTIVITY = "finish"; 111 | 112 | private boolean mFinishActivity = false; 113 | 114 | /** 115 | * Creates a new instance of a dialog displaying the rationale for the use of the location 116 | * permission. 117 | *

118 | * The permission is requested after clicking 'ok'. 119 | * 120 | * @param requestCode Id of the request that is used to request the permission. It is 121 | * returned to the 122 | * {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback}. 123 | * @param finishActivity Whether the calling Activity should be finished if the dialog is 124 | * cancelled. 125 | */ 126 | public static RationaleDialog newInstance(int requestCode, boolean finishActivity) { 127 | Bundle arguments = new Bundle(); 128 | arguments.putInt(ARGUMENT_PERMISSION_REQUEST_CODE, requestCode); 129 | arguments.putBoolean(ARGUMENT_FINISH_ACTIVITY, finishActivity); 130 | RationaleDialog dialog = new RationaleDialog(); 131 | dialog.setArguments(arguments); 132 | return dialog; 133 | } 134 | 135 | @Override 136 | public Dialog onCreateDialog(Bundle savedInstanceState) { 137 | Bundle arguments = getArguments(); 138 | final int requestCode = arguments.getInt(ARGUMENT_PERMISSION_REQUEST_CODE); 139 | mFinishActivity = arguments.getBoolean(ARGUMENT_FINISH_ACTIVITY); 140 | 141 | return new AlertDialog.Builder(getActivity()) 142 | .setMessage(R.string.permission_rationale_location) 143 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 144 | @Override 145 | public void onClick(DialogInterface dialog, int which) { 146 | // After click on Ok, request the permission. 147 | ActivityCompat.requestPermissions(getActivity(), 148 | new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 149 | requestCode); 150 | // Do not finish the Activity while requesting permission. 151 | mFinishActivity = false; 152 | } 153 | }) 154 | .setNegativeButton(android.R.string.cancel, null) 155 | .create(); 156 | } 157 | 158 | @Override 159 | public void onDismiss(DialogInterface dialog) { 160 | super.onDismiss(dialog); 161 | if (mFinishActivity) { 162 | Toast.makeText(getActivity(), 163 | "不给权限就自杀", 164 | Toast.LENGTH_SHORT) 165 | .show(); 166 | getActivity().finish(); 167 | } 168 | } 169 | } 170 | } -------------------------------------------------------------------------------- /app/src/main/java/com/acgmiao/dev/fuckrunning/util/Rectangle.java: -------------------------------------------------------------------------------- 1 | package com.acgmiao.dev.fuckrunning.util; 2 | 3 | import com.google.android.gms.maps.model.LatLng; 4 | 5 | public class Rectangle { 6 | private double West; 7 | private double North; 8 | private double East; 9 | private double South; 10 | 11 | private Rectangle(double latitude1, double longitude1, double latitude2, double longitude2) { 12 | this.West = Math.min(longitude1, longitude2); 13 | this.North = Math.max(latitude1, latitude2); 14 | this.East = Math.max(longitude1, longitude2); 15 | this.South = Math.min(latitude1, latitude2); 16 | } 17 | 18 | private static Rectangle[] region = new Rectangle[]{ 19 | new Rectangle(49.220400, 79.446200, 42.889900, 96.330000), 20 | new Rectangle(54.141500, 109.687200, 39.374200, 135.000200), 21 | new Rectangle(42.889900, 73.124600, 29.529700, 124.143255), 22 | new Rectangle(29.529700, 82.968400, 26.718600, 97.035200), 23 | new Rectangle(29.529700, 97.025300, 20.414096, 124.367395), 24 | new Rectangle(20.414096, 107.975793, 17.871542, 111.744104), 25 | 26 | }; 27 | 28 | private static Rectangle[] exclude = new Rectangle[]{ 29 | new Rectangle(25.398623, 119.921265, 21.785006, 122.497559), 30 | new Rectangle(22.284000, 101.865200, 20.098800, 106.665000), 31 | new Rectangle(21.542200, 106.452500, 20.487800, 108.051000), 32 | new Rectangle(55.817500, 109.032300, 50.325700, 119.127000), 33 | new Rectangle(55.817500, 127.456800, 49.557400, 137.022700), 34 | new Rectangle(44.892200, 131.266200, 42.569200, 137.022700), 35 | }; 36 | 37 | public static boolean IsInsideChina(LatLng location) { 38 | for (int i = 0; i < region.length; i++) { 39 | if (InRectangle(region[i], location)) { 40 | for (int j = 0; j < exclude.length; j++) { 41 | if (InRectangle(exclude[j], location)) { 42 | return false; 43 | } 44 | } 45 | return true; 46 | } 47 | } 48 | return false; 49 | } 50 | 51 | private static boolean InRectangle(Rectangle rect, LatLng location) { 52 | return rect.West <= location.longitude 53 | && rect.East >= location.longitude 54 | && rect.North >= location.latitude 55 | && rect.South <= location.latitude; 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |