├── library
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ └── values
│ │ │ ├── strings.xml
│ │ │ └── attrs.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── gcssloop
│ │ └── widget
│ │ └── RockerView.java
├── build.gradle
└── proguard-rules.pro
├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── assets
│ │ │ └── xposed_init
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── icon_10.png
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── icon_10.png
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── icon_10.png
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── icon_10.png
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ ├── ic_done.png
│ │ │ │ ├── ic_send.png
│ │ │ │ ├── arrow_move.png
│ │ │ │ ├── close_popw.png
│ │ │ │ ├── ic_clear.png
│ │ │ │ ├── ic_google.png
│ │ │ │ ├── ic_share.png
│ │ │ │ ├── move_info.png
│ │ │ │ ├── ic_more_vert.png
│ │ │ │ ├── ic_arrow_back.png
│ │ │ │ ├── ic_info_outline.png
│ │ │ │ ├── ic_microphone.png
│ │ │ │ ├── ic_zoom_in_normal.png
│ │ │ │ ├── ic_zoom_in_press.png
│ │ │ │ ├── ic_zoom_out_press.png
│ │ │ │ └── ic_zoom_out_normal.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── icon_10.png
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values-zh-rCN-v23
│ │ │ │ └── strings.xml
│ │ │ ├── values-v23
│ │ │ │ └── strings.xml
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-v19
│ │ │ │ └── styles.xml
│ │ │ ├── drawable
│ │ │ │ ├── selector_zoom_in.xml
│ │ │ │ └── selector_zoom_out.xml
│ │ │ ├── menu
│ │ │ │ └── search.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ ├── values-zh-rCN
│ │ │ │ └── strings.xml
│ │ │ └── layout
│ │ │ │ ├── layout_direction.xml
│ │ │ │ ├── activity_about.xml
│ │ │ │ └── activity_main.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── github
│ │ │ │ └── jinsen47
│ │ │ │ └── pokefaker
│ │ │ │ ├── app
│ │ │ │ ├── PokeApp.java
│ │ │ │ ├── Constants.java
│ │ │ │ ├── event
│ │ │ │ │ └── MapPickEvent.java
│ │ │ │ ├── util
│ │ │ │ │ ├── StateCheckUtil.java
│ │ │ │ │ ├── Build.java
│ │ │ │ │ ├── SystemProperties.java
│ │ │ │ │ └── PermissionUtil.java
│ │ │ │ ├── ui
│ │ │ │ │ ├── AboutActivity.java
│ │ │ │ │ ├── DirectionLayout.java
│ │ │ │ │ └── MainActivity.java
│ │ │ │ └── service
│ │ │ │ │ ├── MockProvider.java
│ │ │ │ │ ├── LocationHolder.java
│ │ │ │ │ └── LocationService.java
│ │ │ │ ├── MockProviderFaker.java
│ │ │ │ └── HookLocationFaker.java
│ │ └── AndroidManifest.xml
│ ├── debug
│ │ └── res
│ │ │ └── values
│ │ │ └── google_maps_api.xml
│ └── release
│ │ └── res
│ │ └── values
│ │ └── google_maps_api.xml
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── png
├── 1.png
├── 2.png
└── 3.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── README_cn.md
├── LICENSE
├── README.md
├── gradlew.bat
└── gradlew
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /local.properties
3 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------
/png/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/png/1.png
--------------------------------------------------------------------------------
/png/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/png/2.png
--------------------------------------------------------------------------------
/png/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/png/3.png
--------------------------------------------------------------------------------
/app/src/main/assets/xposed_init:
--------------------------------------------------------------------------------
1 | com.github.jinsen47.pokefaker.MockProviderFaker
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Library
3 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.keystore
3 | .gradle
4 | /local.properties
5 | /.idea
6 | .DS_Store
7 | /build
8 | /captures
9 | /art
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/icon_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/mipmap-hdpi/icon_10.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/icon_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/mipmap-mdpi/icon_10.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/icon_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/mipmap-xhdpi/icon_10.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/icon_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/mipmap-xxhdpi/icon_10.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_done.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_send.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_send.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/icon_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/mipmap-xxxhdpi/icon_10.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/arrow_move.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/arrow_move.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/close_popw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/close_popw.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_clear.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_google.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_share.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/move_info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/move_info.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_more_vert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_more_vert.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_info_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_info_outline.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_microphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_microphone.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_zoom_in_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_zoom_in_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_zoom_in_press.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_zoom_in_press.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_zoom_out_press.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_zoom_out_press.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_zoom_out_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinsen47/PokeFaker/HEAD/app/src/main/res/drawable-xxhdpi/ic_zoom_out_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN-v23/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 请进入开发者选项页面, 选择模拟位置应用为本App\n
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v23/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Go to developer options and allow this app to mock location.\n
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v19/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/selector_zoom_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/selector_zoom_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/search.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/PokeApp.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app;
2 |
3 | import android.app.Application;
4 |
5 | /**
6 | * Created by Jinsen on 16/7/13.
7 | */
8 | public class PokeApp extends Application {
9 | @Override
10 | public void onCreate() {
11 | super.onCreate();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/Constants.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app;
2 |
3 | /**
4 | * Created by Jinsen on 16/7/23.
5 | */
6 | public class Constants {
7 |
8 | public static final String PACKAGE_POKEFAKER = "com.github.jinsen47.pokefaker";
9 | public static final String PACKAGE_POKEMONGO = "com.nianticlabs.pokemongo";
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/event/MapPickEvent.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app.event;
2 |
3 | import com.google.android.gms.maps.model.LatLng;
4 |
5 | /**
6 | * Created by Jinsen on 16/7/13.
7 | */
8 | public class MapPickEvent {
9 | public LatLng latLng;
10 |
11 | public MapPickEvent(LatLng latLng) {
12 | this.latLng = latLng;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #DA4336
7 | #6AF79A
8 | #C0C0C0C0
9 | #C0C0C0C0
10 |
11 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | minSdkVersion 11
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | testCompile 'junit:junit:4.12'
24 | compile 'com.android.support:appcompat-v7:23.4.0'
25 | compile 'com.github.GcsSloop:ViewSupport:v1.0.6'
26 | }
27 |
--------------------------------------------------------------------------------
/library/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/GcsSloop/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 |
--------------------------------------------------------------------------------
/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/Jinsen/AndroidSDK/android-sdk-macosx/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 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/util/StateCheckUtil.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app.util;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Created by Jinsen on 16/7/17.
10 | */
11 | public class StateCheckUtil {
12 |
13 | public static boolean isLocationServiceRunning(Context context, Class> serviceClass) {
14 | ActivityManager am = ((ActivityManager) context.getSystemService(context.ACTIVITY_SERVICE));
15 | List tasks = am.getRunningServices(Integer.MAX_VALUE);
16 | for (ActivityManager.RunningServiceInfo info : tasks) {
17 | if (info.service.getClassName().equals(serviceClass.getName()))
18 | return true;
19 | }
20 | return false;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/debug/res/values/google_maps_api.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 | AIzaSyALYwmZwIMDyVPeBoaYTAem9cf_xzj_5Xc
14 |
15 |
--------------------------------------------------------------------------------
/app/src/release/res/values/google_maps_api.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 | AIzaSyBYFpQVzvjUnEpm_aOstxiMVYkOqeJ-9cI
14 |
15 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | PokeFaker
4 | 关于
5 | 去设置
6 | PokeFaker 正在运行
7 | 点击设置位置
8 | 退出并停止服务?
9 | 版本: %1$s
10 | 如果你有什么建议(意见就就不用提了), 或者发现了什么bug(肯定有), 去Github开issue.
11 | 需要%1$s权限, 点击设置
12 | 悬浮窗
13 | 相机
14 | 模拟位置
15 |
16 |
17 | 无法识别的输入
18 | 清空
19 | 分享精灵坐标
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/util/Build.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app.util;
2 |
3 | /**
4 | * Created by Jinsen on 16/7/23.
5 | */
6 | public class Build extends android.os.Build {
7 | public static class VERSION_CODES extends android.os.Build.VERSION_CODES {
8 | /**
9 | * August 2014: MIUI 6
10 | */
11 | public static final int MIUI_6 = 4;
12 |
13 | /**
14 | * August 2015: MIUI 7
15 | */
16 | public static final int MIUI_7 = 5;
17 | }
18 | public static class VERSION extends android.os.Build.VERSION {
19 | /**
20 | * The user-visible SDK version of the framework; its possible
21 | * values are defined in {@link Build.VERSION_CODES}.
22 | */
23 | public static final int MIUI_INT = SystemProperties.getInt(
24 | "ro.miui.ui.version.code", 0);
25 | }
26 |
27 | public static boolean IS_MIUI = Build.VERSION.MIUI_INT >= Build.VERSION_CODES.MIUI_6;
28 | }
29 |
--------------------------------------------------------------------------------
/README_cn.md:
--------------------------------------------------------------------------------
1 | # PokeFaker
2 | 一个关于PokemonGo的Xposed插件
3 |
4 | ### [English version](README.md)
5 |
6 | ## DISCLAIMER
7 | - 这是一个Xposed作弊插件, 任何作弊都有被发现的可能, 使用前请做好被封号的心理准备
8 | - 对任何可能造成的封号, **不负责**
9 | - 对任何异常情况造成的数据丢失, 或变砖等等, **不负责**
10 | - 如果对以上声明任何一条不同意, 请Ctrl+W(Cmd+W)离开
11 |
12 | ## DOWNLOAD
13 | [Fir](http://fir.im/z7vm) [Xposed](http://repo.xposed.info/module/com.github.jinsen47.pokefaker)
14 |
15 | ## HOW TO USE
16 | - 需要Xposed框架和Xposed框架管理器
17 | - 需要科学上网
18 | - 下载并安装应用, **在Xposed框架管理器中启用PokeFaker**
19 | - 重启手机, 打开权限管理(安全中心等等), 在应用权限设置中允许**悬浮窗**权限
20 | - 打开GPS, PokemonGo会检测
21 | - 打开应用, 选个你喜欢的地方
22 | - 打开PokemonGo, 起飞~
23 |
24 | ## FEATURES
25 | - 提供了一个简单的方向选择器, 单击分别向四个方向移动固定的距离
26 | - GPS开关要打开, 但是实际并不会调起系统GPS
27 | - 使用Google地图选择地点, 世界任你遨游
28 | - 完全合法, 没有反编译或替换他们一个字节
29 |
30 | ## SCREENSHOT
31 | 
32 | 
33 | 
34 |
35 | ## HELP IN NEED
36 | - 我用PS自己搞了个图标, 但是并不是很满意, 有想法的同学欢迎联系我
37 | - 个人开发者, 欢迎打赏一杯咖啡钱~
38 |
39 | ## IN THE FUTURE
40 | - 屏幕方向键太难用, 准备换成屏幕摇杆
41 | - 点击方向键, 反应太慢, 准备着手解决, 但是目前并没有什么想法
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Jinsen Sheng
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PokeFaker
3 | About
4 | Go to Settings
5 | PokeFaker Running
6 | Click for setting
7 | Invalid Input
8 | Quit this app and stop service?
9 | Version: %1$s
10 | If you find bugs or have suggestions and sure you will, please open an issue on Github, I\'m glad to make this app better.
11 | %1$s permission needed. Go setting.
12 | POPUP WINDOW
13 | CAMERA
14 | MOCK LOCATION
15 |
16 |
17 | eg:(34.00943,-118.49789)
18 | clear
19 | Share you pokemon location
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/ui/AboutActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app.ui;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.MenuItem;
7 | import android.widget.TextView;
8 |
9 | import com.github.jinsen47.pokefaker.BuildConfig;
10 | import com.github.jinsen47.pokefaker.R;
11 |
12 | /**
13 | * Created by Jinsen on 16/7/17.
14 | */
15 | public class AboutActivity extends AppCompatActivity {
16 | @Override
17 | protected void onCreate(@Nullable Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_about);
20 | if (getSupportActionBar() != null) {
21 | getSupportActionBar().setDisplayShowHomeEnabled(true);
22 | getSupportActionBar().setLogo(R.mipmap.ic_launcher);
23 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
24 | }
25 | ((TextView) findViewById(R.id.text_version)).setText(getString(R.string.version, BuildConfig.VERSION_NAME));
26 | }
27 |
28 | @Override
29 | public boolean onOptionsItemSelected(MenuItem item) {
30 | if (item.getItemId() == android.R.id.home) {
31 | this.finish();
32 | return true;
33 | }
34 | return super.onOptionsItemSelected(item);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_direction.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
24 |
25 |
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PokeFaker
2 | An Xposed plugin to play PokemonGo freely.
3 |
4 | ### [Chinese version](README_cn.md)
5 |
6 | ## DISCLAIMER
7 | - This is a plugin for cheating, should be used with caution.
8 | - **NOT RESPONSIBLE** for any possible account forbidden.
9 | - **NOT RESPONSIBLE** for any exceptions or bricked situations.
10 |
11 | ## DOWNLOAD
12 | [Fir](http://fir.im/z7vm) [Xposed](http://repo.xposed.info/module/com.github.jinsen47.pokefaker)
13 |
14 | ## HOW TO USE
15 | - You need to flash your Android phone with Xposed Framework, and install Xposed_installer.
16 | - Download the release apk and install it, **enable it in the Xposed_installer**
17 | - Reboot your phone and when it's done, open the **Permission Manager**(or something like that), grant the pop-up window permission on app's setting page.
18 | - Turn on your GPS.
19 | - Open the app and select the location you want to start the game, and if no exceptions are thrown you are good to go.
20 | - Open PokemonGo and have fun.
21 |
22 | ## FEATURES
23 | - Provide a dpad(joystick like, but not) on the screen, move your character by four buttons.
24 | - Do not need to use GPS(BUT U HAVE TO OPEN IT), battery saving.
25 | - Provide a google map view to select location.
26 | - Totally legal, I dont decompile or replace any bytes in there app.
27 |
28 | ## SCREENSHOT
29 | 
30 | 
31 | 
32 |
33 | ## HELP IN NEED
34 | - I photoshoped an icon for now, but I dont like much, if you are interested in helping me, please contact me.
35 | - Personal developer, donation in need.
36 |
37 | ## IN THE FUTURE
38 | - Dpad view is ugly and hard to use, prepare to replace it with joystick.
39 | - PokemonGo takes too long to respond moving actions, try to make this better, but dont got a clue.
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
11 |
18 |
27 |
30 |
33 |
36 |
40 |
43 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | def loadProperty(String property) {
4 | def local = new Properties()
5 | local.load(new FileInputStream(new File('app/local.properties')))
6 | local.getProperty(property)
7 | }
8 |
9 | android {
10 | compileSdkVersion 23
11 | buildToolsVersion "23.0.3"
12 | defaultConfig {
13 | applicationId "com.github.jinsen47.pokefaker"
14 | minSdkVersion 16
15 | targetSdkVersion 23
16 | versionCode 2
17 | versionName '1.0.1'
18 | }
19 | signingConfigs {
20 | release {
21 | storeFile file('../jinsen47.keystore')
22 | keyPassword loadProperty('KEYPASS')
23 | keyAlias loadProperty('ALIAS')
24 | storePassword loadProperty('STOREPASS')
25 | }
26 | debug {
27 | }
28 | }
29 | buildTypes {
30 | release {
31 | minifyEnabled false
32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
33 | signingConfig signingConfigs.release
34 | }
35 | }
36 | lintOptions {
37 | checkReleaseBuilds false
38 | // Or, if you prefer, you can continue to check for errors in release builds,
39 | // but continue the build even when errors are found:
40 | abortOnError false
41 | }
42 | productFlavors {
43 | }
44 | }
45 |
46 | dependencies {
47 | compile fileTree(dir: 'libs', include: ['*.jar'])
48 | compile 'com.android.support:appcompat-v7:23.4.0'
49 | compile 'com.google.android.gms:play-services-maps:9.2.0'
50 | provided 'de.robv.android.xposed:api:82'
51 | provided 'de.robv.android.xposed:api:82:sources'
52 | compile 'org.greenrobot:eventbus:3.0.0'
53 | compile 'com.jaredrummler:android-processes:1.0.8'
54 | compile 'com.github.renaudcerrato:FloatingSearchView:1.0.1'
55 | compile 'com.github.renaudcerrato:ToggleDrawable:1.0.1'
56 | compile 'com.github.clans:fab:1.6.4'
57 | compile project(':library')
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
13 |
14 |
23 |
24 |
32 |
33 |
39 |
40 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
17 |
18 |
24 |
27 |
30 |
33 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/util/SystemProperties.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app.util;
2 |
3 | import java.lang.reflect.InvocationTargetException;
4 | import java.lang.reflect.Method;
5 |
6 | /**
7 | * Created by sunbreak on 1/7/16.
8 | */
9 | public class SystemProperties {
10 |
11 | public static final String PROXY_CLASS_NAME = "android.os.SystemProperties";
12 | public static final String PROPERTY_SEARCH = "miuibbs.app.search";
13 |
14 | public static String get(String key) {
15 | ClassLoader classLoader = SystemProperties.class.getClassLoader();
16 | try {
17 | Class> clz = classLoader.loadClass(PROXY_CLASS_NAME);
18 | Class[] params = {String.class};
19 | Method mGet = clz.getMethod("get", params);
20 | Object[] arguments = {key};
21 | return (String) mGet.invoke(clz, arguments);
22 | } catch (ClassNotFoundException e) {
23 | e.printStackTrace();
24 | } catch (NoSuchMethodException e) {
25 | e.printStackTrace();
26 | } catch (InvocationTargetException e) {
27 | e.printStackTrace();
28 | } catch (IllegalAccessException e) {
29 | e.printStackTrace();
30 | }
31 | return null;
32 | }
33 |
34 | public static String get(String key, String defValue) {
35 | ClassLoader classLoader = SystemProperties.class.getClassLoader();
36 | try {
37 | Class> clz = classLoader.loadClass(PROXY_CLASS_NAME);
38 | Class[] params = {String.class, String.class};
39 | Method mGet = clz.getMethod("get", params);
40 | Object[] arguments = {key, defValue};
41 | return (String) mGet.invoke(clz, arguments);
42 | } catch (ClassNotFoundException e) {
43 | e.printStackTrace();
44 | } catch (NoSuchMethodException e) {
45 | e.printStackTrace();
46 | } catch (InvocationTargetException e) {
47 | e.printStackTrace();
48 | } catch (IllegalAccessException e) {
49 | e.printStackTrace();
50 | }
51 | return defValue;
52 | }
53 |
54 | public static int getInt(String key, int defValue) {
55 | ClassLoader classLoader = SystemProperties.class.getClassLoader();
56 | try {
57 | Class> clz = classLoader.loadClass(PROXY_CLASS_NAME);
58 | Class[] params = {String.class, int.class};
59 | Method mGet = clz.getMethod("getInt", params);
60 | Object[] arguments = {key, defValue};
61 | return (Integer) mGet.invoke(clz, arguments);
62 | } catch (ClassNotFoundException e) {
63 | e.printStackTrace();
64 | } catch (NoSuchMethodException e) {
65 | e.printStackTrace();
66 | } catch (InvocationTargetException e) {
67 | e.printStackTrace();
68 | } catch (IllegalAccessException e) {
69 | e.printStackTrace();
70 | }
71 | return defValue;
72 | }
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/service/MockProvider.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app.service;
2 |
3 | import android.content.Context;
4 | import android.database.DataSetObserver;
5 | import android.location.Location;
6 | import android.location.LocationManager;
7 | import android.location.LocationProvider;
8 | import android.os.Build;
9 | import android.os.SystemClock;
10 | import android.support.annotation.NonNull;
11 | import android.text.TextUtils;
12 | import android.util.Log;
13 |
14 | import com.google.android.gms.maps.model.LatLng;
15 |
16 | /**
17 | * Created by Jinsen on 16/7/14.
18 | */
19 | public class MockProvider {
20 |
21 | private static final String TAG = MockProvider.class.getSimpleName();
22 |
23 | private final String mProvider;
24 | private final Context mContext;
25 | private LocationManager mLocationManager;
26 | private DataSetObserver mDataObserver = new DataSetObserver() {
27 | @Override
28 | public void onChanged() {
29 | LatLng latLng = LocationHolder.getInstance(mContext).pollLatLng();
30 | postLocation(latLng.latitude, latLng.longitude);
31 | }
32 | };
33 |
34 | /**
35 | *
36 | * @param context
37 | * @param provider
38 | * @throws IllegalArgumentException if provider is empty or null
39 | */
40 | public MockProvider(@NonNull Context context, @NonNull String provider) {
41 | if (TextUtils.isEmpty(provider)) {
42 | throw new IllegalArgumentException("Provider should not be null or empty!");
43 | }
44 | if (context == null) {
45 | throw new IllegalArgumentException("Context should not be null");
46 | }
47 | mContext = context;
48 | mProvider = provider;
49 | mLocationManager = ((LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE));
50 |
51 | mLocationManager.addTestProvider(mProvider, false, false, false, false, true /* allow speed */, false, false, 0, 5);
52 | }
53 |
54 | public synchronized void start() {
55 | LocationHolder.getInstance(mContext).registerObserver(mDataObserver);
56 | mLocationManager.setTestProviderEnabled(mProvider, true);
57 | mLocationManager.setTestProviderStatus(mProvider, LocationProvider.AVAILABLE, null, System.currentTimeMillis());
58 | }
59 |
60 | public synchronized void pause() {
61 | LocationHolder.getInstance(mContext).unregisterObserver(mDataObserver);
62 | mLocationManager.setTestProviderEnabled(mProvider, false);
63 | }
64 |
65 | public synchronized void remove() {
66 | LocationHolder.getInstance(mContext).unregisterObserver(mDataObserver);
67 | try {
68 | if (mLocationManager.getProvider(mProvider) != null) {
69 | mLocationManager.removeTestProvider(mProvider);
70 | }
71 | } catch (IllegalArgumentException e) {
72 | Log.e(TAG, mProvider + "has already been removed!");
73 | } catch (SecurityException e) {
74 | Log.e(TAG, e.getMessage());
75 | }
76 | }
77 |
78 | public void postLocation(double latitude, double longitude) {
79 | Location l = new Location(mProvider);
80 | l.setLatitude(latitude);
81 | l.setLongitude(longitude);
82 | l.setAccuracy(25.0f);
83 | l.setAltitude(47.0f);
84 | l.setTime(System.currentTimeMillis());
85 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
86 | l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
87 | }
88 | postLocation(l);
89 | }
90 |
91 | private void postLocation(Location l) {
92 | try {
93 | mLocationManager.setTestProviderLocation(mProvider, l);
94 | } catch (SecurityException e) {
95 | Log.e(TAG, "Mock location disabled!!!");
96 | }
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
11 |
19 |
26 |
38 |
43 |
54 |
66 |
78 |
79 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/ui/DirectionLayout.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app.ui;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.view.LayoutInflater;
6 | import android.view.MotionEvent;
7 | import android.view.View;
8 | import android.view.WindowManager;
9 | import android.widget.ImageView;
10 | import android.widget.RelativeLayout;
11 |
12 | import com.gcssloop.widget.RockerView;
13 | import com.github.jinsen47.pokefaker.R;
14 |
15 | /**
16 | * Created by Jinsen on 16/7/13.
17 | */
18 | public class DirectionLayout extends RelativeLayout {
19 | private Context mContext;
20 | private View mContentView;
21 | private RockerView rocker;
22 | private ImageView moveInfo;
23 | private ImageView moveButton;
24 | private onDirectionLayoutListener mListener;
25 | private WindowManager windowManager;
26 | private double radian = 0D;
27 | private float xInScreen;
28 | private float yInScreen;
29 | private boolean isMovingView;
30 |
31 |
32 | public DirectionLayout(Context context) {
33 | super(context);
34 | mContext = context;
35 | windowManager=(WindowManager)getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
36 | init();
37 | }
38 |
39 | private void init() {
40 | LayoutInflater inflater = LayoutInflater.from(mContext);
41 | mContentView = inflater.inflate(R.layout.layout_direction, this);
42 | rocker = (RockerView) mContentView.findViewById(R.id.rocker);
43 | rocker.setRefreshCycle(99999);
44 | moveInfo = ((ImageView)mContentView.findViewById(R.id.move_info));
45 | moveButton = ((ImageView)mContentView.findViewById(R.id.arrowMove));
46 | moveButton.setVisibility(INVISIBLE);
47 | moveInfo.setOnClickListener(new View.OnClickListener() {
48 | @Override
49 | public void onClick(View v) {
50 | if(isMovingView){
51 | moveInfo.setBackgroundResource(R.drawable.move_info);
52 | setRockerEnabled(true);
53 | }else{
54 | moveInfo.setBackgroundResource(R.drawable.close_popw);
55 | setRockerEnabled(false);
56 | }
57 | isMovingView = !isMovingView;
58 | }
59 | });
60 |
61 | if (null != rocker){
62 | rocker.setListener(new RockerView.RockerListener() {
63 | @Override
64 | public void callback(int eventType, int currentAngle, double power) {
65 | if (currentAngle > 0){
66 | radian = currentAngle * Math.PI / 180;
67 | mListener.onDirection(radian, power);
68 | }
69 | switch (eventType) {
70 | case RockerView.EVENT_ACTION:
71 | rocker.setRefreshCycle(1500);
72 | break;
73 | case RockerView.EVENT_CLOCK:
74 | if(currentAngle<0){
75 | rocker.setRefreshCycle(99999);
76 | }
77 | break;
78 | }
79 | }
80 | });
81 | }
82 | }
83 |
84 | public void setRockerEnabled(boolean flag){
85 | if(!flag){
86 | rocker.setAreaColor(0x99777777);
87 | rocker.setRockerColor(0x00FFFFFF);
88 | moveButton.setVisibility(VISIBLE);
89 | moveButton.setOnTouchListener(mTouchListener);
90 | mListener.getLayoutParams().alpha=1f;
91 | }else{
92 | rocker.setAreaColor(0x44777777);
93 | rocker.setRockerColor(0x77555555);
94 | moveButton.setVisibility(INVISIBLE);
95 | moveButton.setOnTouchListener(null);
96 | mListener.getLayoutParams().alpha=0.5f;
97 | }
98 | windowManager.updateViewLayout(this, mListener.getLayoutParams());
99 | rocker.setEnabled(flag);
100 | }
101 |
102 | private View.OnTouchListener mTouchListener = new View.OnTouchListener() {
103 | @Override
104 | public boolean onTouch(View v, MotionEvent event) {
105 | switch (event.getAction()) {
106 | case MotionEvent.ACTION_MOVE:
107 | xInScreen = event.getRawX();
108 | yInScreen = event.getRawY();
109 | updateViewPosition();
110 | break;
111 | default:
112 | break;
113 | }
114 | return true;
115 | }
116 | };
117 |
118 | private void updateViewPosition(){
119 | mListener.getLayoutParams().x = (int) (xInScreen-this.getWidth()/2);
120 | mListener.getLayoutParams().y = (int) (yInScreen-this.getHeight()/2);
121 | windowManager.updateViewLayout(this, mListener.getLayoutParams());
122 | saveWindowPosition(mListener.getLayoutParams().x, mListener.getLayoutParams().y);
123 | }
124 |
125 | private void saveWindowPosition(int x, int y) {
126 | SharedPreferences mSp = getContext().getSharedPreferences("default", Context.MODE_PRIVATE);
127 | SharedPreferences.Editor editor = mSp.edit();
128 | editor.putInt("window_x", x);
129 | editor.putInt("window_y", y);
130 | editor.apply();
131 | }
132 |
133 | public interface onDirectionLayoutListener {
134 | void onDirection(double radian, double power);
135 | WindowManager.LayoutParams getLayoutParams();
136 | }
137 |
138 | public void setDirectionLayoutListener(onDirectionLayoutListener listener) {
139 | mListener = listener;
140 | }
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/service/LocationHolder.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app.service;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.database.DataSetObserver;
6 | import android.location.Location;
7 | import android.os.Handler;
8 | import android.os.Looper;
9 | import android.os.SystemClock;
10 | import android.util.Log;
11 |
12 | import com.google.android.gms.maps.model.LatLng;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | import de.robv.android.xposed.XSharedPreferences;
18 |
19 | /**
20 | * Created by Jinsen on 16/7/13.
21 | */
22 | public class LocationHolder {
23 | private static final String TAG = LocationHolder.class.getSimpleName();
24 | private static LocationHolder sInstance;
25 |
26 | private static long REPEAT_TIME = 1000;
27 |
28 | private Context mContext;
29 | private Handler mHandler;
30 | private SharedPreferences mPreference = null;
31 | private XSharedPreferences mXPreference = null;
32 | private LatLng mCacheLatLng;
33 |
34 | private List mObervers = new ArrayList<>();
35 |
36 | private boolean isRunning = false;
37 |
38 | private LocationHolder(Context context) {
39 | if (context != null) {
40 | mHandler = new Handler(Looper.getMainLooper());
41 | mPreference = context.getSharedPreferences("location", Context.MODE_WORLD_READABLE);
42 | } else {
43 | mXPreference = new XSharedPreferences("com.github.jinsen47.pokefaker", "location");
44 | mXPreference.makeWorldReadable();
45 | }
46 | }
47 |
48 | public void start() {
49 | if (!isRunning) {
50 | mHandler.postDelayed(new Runnable() {
51 | @Override
52 | public void run() {
53 | notifyDataObserver();
54 | if (isRunning) {
55 | mHandler.postDelayed(this, REPEAT_TIME);
56 | }
57 | }
58 | }, REPEAT_TIME);
59 | isRunning = true;
60 | }
61 | }
62 |
63 | public void stop() {
64 | mHandler.removeCallbacks(null);
65 | isRunning = false;
66 | }
67 |
68 | public static LocationHolder getInstance(Context context) {
69 | if (sInstance == null) {
70 | synchronized (LocationHolder.class) {
71 | if (sInstance == null) {
72 | sInstance = new LocationHolder(context);
73 | }
74 | }
75 | }
76 | return sInstance;
77 | }
78 |
79 | public synchronized void postLocation(LatLng latLng) {
80 | if (latLng != null) {
81 | writeToPreference(latLng);
82 | mCacheLatLng = latLng;
83 | notifyDataObserver();
84 | }
85 | }
86 |
87 | private void notifyDataObserver() {
88 | Log.v(TAG, "posting data:" + mCacheLatLng.toString());
89 | for (DataSetObserver o : mObervers) {
90 | o.onChanged();
91 | }
92 | }
93 |
94 | private void writeToPreference(LatLng latLng) {
95 | if (mPreference != null) {
96 | SharedPreferences.Editor editor = mPreference.edit();
97 | editor.putString("latitude", latLng.latitude + "");
98 | editor.putString("longitude", latLng.longitude + "");
99 | editor.apply();
100 | }
101 | }
102 |
103 |
104 | public LatLng pollLatLng() {
105 | if (mXPreference != null) {
106 | mXPreference.reload();
107 | mCacheLatLng = restoreFromXpreference();
108 | }
109 | return mCacheLatLng;
110 | }
111 |
112 | public Location pollLocation(String provider) {
113 | Location l = new LocationBuilder().defaultBuilder().location(pollLatLng()).build();
114 | l.setProvider(provider);
115 | return l;
116 | }
117 |
118 | private LatLng restoreFromXpreference() {
119 | if (mXPreference != null) {
120 | mXPreference.reload();
121 | double latitude = Double.parseDouble(mXPreference.getString("latitude", "0"));
122 | double longitude = Double.parseDouble(mXPreference.getString("longitude", "0"));
123 | return new LatLng(latitude, longitude);
124 | }
125 | return null;
126 | }
127 |
128 | public void registerObserver(DataSetObserver o) {
129 | if (o != null && !mObervers.contains(o)) {
130 | mObervers.add(o);
131 | }
132 | }
133 |
134 | public void unregisterObserver(DataSetObserver o) {
135 | if (mObervers.contains(o)) {
136 | mObervers.remove(o);
137 | }
138 | }
139 |
140 | private static class LocationBuilder {
141 | Location location;
142 | public LocationBuilder() {
143 | location = new Location("gps");
144 | }
145 | public LocationBuilder provider(String provider) {
146 | location.setProvider(provider);
147 | return this;
148 | }
149 | public LocationBuilder location(LatLng latLng) {
150 | location.setLatitude(latLng.latitude);
151 | location.setLongitude(latLng.longitude);
152 | return this;
153 | }
154 | public LocationBuilder accuracy(float acc) {
155 | location.setAccuracy(acc);
156 | return this;
157 | }
158 | public LocationBuilder defaultBuilder() {
159 | location.reset();
160 | location.setProvider("gps");
161 | location.setAccuracy(8.0f);
162 | location.setSpeed(0.0f);
163 | location.setBearing(0.0f);
164 | location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
165 | location.setTime(System.currentTimeMillis());
166 | return this;
167 | }
168 | public Location build() {
169 | return location;
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/MockProviderFaker.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker;
2 |
3 | import android.content.ContentResolver;
4 | import android.location.Location;
5 | import android.os.Build;
6 | import android.provider.Settings;
7 | import android.util.Log;
8 |
9 | import java.util.Arrays;
10 | import java.util.HashSet;
11 | import java.util.Set;
12 |
13 | import de.robv.android.xposed.IXposedHookLoadPackage;
14 | import de.robv.android.xposed.XC_MethodHook;
15 | import de.robv.android.xposed.XposedBridge;
16 | import de.robv.android.xposed.XposedHelpers;
17 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
18 |
19 | /**
20 | * Created by Jinsen on 16/7/16.
21 | * Mock by add mock providers
22 | */
23 | public class MockProviderFaker implements IXposedHookLoadPackage {
24 |
25 | private static final Set mPackageSet = new HashSet() {{
26 | add("com.nianticlabs.pokemongo");
27 | }};
28 | private static final String TAG = MockProviderFaker.class.getSimpleName();
29 |
30 | @Override
31 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
32 | preventGetMockLocationSetting(lpparam);
33 | preventCheckFromMockProvider(lpparam);
34 | // dumpLocation(lpparam);
35 | }
36 |
37 | private void dumpLocation(XC_LoadPackage.LoadPackageParam lpparam) {
38 | if (lpparam.packageName.equals("com.nianticlabs.pokemongo")) {
39 | XposedHelpers.findAndHookMethod(
40 | "com.nianticlabs.nia.location.NianticLocationManager", /* class name*/
41 | lpparam.classLoader,
42 | "locationUpdate", /* method name */
43 | Location.class, /* params list */
44 | int[].class,
45 | new XC_MethodHook() {
46 | @Override
47 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
48 | Location location = ((Location) param.args[0]);
49 | int[] status = ((int[]) param.args[1]);
50 | if (location != null) {
51 | XposedBridge.log(location.toString());
52 | if (location.getExtras() != null) {
53 | XposedBridge.log(location.getExtras().toString());
54 | }
55 | } else {
56 | XposedBridge.log("location == null");
57 | }
58 | XposedBridge.log("status = " + Arrays.toString(status));
59 | }
60 | });
61 | }
62 | }
63 |
64 | private void preventGetMockLocationSetting(XC_LoadPackage.LoadPackageParam lpparam) {
65 | if (!mPackageSet.contains(lpparam.packageName)) return;
66 | // hook Settings.Secure.getInt(ContentResolver, String)
67 | XposedHelpers.findAndHookMethod(
68 | Settings.Secure.class,
69 | "getInt",
70 | ContentResolver.class,
71 | String.class,
72 | new XC_MethodHook() {
73 | @Override
74 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
75 | String name = ((String) param.args[1]);
76 | if (name.equals(Settings.Secure.ALLOW_MOCK_LOCATION)) {
77 | Log.d(TAG, "Bingo, find its calling getInt");
78 | param.setResult(0);
79 | }
80 | }
81 | });
82 | // hook Settings.Secure.getString(ContentResolver, String)
83 | XposedHelpers.findAndHookMethod(
84 | Settings.Secure.class,
85 | "getString",
86 | ContentResolver.class,
87 | String.class,
88 | new XC_MethodHook() {
89 | @Override
90 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
91 | String name = ((String) param.args[1]);
92 | if (name.equals(Settings.Secure.ALLOW_MOCK_LOCATION)) {
93 | Log.d(TAG, "Bingo, find its calling getString");
94 | param.setResult("0");
95 | }
96 | }
97 | });
98 | // hook Settings.Secure.getStringForUser(ContentResolver, String, int)
99 | if (Build.VERSION.SDK_INT >= 17) {
100 | XposedHelpers.findAndHookMethod(
101 | Settings.Secure.class,
102 | "getStringForUser",
103 | ContentResolver.class,
104 | String.class,
105 | int.class,
106 | new XC_MethodHook() {
107 | @Override
108 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
109 | String name = ((String) param.args[1]);
110 | if (name.equals(Settings.Secure.ALLOW_MOCK_LOCATION)) {
111 | Log.d(TAG, "Bingo, find its calling getStringForUser");
112 | param.setResult("0");
113 | }
114 | }
115 | });
116 | }
117 | }
118 |
119 | private void preventCheckFromMockProvider(final XC_LoadPackage.LoadPackageParam lpparam) {
120 | if (Build.VERSION.SDK_INT >= 18) {
121 | // hook Location.isFromMockProvider()
122 | XposedHelpers.findAndHookMethod(
123 | Location.class,
124 | "isFromMockProvider",
125 | new XC_MethodHook() {
126 | @Override
127 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
128 | XposedHelpers.setBooleanField(param.thisObject, "mIsFromMockProvider", false);
129 | Log.d(TAG, param.thisObject.getClass().getName() + "is calling from mock provider");
130 | param.setResult(false);
131 | }
132 | });
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/service/LocationService.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app.service;
2 |
3 | import android.app.ActivityManager;
4 | import android.app.Notification;
5 | import android.app.PendingIntent;
6 | import android.app.Service;
7 | import android.content.Intent;
8 | import android.content.SharedPreferences;
9 | import android.graphics.PixelFormat;
10 | import android.location.LocationManager;
11 | import android.os.Build;
12 | import android.os.Handler;
13 | import android.os.HandlerThread;
14 | import android.os.IBinder;
15 | import android.os.Parcelable;
16 | import android.text.TextUtils;
17 | import android.view.Gravity;
18 | import android.view.WindowManager;
19 |
20 | import com.github.jinsen47.pokefaker.R;
21 | import com.github.jinsen47.pokefaker.app.ui.DirectionLayout;
22 | import com.github.jinsen47.pokefaker.app.event.MapPickEvent;
23 | import com.github.jinsen47.pokefaker.app.ui.MainActivity;
24 | import com.google.android.gms.maps.model.LatLng;
25 | import com.jaredrummler.android.processes.AndroidProcesses;
26 | import com.jaredrummler.android.processes.models.AndroidAppProcess;
27 |
28 | import org.greenrobot.eventbus.EventBus;
29 | import org.greenrobot.eventbus.Subscribe;
30 |
31 | import java.util.ArrayList;
32 | import java.util.List;
33 |
34 | public class LocationService extends Service {
35 | private static final String TAG = LocationService.class.getSimpleName();
36 | private static final long CHECK_INTERVAL = 2000;
37 | private static final String POKEMON_PACKAGE = "com.nianticlabs.pokemongo";
38 | private Intent mIntent;
39 | private DirectionLayout mDirectionLayout;
40 | private DirectionLayout.onDirectionLayoutListener mDirectionListener;
41 | private HandlerThread mHandlerThread = new HandlerThread("dpad_service");
42 | private Handler mHandler;
43 | private LatLng mCurrentLatLng;
44 | private List mMockProviders = new ArrayList<>();
45 | private boolean hasWindowAdded = false;
46 | private WindowManager windowManager;
47 | private WindowManager.LayoutParams wmParams=null;
48 |
49 | public LocationService() {
50 | }
51 |
52 | @Override
53 | public void onCreate() {
54 | super.onCreate();
55 | mDirectionLayout = new DirectionLayout(this);
56 |
57 | mMockProviders.add(new MockProvider(this, LocationManager.GPS_PROVIDER));
58 | mMockProviders.add(new MockProvider(this, LocationManager.NETWORK_PROVIDER));
59 |
60 | setListener(mDirectionLayout);
61 |
62 | EventBus.getDefault().register(this);
63 |
64 | mCurrentLatLng = new LatLng(0, 0);
65 | fetchSavedLocation();
66 | LocationHolder.getInstance(this).start();
67 |
68 | mHandlerThread.start();
69 | mHandler = new Handler(mHandlerThread.getLooper());
70 | mHandler.postDelayed(new Runnable() {
71 | @Override
72 | public void run() {
73 | if (isPokemonRunning()) {
74 | if (!hasWindowAdded) {
75 | addAlertWindow();
76 | hasWindowAdded = true;
77 | }
78 | } else {
79 | if (hasWindowAdded) {
80 | removeAlertWindow();
81 | hasWindowAdded = false;
82 | }
83 | }
84 | mHandler.postDelayed(this, CHECK_INTERVAL);
85 | }
86 | }, CHECK_INTERVAL);
87 |
88 | Notification notification = new Notification.Builder(this)
89 | .setContentTitle(getString(R.string.service_running_title))
90 | .setSmallIcon(R.mipmap.ic_launcher)
91 | .setContentText(getString(R.string.service_running_content))
92 | .setAutoCancel(false)
93 | .setOngoing(true)
94 | .build();
95 | Intent notificationIntent = new Intent(this, MainActivity.class);
96 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
97 | notification.contentIntent = pendingIntent;
98 | startForeground(1, notification);
99 |
100 | // TODO: 16/7/13 refactor this ugly impl
101 | updateLocation();
102 | for (MockProvider m: mMockProviders) {
103 | m.start();
104 | }
105 | }
106 |
107 | private void fetchSavedLocation() {
108 | SharedPreferences mSp = getSharedPreferences("location", MODE_PRIVATE);
109 | String latitudeString = mSp.getString("latitude", null);
110 | String longitudeString = mSp.getString("longitude", null);
111 | double latitude = 0.0d;
112 | double longitude = 0.0d;
113 | if (!TextUtils.isEmpty(latitudeString)) {
114 | latitude = Double.parseDouble(latitudeString);
115 | }
116 | if (!TextUtils.isEmpty(longitudeString)) {
117 | longitude = Double.parseDouble(longitudeString);
118 | }
119 | mCurrentLatLng = new LatLng(latitude, longitude);
120 | }
121 |
122 | private boolean isPokemonRunning() {
123 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
124 | ActivityManager am = ((ActivityManager) getSystemService(ACTIVITY_SERVICE));
125 | List list = am.getRunningAppProcesses();
126 | if (list != null && !list.isEmpty()) {
127 | if (list.get(0).processName.equals(POKEMON_PACKAGE)) {
128 | return true;
129 | } else {
130 | return false;
131 | }
132 | }
133 | } else {
134 | List list = AndroidProcesses.getRunningAppProcesses();
135 | if (list != null && !list.isEmpty()) {
136 | for (AndroidAppProcess p : list) {
137 | if (p.getPackageName().equals(POKEMON_PACKAGE) && p.foreground) {
138 | return true;
139 | }
140 | }
141 | return false;
142 | }
143 | }
144 | return false;
145 | }
146 | private WindowManager.LayoutParams createLayoutParams(){
147 | WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
148 | WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
149 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
150 | PixelFormat.TRANSLUCENT);
151 |
152 | lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
153 | lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
154 | lp.gravity = Gravity.START | Gravity.TOP;
155 | fetchWindowPosition(lp);
156 | return lp;
157 | }
158 |
159 | private void fetchWindowPosition(WindowManager.LayoutParams lp) {
160 | SharedPreferences mSp = getSharedPreferences("default", MODE_PRIVATE);
161 | int x = mSp.getInt("window_x", 0);
162 | int y = mSp.getInt("window_y", 0);
163 | lp.x = x;
164 | lp.y = y;
165 | if (y == 0) {
166 | lp.y = (int) (100 * getResources().getDisplayMetrics().density);
167 | }
168 | }
169 |
170 | private void addAlertWindow() {
171 | windowManager = ((WindowManager) getSystemService(WINDOW_SERVICE));
172 | wmParams = createLayoutParams();
173 | windowManager.addView(mDirectionLayout, wmParams);
174 | }
175 |
176 | private void removeAlertWindow() {
177 | WindowManager windowManager = ((WindowManager) getSystemService(WINDOW_SERVICE));
178 | windowManager.removeView(mDirectionLayout);
179 | }
180 |
181 | @Override
182 | public void onDestroy() {
183 | super.onDestroy();
184 | EventBus.getDefault().unregister(this);
185 | LocationHolder.getInstance(this).stop();
186 | for (MockProvider m : mMockProviders) {
187 | m.remove();
188 | }
189 | mHandler.removeCallbacks(null);
190 | }
191 |
192 | @Subscribe
193 | public void onPickEvent(MapPickEvent e) {
194 | mCurrentLatLng = e.latLng;
195 | updateLocation();
196 | }
197 |
198 | private void updateLocation() {
199 | LocationHolder.getInstance(this).postLocation(mCurrentLatLng);
200 | }
201 |
202 | private void setListener(DirectionLayout layout) {
203 | mDirectionListener = new DirectionLayout.onDirectionLayoutListener() {
204 | @Override
205 | public void onDirection(double radian, double zoom) {
206 | move(mCurrentLatLng, radian, zoom);
207 | }
208 |
209 | @Override
210 | public WindowManager.LayoutParams getLayoutParams() {
211 | return wmParams;
212 | }
213 | };
214 | layout.setDirectionLayoutListener(mDirectionListener);
215 | }
216 |
217 | private void move(LatLng ori, double angle, double power) {
218 | double BASE = 0.00001 * power;
219 | double latitude = ori.latitude;
220 | double longitude = ori.longitude;
221 | latitude += BASE * Math.sin(angle);
222 | longitude += BASE * Math.cos(angle);
223 | mCurrentLatLng = new LatLng(latitude, longitude);
224 | updateLocation();
225 | }
226 |
227 | @Override
228 | public IBinder onBind(Intent intent) {
229 | return null;
230 | }
231 |
232 | @Override
233 | public int onStartCommand(Intent intent, int flags, int startId) {
234 | mIntent = intent;
235 | Parcelable p = intent.getParcelableExtra("position");
236 | if (p != null) {
237 | mCurrentLatLng = ((LatLng) p);
238 | updateLocation();
239 | }
240 | return super.onStartCommand(intent, flags, startId);
241 | }
242 |
243 | }
244 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/util/PermissionUtil.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app.util;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.app.AppOpsManager;
6 | import android.content.Context;
7 | import android.content.DialogInterface;
8 | import android.content.Intent;
9 | import android.content.pm.PackageManager;
10 | import android.net.Uri;
11 | import android.provider.Settings;
12 | import android.support.v7.app.AlertDialog;
13 |
14 | import com.github.jinsen47.pokefaker.R;
15 | import com.github.jinsen47.pokefaker.app.Constants;
16 |
17 | import java.lang.reflect.InvocationTargetException;
18 | import java.lang.reflect.Method;
19 |
20 | /**
21 | * Created by Jinsen on 16/7/17.
22 | */
23 | public class PermissionUtil {
24 | public static final int PERMISSION_REQUEST_PHONE = 0;
25 | public static final int PERMISSION_REQUEST_CAMERA = 1;
26 | public static final int PERMISSION_REQUEST_SYSTEM_ALERT_WINDOW = 2;
27 | public static final int PERMISSION_REQUEST_MOCK_LOCATION = 3;
28 |
29 | /**
30 | *
31 | * @param context
32 | * @return true is mock setting being requested, or false if permission allowed/granted
33 | */
34 | public static boolean checkAndRequestMockSetting(final Context context) {
35 | boolean isGranted = false;
36 | try {
37 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
38 | AppOpsManager opsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
39 | isGranted = (opsManager.checkOpNoThrow(AppOpsManager.OPSTR_MOCK_LOCATION, android.os.Process.myUid(), Constants.PACKAGE_POKEFAKER) == AppOpsManager.MODE_ALLOWED);
40 | } else {
41 | int ret = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION);
42 | isGranted = (ret == 1);
43 | }
44 | } catch (Settings.SettingNotFoundException e) {
45 | isGranted = false;
46 | } finally {
47 | if (!isGranted) {
48 | new AlertDialog.Builder(context)
49 | .setTitle(android.R.string.dialog_alert_title)
50 | .setMessage(context.getString(R.string.need_permission_message, context.getString(R.string.permission_display_mock_location)))
51 | .setPositiveButton(R.string.jump_setting, new DialogInterface.OnClickListener() {
52 | @Override
53 | public void onClick(DialogInterface dialog, int which) {
54 | Intent i = new Intent();
55 | i.setData(Uri.parse("package:" + Constants.PACKAGE_POKEFAKER));
56 | PackageManager pm = context.getPackageManager();
57 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
58 | i.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
59 | } else {
60 | i.setClassName("com.android.settings", "com.android.settings.DevelopmentSettings");
61 | }
62 | if (!pm.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
63 | context.startActivity(i);
64 | }
65 | }
66 | })
67 | .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
68 | @Override
69 | public void onClick(DialogInterface dialog, int which) {
70 | dialog.dismiss();
71 | }
72 | })
73 | .create().show();
74 | return true;
75 | } else {
76 | return false;
77 | }
78 | }
79 | }
80 |
81 | /**
82 | *
83 | * @param activity
84 | * @return true is pop-up window permission requested, or false if permission allowed/granted
85 | */
86 | public static boolean checkAndRequestPopupWindowPermission(final Activity activity) {
87 | boolean isGranted = false;
88 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
89 | isGranted = Settings.canDrawOverlays(activity);
90 | if (!isGranted) {
91 | final Intent i = new Intent();
92 | if (Build.IS_MIUI) {
93 | i.setAction("miui.intent.action.APP_PERM_EDITOR");
94 | i.putExtra("extra_pkgname", activity.getPackageName());
95 | } else {
96 | i.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
97 | i.setData(Uri.parse("package:" + Constants.PACKAGE_POKEFAKER));
98 | }
99 | new AlertDialog.Builder(activity)
100 | .setTitle(android.R.string.dialog_alert_title)
101 | .setMessage(activity.getString(R.string.need_permission_message, activity.getString(R.string.permission_display_window)))
102 | .setPositiveButton(R.string.jump_setting, new DialogInterface.OnClickListener() {
103 | @Override
104 | public void onClick(DialogInterface dialog, int which) {
105 | activity.startActivity(i);
106 | }
107 | })
108 | .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
109 | @Override
110 | public void onClick(DialogInterface dialog, int which) {
111 | dialog.dismiss();
112 | }
113 | }).create().show();
114 | }
115 | } else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
116 | isGranted = !checkAndRequestPermission(activity, Manifest.permission.SYSTEM_ALERT_WINDOW, PERMISSION_REQUEST_SYSTEM_ALERT_WINDOW);
117 | } else {
118 | isGranted = false;
119 | }
120 | return !isGranted;
121 | }
122 |
123 | /**
124 | * @param activity
125 | * @param permission
126 | * @param requestCode
127 | * @return true if permission being requested, or false if permission allowed/granted
128 | */
129 | public static boolean checkAndRequestPermission(final Activity activity, String permission, int requestCode) {
130 | boolean isGranted = false;
131 | Permission per = Permission.findByName(permission);
132 | if (per == null) {
133 | // Means cant find this permission, take it as granted
134 | return false;
135 | }
136 |
137 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
138 | int p = activity.checkSelfPermission(permission);
139 | if (p != PackageManager.PERMISSION_GRANTED) {
140 | activity.requestPermissions(new String[]{permission}, requestCode);
141 | isGranted = false;
142 | } else {
143 | isGranted = true;
144 | }
145 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
146 | AppOpsManager appOpsManager = (AppOpsManager) activity.getSystemService(Context.APP_OPS_SERVICE);
147 | int result = checkOp(appOpsManager, per.opName);
148 | switch (result) {
149 | case AppOpsManager.MODE_ALLOWED:
150 | isGranted = true;
151 | break;
152 | default:
153 | isGranted = false;
154 | break;
155 | }
156 | }
157 | final Intent i = new Intent();
158 | if (Build.IS_MIUI) {
159 | i.setAction("miui.intent.action.APP_PERM_EDITOR");
160 | i.putExtra("extra_pkgname", activity.getPackageName());
161 | } else {
162 | i.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
163 | i.setData(Uri.parse("package:" + Constants.PACKAGE_POKEFAKER));
164 | }
165 | if (!isGranted) {
166 | String message = activity.getString(R.string.need_permission_message, activity.getString(per.displayRes));
167 | new AlertDialog.Builder(activity)
168 | .setTitle(android.R.string.dialog_alert_title)
169 | .setMessage(message)
170 | .setPositiveButton(R.string.jump_setting, new DialogInterface.OnClickListener() {
171 | @Override
172 | public void onClick(DialogInterface dialog, int which) {
173 | activity.startActivity(i);
174 | dialog.dismiss();
175 | }
176 | })
177 | .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
178 | @Override
179 | public void onClick(DialogInterface dialog, int which) {
180 | dialog.dismiss();
181 | }
182 | }).show();
183 | }
184 | return !isGranted;
185 | }
186 |
187 | private static int checkOp(AppOpsManager manager, String operation) {
188 | try {
189 | Class> clz = AppOpsManager.class;
190 | Class[] params = {int.class, int.class, String.class};
191 | Method method = clz.getMethod("checkOp", params);
192 | int op = clz.getField(operation).getInt(null);
193 | Object[] arguments = {op, android.os.Process.myUid(), Constants.PACKAGE_POKEFAKER};
194 | return (int) method.invoke(manager, arguments);
195 | } catch (InvocationTargetException e) {
196 | e.printStackTrace();
197 | } catch (NoSuchMethodException e) {
198 | e.printStackTrace();
199 | } catch (NoSuchFieldException e) {
200 | e.printStackTrace();
201 | } catch (IllegalAccessException e) {
202 | e.printStackTrace();
203 | }
204 | return AppOpsManager.MODE_IGNORED;
205 | }
206 |
207 | private enum Permission {
208 | // Permission for Android 5.0/5.1
209 | CAMERA(Manifest.permission.CAMERA, "OP_CAMERA", R.string.permission_display_camera),
210 | WINDOW(Manifest.permission.SYSTEM_ALERT_WINDOW, "OP_SYSTEM_ALERT_WINDOW", R.string.permission_display_window);
211 |
212 | public final String name;
213 | public final String opName;
214 | public final int displayRes;
215 |
216 | Permission(String name, String opName, int displayRes) {
217 | this.name = name;
218 | this.opName = opName;
219 | this.displayRes = displayRes;
220 | }
221 |
222 | public static Permission findByName(String permission) {
223 | for (Permission p : Permission.values()) {
224 | if (p.name.equals(permission)) {
225 | return p;
226 | }
227 | }
228 | return null;
229 | }
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/app/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker.app.ui;
2 |
3 | import android.content.Context;
4 | import android.content.DialogInterface;
5 | import android.content.Intent;
6 | import android.content.SharedPreferences;
7 | import android.database.DataSetObserver;
8 | import android.graphics.drawable.Drawable;
9 | import android.os.Bundle;
10 | import android.os.Handler;
11 | import android.os.Process;
12 | import android.support.v4.content.ContextCompat;
13 | import android.support.v4.graphics.drawable.DrawableCompat;
14 | import android.support.v7.app.AlertDialog;
15 | import android.support.v7.app.AppCompatActivity;
16 | import android.support.v7.widget.ActionMenuView;
17 | import android.text.TextUtils;
18 | import android.util.Log;
19 | import android.view.Menu;
20 | import android.view.MenuItem;
21 | import android.view.View;
22 | import android.widget.ImageView;
23 | import android.widget.Toast;
24 |
25 | import com.github.clans.fab.FloatingActionButton;
26 | import com.github.clans.fab.FloatingActionMenu;
27 | import com.github.jinsen47.pokefaker.R;
28 | import com.github.jinsen47.pokefaker.app.event.MapPickEvent;
29 | import com.github.jinsen47.pokefaker.app.service.LocationHolder;
30 | import com.github.jinsen47.pokefaker.app.service.LocationService;
31 | import com.github.jinsen47.pokefaker.app.util.PermissionUtil;
32 | import com.github.jinsen47.pokefaker.app.util.StateCheckUtil;
33 | import com.google.android.gms.maps.CameraUpdateFactory;
34 | import com.google.android.gms.maps.GoogleMap;
35 | import com.google.android.gms.maps.OnMapReadyCallback;
36 | import com.google.android.gms.maps.SupportMapFragment;
37 | import com.google.android.gms.maps.UiSettings;
38 | import com.google.android.gms.maps.model.CameraPosition;
39 | import com.google.android.gms.maps.model.LatLng;
40 | import com.google.android.gms.maps.model.Marker;
41 | import com.google.android.gms.maps.model.MarkerOptions;
42 | import com.mypopsy.drawable.SearchArrowDrawable;
43 | import com.mypopsy.drawable.ToggleDrawable;
44 | import com.mypopsy.drawable.util.Bezier;
45 | import com.mypopsy.widget.FloatingSearchView;
46 | import com.mypopsy.widget.internal.ViewUtils;
47 |
48 | import org.greenrobot.eventbus.EventBus;
49 |
50 | public class MainActivity extends AppCompatActivity implements OnMapReadyCallback, GoogleMap.OnMapClickListener, View.OnClickListener{
51 |
52 | private static final String TAG = MainActivity.class.getSimpleName();
53 | private static final int ICON_SEARCH = 1;
54 | private static final int ICON_DRAWER = 2;
55 |
56 | private SupportMapFragment mMapFragment;
57 | private ImageView mZoomIn;
58 | private ImageView mZoomOut;
59 | private FloatingSearchView mSearchView;
60 | private FloatingActionButton mShareButton;
61 | private FloatingActionButton mGoButton;
62 | private FloatingActionMenu mMenu;
63 | private FloatingActionButton mAboutButton;
64 |
65 | private GoogleMap mMap;
66 | private MarkerOptions mMarkerOpts;
67 | private Marker mMarker;
68 |
69 | private SharedPreferences mSp;
70 | private boolean isServiceRunning = false;
71 |
72 | private AlertDialog mQuitDialog;
73 |
74 | private Handler mHandler;
75 |
76 | private DataSetObserver mLocationChangeObserver = new DataSetObserver() {
77 | @Override
78 | public void onChanged() {
79 | LocationHolder holder = LocationHolder.getInstance(MainActivity.this);
80 | LatLng l = holder.pollLatLng();
81 | if (l != null &&
82 | mMarker != null &&
83 | !l.equals(mMarker.getPosition())) {
84 | mMarker.setPosition(l);
85 | }
86 | }
87 | };
88 | @Override
89 | protected void onCreate(Bundle savedInstanceState) {
90 | super.onCreate(savedInstanceState);
91 | setContentView(R.layout.activity_main);
92 |
93 | if (getSupportActionBar() != null) {
94 | getSupportActionBar().hide();
95 | }
96 |
97 | mMapFragment = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map));
98 | mZoomIn = ((ImageView) findViewById(R.id.icon_zoom_in));
99 | mZoomOut = ((ImageView) findViewById(R.id.icon_zoom_out));
100 | mSearchView = ((FloatingSearchView) findViewById(R.id.view_search));
101 | mShareButton = ((FloatingActionButton) findViewById(R.id.fab_share));
102 | mGoButton = ((FloatingActionButton) findViewById(R.id.fab_start));
103 | mAboutButton = ((FloatingActionButton) findViewById(R.id.fab_about));
104 | mMenu = ((FloatingActionMenu) findViewById(R.id.fab_menu));
105 |
106 | mMapFragment.getMapAsync(this);
107 | mZoomIn.setOnClickListener(this);
108 | mZoomOut.setOnClickListener(this);
109 |
110 | mSp = getSharedPreferences("location", MODE_PRIVATE);
111 | mQuitDialog = new AlertDialog.Builder(MainActivity.this)
112 | .setTitle(getString(android.R.string.dialog_alert_title))
113 | .setMessage(getString(R.string.quit))
114 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
115 | @Override
116 | public void onClick(DialogInterface dialog, int which) {
117 | stopService(new Intent(MainActivity.this, LocationService.class));
118 | Process.killProcess(Process.myPid());
119 | }
120 | })
121 | .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
122 | @Override
123 | public void onClick(DialogInterface dialog, int which) {
124 | dialog.dismiss();
125 | }
126 | })
127 | .create();
128 | updateNavigationIcon(ICON_SEARCH);
129 | mSearchView.showLogo(true);
130 | mSearchView.getMenu().findItem(R.id.menu_clear).setVisible(false);
131 | mSearchView.setOnSearchListener(mSearchViewListener);
132 | mSearchView.setOnIconClickListener(mSearchViewListener);
133 | mSearchView.setOnSearchFocusChangedListener(mSearchViewListener);
134 | mSearchView.setOnMenuItemClickListener(mSearchViewListener);
135 |
136 | mShareButton.setOnClickListener(this);
137 | mAboutButton.setOnClickListener(this);
138 | mGoButton.setOnClickListener(this);
139 |
140 | mHandler = new Handler();
141 | }
142 |
143 | private void updateNavigationIcon(int itemId) {
144 | Context context = mSearchView.getContext();
145 | Drawable drawable = null;
146 |
147 | switch(itemId) {
148 | case ICON_SEARCH:
149 | drawable = new SearchArrowDrawable(context);
150 | break;
151 | case ICON_DRAWER:
152 | drawable = new android.support.v7.graphics.drawable.DrawerArrowDrawable(context);
153 | break;
154 | }
155 | drawable = DrawableCompat.wrap(drawable);
156 | DrawableCompat.setTint(drawable, ViewUtils.getThemeAttrColor(this, R.attr.colorControlNormal));
157 | mSearchView.setIcon(drawable);
158 | }
159 |
160 | @Override
161 | protected void onStart() {
162 | super.onStart();
163 | isServiceRunning = StateCheckUtil.isLocationServiceRunning(this, LocationService.class);
164 | LocationHolder.getInstance(this).registerObserver(mLocationChangeObserver);
165 | }
166 |
167 | @Override
168 | protected void onStop() {
169 | super.onStop();
170 | LocationHolder.getInstance(this).unregisterObserver(mLocationChangeObserver);
171 | }
172 |
173 | @Override
174 | public void onBackPressed() {
175 | mQuitDialog.show();
176 | }
177 |
178 | private void fetchSavedLocation() {
179 | String latitudeString = mSp.getString("latitude", null);
180 | String longitudeString = mSp.getString("longitude", null);
181 | double latitude = 0.0d;
182 | double longitude = 0.0d;
183 | if (!TextUtils.isEmpty(latitudeString)) {
184 | latitude = Double.parseDouble(latitudeString);
185 | }
186 | if (!TextUtils.isEmpty(longitudeString)) {
187 | longitude = Double.parseDouble(longitudeString);
188 | }
189 | mMarker.setPosition(new LatLng(latitude, longitude));
190 | }
191 |
192 | @Override
193 | public void onMapReady(GoogleMap googleMap) {
194 | if (mMap == null) {
195 | mMap = googleMap;
196 | setMap(mMap);
197 | }
198 |
199 | mMarkerOpts = new MarkerOptions().position(new LatLng(0, 0));
200 | mMarker = googleMap.addMarker(mMarkerOpts);
201 | fetchSavedLocation();
202 | if (!(mMarker.getPosition().latitude == 0.0 && mMarker.getPosition().longitude == 0.0)) {
203 | moveCamera(true);
204 | }
205 | }
206 |
207 | private void setMap(GoogleMap map) {
208 | float density = getResources().getDisplayMetrics().density;
209 | map.setPadding(0, ((int) (82* density)), 0, 0);
210 | map.setOnMapClickListener(this);
211 | map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
212 | map.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
213 | @Override
214 | public boolean onMarkerClick(Marker marker) {
215 | return true;
216 | }
217 | });
218 | }
219 |
220 | private void moveCamera(boolean animate) {
221 | CameraPosition cp = new CameraPosition.Builder().target(mMarker.getPosition())
222 | .zoom(15.5f)
223 | .bearing(0)
224 | .tilt(25)
225 | .build();
226 | if (animate) {
227 | mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cp));
228 | } else {
229 | mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cp));
230 | }
231 | }
232 |
233 | @Override
234 | public void onMapClick(LatLng latLng) {
235 | mMarker.setPosition(latLng);
236 | postLocation(latLng);
237 | }
238 |
239 | private void postLocation(LatLng latlng) {
240 | EventBus.getDefault().post(new MapPickEvent(latlng));
241 | }
242 |
243 | @Override
244 | public void onClick(View v) {
245 | switch (v.getId()) {
246 | case R.id.icon_zoom_in:
247 | mMap.animateCamera(CameraUpdateFactory.zoomIn());
248 | break;
249 | case R.id.icon_zoom_out:
250 | mMap.animateCamera(CameraUpdateFactory.zoomOut());
251 | break;
252 | case R.id.fab_about:
253 | startActivity(new Intent(this, AboutActivity.class));
254 | mMenu.close(false);
255 | break;
256 | case R.id.fab_share:
257 | Intent intent = new Intent(Intent.ACTION_SEND);
258 | intent.setType("text/plain");
259 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
260 | intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_title));
261 | intent.putExtra(Intent.EXTRA_TEXT, mMarker.getPosition().latitude + "," + mMarker.getPosition().longitude);
262 | startActivity(Intent.createChooser(intent, "Share with"));
263 | break;
264 | case R.id.fab_start:
265 | if (PermissionUtil.checkAndRequestPopupWindowPermission(this)) break;
266 | if (PermissionUtil.checkAndRequestMockSetting(this)) break;
267 |
268 | if (isServiceRunning) {
269 | stopService(new Intent(this, LocationService.class));
270 | mGoButton.setColorNormal(ContextCompat.getColor(this, R.color.colorStopped));
271 | mGoButton.setImageResource(R.drawable.ic_send);
272 | isServiceRunning = false;
273 | } else {
274 | Intent i = new Intent(this, LocationService.class);
275 | i.putExtra("position", mMarker.getPosition());
276 | startService(i);
277 | postLocation(mMarker.getPosition());
278 | mGoButton.setColorNormal(ContextCompat.getColor(this, R.color.colorRunning));
279 | mGoButton.setImageResource(R.drawable.ic_done);
280 | isServiceRunning = true;
281 | }
282 | break;
283 | }
284 | }
285 |
286 | private static LatLng parseLatlng(String searchText) {
287 | if (!searchText.contains(",")) return null;
288 | String[] positionArray = searchText.split(",");
289 | if (positionArray.length != 2) return null;
290 | try {
291 | double lat = Double.valueOf(positionArray[0]);
292 | double lng = Double.valueOf(positionArray[1]);
293 | return new LatLng(lat, lng);
294 |
295 | } catch (NumberFormatException e) {
296 | return null;
297 | }
298 | }
299 |
300 | private SearchViewListener mSearchViewListener = new SearchViewListener();
301 |
302 | private class SearchViewListener implements FloatingSearchView.OnSearchListener, FloatingSearchView.OnIconClickListener, FloatingSearchView.OnSearchFocusChangedListener, ActionMenuView.OnMenuItemClickListener {
303 |
304 | @Override
305 | public void onNavigationClick() {
306 | mSearchView.setActivated(!mSearchView.isActivated());
307 | }
308 |
309 | @Override
310 | public void onFocusChanged(final boolean focused) {
311 | boolean textEmpty = mSearchView.getText().length() == 0;
312 | mSearchView.getMenu().findItem(R.id.menu_clear).setVisible(focused);
313 | mSearchView.showLogo(!focused && textEmpty);
314 |
315 | if (focused) {
316 | mSearchView.showIcon(true);
317 | }
318 | }
319 |
320 | @Override
321 | public void onSearchAction(CharSequence charSequence) {
322 | LatLng result = parseLatlng(charSequence.toString());
323 | if (result != null) {
324 | mMarker.setPosition(result);
325 | moveCamera(true);
326 | mSearchView.setActivated(false);
327 | postLocation(result);
328 | } else {
329 | Toast.makeText(MainActivity.this, getString(R.string.invalid_input), Toast.LENGTH_SHORT).show();
330 | Log.w(TAG, "invalid input = " + charSequence);
331 | }
332 | }
333 |
334 | @Override
335 | public boolean onMenuItemClick(MenuItem item) {
336 | switch (item.getItemId()) {
337 | case R.id.menu_clear:
338 | mSearchView.setText("");
339 | break;
340 | }
341 | return true;
342 | }
343 | }
344 | }
345 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jinsen47/pokefaker/HookLocationFaker.java:
--------------------------------------------------------------------------------
1 | package com.github.jinsen47.pokefaker;
2 |
3 | import android.content.ContentResolver;
4 | import android.location.GpsSatellite;
5 | import android.location.Location;
6 | import android.location.LocationListener;
7 | import android.provider.Settings;
8 |
9 | import com.github.jinsen47.pokefaker.app.service.LocationHolder;
10 |
11 | import java.lang.reflect.Method;
12 | import java.util.ArrayList;
13 | import java.util.Arrays;
14 | import java.util.List;
15 |
16 | import de.robv.android.xposed.IXposedHookLoadPackage;
17 | import de.robv.android.xposed.XC_MethodHook;
18 | import de.robv.android.xposed.XC_MethodReplacement;
19 | import de.robv.android.xposed.XposedBridge;
20 | import de.robv.android.xposed.XposedHelpers;
21 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
22 |
23 | /**
24 | * Created by Jinsen on 16/7/12.
25 | * This class was some attempted from hooking system, but failed.
26 | * Keep it here for further use.
27 | * @hide
28 | */
29 | public class HookLocationFaker implements IXposedHookLoadPackage {
30 | private Object mNianticLocationManager;
31 | private LocationHolder mLocationHolder;
32 |
33 | private static final String POKEMON_GO = "com.nianticlabs.pokemongo";
34 |
35 | public HookLocationFaker() {
36 | mLocationHolder = LocationHolder.getInstance(null);
37 |
38 | }
39 |
40 | @Override
41 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
42 | dumpLocation(lpparam);
43 | // hook_createProviders(lpparam);
44 | // hook_LocationManager(lpparam);
45 | // hook_gpsStatusUpdate(lpparam);
46 | // hook_ContentProvider_getInt(lpparam);
47 | }
48 |
49 | private void hook_ContentProvider_getInt(XC_LoadPackage.LoadPackageParam lpparam) {
50 | if (!lpparam.packageName.equals(POKEMON_GO)) return;
51 | XposedHelpers.findAndHookMethod(
52 | Settings.Secure.class,
53 | "getInt",
54 | ContentResolver.class,
55 | String.class,
56 | new XC_MethodHook() {
57 | @Override
58 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
59 | String name = ((String) param.args[1]);
60 | if (name.equals(Settings.Secure.ALLOW_MOCK_LOCATION)) {
61 | XposedBridge.log("Bingo, find its calling!");
62 | param.setResult(0);
63 | }
64 | }
65 | });
66 | }
67 |
68 | private void dumpLocation(XC_LoadPackage.LoadPackageParam lpparam) {
69 | if (lpparam.packageName.equals("com.nianticlabs.pokemongo")) {
70 | XposedHelpers.findAndHookMethod(
71 | "com.nianticlabs.nia.location.NianticLocationManager", /* class name*/
72 | lpparam.classLoader,
73 | "locationUpdate", /* method name */
74 | Location.class, /* params list */
75 | int[].class,
76 | new XC_MethodHook() {
77 | @Override
78 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
79 | Location location = ((Location) param.args[0]);
80 | int[] status = ((int[]) param.args[1]);
81 | if (location != null) {
82 | XposedBridge.log(location.toString());
83 | if (location.getExtras() != null) {
84 | XposedBridge.log(location.getExtras().toString());
85 | }
86 | } else {
87 | XposedBridge.log("location == null");
88 | }
89 | XposedBridge.log("status = " + Arrays.toString(status));
90 | }
91 | });
92 |
93 | // XposedHelpers.findAndHookMethod(
94 | // "com.nianticlabs.nia.location.NianticLocationManager",
95 | // lpparam.classLoader,
96 | // "gpsStatusUpdate",
97 | // int.class,
98 | // GpsSatellite[].class,
99 | // new XC_MethodHook() {
100 | // @Override
101 | // protected void afterHookedMethod(MethodHookParam param) throws Throwable {
102 | // super.beforeHookedMethod(param);
103 | // int timeToFix = ((int) param.args[0]);
104 | // GpsSatellite[] satellites = ((GpsSatellite[]) param.args[1]);
105 | // XposedBridge.log("timeToFix = " + timeToFix);
106 | // for (GpsSatellite s : satellites) {
107 | // XposedBridge.log(satelliteToString(s));
108 | // }
109 | // }
110 | // });
111 | }
112 | }
113 |
114 | private void hook_createProviders(XC_LoadPackage.LoadPackageParam lpparam) throws ClassNotFoundException {
115 | if (lpparam.packageName.equals("com.nianticlabs.pokemongo")) {
116 | XposedHelpers.findAndHookMethod(
117 | "com.nianticlabs.nia.location.NianticLocationManager", /* class name*/
118 | lpparam.classLoader,
119 | "createProviders", /* method name */
120 | new XC_MethodHook() {
121 | @Override
122 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
123 | XposedBridge.log("after createProviders ");
124 | List list = ((List) XposedHelpers.getObjectField(param.thisObject, "providers"));
125 | if (list != null) {
126 | XposedBridge.log("provider list = " + list.size());
127 | list.remove(0);
128 | }
129 | if (param.thisObject == null) {
130 | XposedBridge.log("NianticLocatoinManager == null");
131 | } else {
132 | mNianticLocationManager = param.thisObject;
133 | }
134 | }
135 | }
136 | );
137 | }
138 | }
139 |
140 | private void hook_gpsStatusUpdate(XC_LoadPackage.LoadPackageParam lpparam) {
141 | if (!lpparam.packageName.equals("com.nianticlabs.pokemongo")) return;
142 |
143 | XposedHelpers.findAndHookMethod(
144 | "com.nianticlabs.nia.location.NianticLocationManager",
145 | lpparam.classLoader,
146 | "gpsStatusUpdate",
147 | int.class,
148 | GpsSatellite[].class,
149 | new XC_MethodReplacement() {
150 | @Override
151 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
152 | // int timeToFix = ((int) param.args[0]);
153 | // GpsSatellite[] satellites = ((GpsSatellite[]) param.args[1]);
154 | // for (GpsSatellite s: satellites) {
155 | // XposedBridge.log(satelliteToString(s));
156 | // }
157 | Location locationGps = mLocationHolder.pollLocation("gps");
158 | Location locationNetwork = mLocationHolder.pollLocation("network");
159 | call_updateLocation(locationGps);
160 | call_updateLocation(locationNetwork);
161 |
162 | // List fakeSatelliteList = FakeGpsSatellite.fetchGpsSatellites();
163 | // GpsSatellite[] fakeSatellites = new GpsSatellite[fakeSatelliteList.size()];
164 | // for (int i = 0; i < fakeSatelliteList.size(); i++) {
165 | // fakeSatellites[i] = fakeSatelliteList.get(i);
166 | // }
167 | // param.args[1] = fakeSatellites;
168 | return null;
169 | }
170 | }
171 | );
172 | }
173 |
174 | private static String satelliteToString(GpsSatellite s) {
175 | StringBuilder sb = new StringBuilder();
176 | if (s != null) {
177 | sb.append("GpsSatellite[" +
178 | "usedInFix = " + s.usedInFix() + ", " +
179 | "hasAlmanac = " + s.hasAlmanac() + ", " +
180 | "hasEphemeris = " + s.hasEphemeris() + ", " +
181 | "prn = " + s.getPrn() + ", " +
182 | "snr = " + s.getSnr() + ", " +
183 | "elevation = " + s.getElevation() + ", " +
184 | "azimuth = " + s.getAzimuth() + "]");
185 | } else {
186 | sb.append("GpsSatellite[null]");
187 | }
188 | return sb.toString();
189 | }
190 |
191 | private void hook_LocationManager(XC_LoadPackage.LoadPackageParam lpparam) {
192 | if (lpparam.packageName.equals("com.nianticlabs.pokemongo")) {
193 | XposedHelpers.findAndHookMethod(
194 | "android.location.LocationManager",
195 | lpparam.classLoader,
196 | "requestLocationUpdates",
197 | String.class, long.class, float.class, LocationListener.class,
198 | new XC_MethodHook() {
199 | @Override
200 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
201 | if (param.args.length == 4 && (param.args[0] instanceof String)) {
202 |
203 | LocationListener ll = (LocationListener)param.args[3];
204 |
205 | Class> clazz = LocationListener.class;
206 | Method m = null;
207 | for (Method method : clazz.getDeclaredMethods()) {
208 | if (method.getName().equals("onLocationChanged")) {
209 | m = method;
210 | break;
211 | }
212 | }
213 |
214 | try {
215 | if (m != null) {
216 | Location l = mLocationHolder.pollLocation("gps");
217 | Object[] args = new Object[1];
218 |
219 | args[0] = l;
220 |
221 | //invoke onLocationChanged directly to pass location infomation
222 | m.invoke(ll, args);
223 |
224 | XposedBridge.log("fake location: " + l.toString());
225 | }
226 | } catch (Exception e) {
227 | XposedBridge.log(e);
228 | }
229 | }
230 | }
231 | }
232 | );
233 | }
234 | }
235 |
236 |
237 | private void call_updateLocation(Location location) {
238 | XposedBridge.log("HookLocationFaker = " + HookLocationFaker.this.toString());
239 | int[] statusMap = new int[]{3,3,4};
240 | if (mNianticLocationManager == null) {
241 | XposedBridge.log("pokemon param == null");
242 | } else {
243 | XposedBridge.log("posting: " + location.toString());
244 | XposedHelpers.callMethod(mNianticLocationManager,
245 | "locationUpdate",
246 | location, statusMap); /* object list */
247 | }
248 |
249 | }
250 |
251 | private static class FakeGpsSatellite {
252 | boolean mValid;
253 | boolean mHasEphemeris;
254 | boolean mHasAlmanac;
255 | boolean mUsedInFix;
256 | int mPrn;
257 | float mSnr;
258 | float mElevation;
259 | float mAzimuth;
260 |
261 | static FakeGpsSatellite[] staticList = new FakeGpsSatellite[]{
262 | new FakeGpsSatellite(true, true, true, false, 5, 0.0f, 5.0f, 112.0f),
263 | new FakeGpsSatellite(true, true, true, true, 13, 12.4f, 23.0f, 53.0f),
264 | new FakeGpsSatellite(true, true, true, true, 14, 16.9f, 6.0f, 247.0f),
265 | new FakeGpsSatellite(true, true, true, true, 15, 22.1f, 58.0f, 45.0f),
266 | new FakeGpsSatellite(true, true, true, false, 18, 0f, 52.0f, 309.0f),
267 | new FakeGpsSatellite(true, true, true, true, 20, 21.5f, 54.0f, 105.0f),
268 | new FakeGpsSatellite(true, true, true, true, 21, 24.1f, 56.0f, 251.0f),
269 | new FakeGpsSatellite(true, true, true, false, 22, 0f, 14.0f, 299.0f),
270 | new FakeGpsSatellite(true, true, true, true, 24, 25.9f, 57.0f, 157.0f),
271 | new FakeGpsSatellite(true, true, true, true, 27, 18.0f, 3.0f, 309.0f),
272 | new FakeGpsSatellite(true, true, true, true, 28, 18.2f, 3.0f, 42.0f),
273 | new FakeGpsSatellite(true, true, true, false, 41, 18.2f, 3.0f, 0.0f),
274 | new FakeGpsSatellite(true, true, true, false, 50, 29.2f, 0.0f, 0.0f),
275 | new FakeGpsSatellite(true, true, true, true, 67, 14.4f, 2.0f, 92.0f),
276 | new FakeGpsSatellite(true, true, true, true, 68, 21.2f, 45.0f, 60.0f),
277 | new FakeGpsSatellite(true, true, true, true, 69, 17.5f, 50.0f, 330.0f),
278 | new FakeGpsSatellite(true, true, true, true, 70, 22.4f, 7.0f, 291.0f),
279 | new FakeGpsSatellite(true, true, true, true, 77, 23.8f, 10.0f, 23.0f),
280 | new FakeGpsSatellite(true, true, true, true, 78, 18.0f, 47.0f, 70.0f),
281 | new FakeGpsSatellite(true, true, true, true, 79, 22.8f, 41.0f, 142.0f),
282 | new FakeGpsSatellite(true, true, true, true, 83, 20.2f, 9.0f, 212.0f),
283 | new FakeGpsSatellite(true, true, true, true, 84, 16.7f, 30.0f, 264.0f),
284 | new FakeGpsSatellite(true, true, true, true, 85, 12.1f, 20.0f, 317.0f),
285 | };
286 |
287 | static List cacheGpsSatelliteList;
288 |
289 | public FakeGpsSatellite(boolean mValid, boolean mHasEphemeris, boolean mHasAlmanac, boolean mUsedInFix, int mPrn, float mSnr, float mElevation, float mAzimuth) {
290 | this.mValid = mValid;
291 | this.mHasEphemeris = mHasEphemeris;
292 | this.mHasAlmanac = mHasAlmanac;
293 | this.mUsedInFix = mUsedInFix;
294 | this.mPrn = mPrn;
295 | this.mSnr = mSnr;
296 | this.mElevation = mElevation;
297 | this.mAzimuth = mAzimuth;
298 | }
299 |
300 | public static List fetchGpsSatellites() {
301 | if (cacheGpsSatelliteList == null) {
302 | cacheGpsSatelliteList = new ArrayList<>();
303 | for (FakeGpsSatellite fake: staticList) {
304 | GpsSatellite real = ((GpsSatellite) XposedHelpers.newInstance(GpsSatellite.class, fake.mPrn));
305 | XposedHelpers.setBooleanField(real, "mValid", fake.mValid);
306 | XposedHelpers.setBooleanField(real, "mUsedInFix", fake.mUsedInFix);
307 | XposedHelpers.setBooleanField(real, "mHasAlmanac", fake.mHasAlmanac);
308 | XposedHelpers.setBooleanField(real, "mHasEphemeris", fake.mHasEphemeris);
309 | XposedHelpers.setFloatField(real, "mSnr", fake.mSnr);
310 | XposedHelpers.setFloatField(real, "mElevation", fake.mElevation);
311 | XposedHelpers.setFloatField(real, "mAzimuth", fake.mAzimuth);
312 | cacheGpsSatelliteList.add(real);
313 | }
314 | }
315 | return cacheGpsSatelliteList;
316 |
317 | }
318 | }
319 |
320 | }
321 |
--------------------------------------------------------------------------------
/library/src/main/java/com/gcssloop/widget/RockerView.java:
--------------------------------------------------------------------------------
1 | package com.gcssloop.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.graphics.PixelFormat;
10 | import android.graphics.Point;
11 | import android.graphics.PorterDuff;
12 | import android.graphics.Rect;
13 | import android.graphics.drawable.BitmapDrawable;
14 | import android.graphics.drawable.ColorDrawable;
15 | import android.graphics.drawable.Drawable;
16 | import android.os.Handler;
17 | import android.os.HandlerThread;
18 | import android.support.annotation.NonNull;
19 | import android.util.AttributeSet;
20 | import android.util.Log;
21 | import android.view.MotionEvent;
22 | import android.view.SurfaceHolder;
23 | import android.view.SurfaceView;
24 | import android.view.View;
25 |
26 | import com.gcssloop.rocker.R;
27 | import com.gcssloop.view.utils.DensityUtils;
28 | import com.gcssloop.view.utils.MathUtils;
29 |
30 | /**
31 | * A custom view for game or others.
32 | *
33 | * Author: GcsSloop
34 | * Created Date: 16/5/24
35 | * Copyright (C) 2016 GcsSloop.
36 | * GitHub: https://github.com/GcsSloop
37 | */
38 | public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.Callback {
39 |
40 | private static final String TAG = RockerView.class.getSimpleName();
41 | private static int DEFAULT_AREA_RADIUS = 75;
42 | private static int DEFAULT_ROCKER_RADIUS = 25;
43 |
44 | private static int DEFAULT_AREA_COLOR = Color.CYAN;
45 | private static int DEFAULT_ROCKER_COLOR = Color.RED;
46 |
47 | private static int DEFAULT_REFRESH_CYCLE = 30;
48 |
49 |
50 | private SurfaceHolder mHolder;
51 | private HandlerThread mViewThead;
52 | private HandlerThread mHandlerThread;
53 | private Handler mHandler;
54 | private boolean drawOk = true;
55 |
56 | private Paint mPaint;
57 |
58 | /**
59 | * The rocker active area center position.
60 | * usually, it is the center of this view.
61 | */
62 | private Point mAreaPosition;
63 |
64 | /**
65 | * The Rocker position.
66 | * usually, it as same asmAreaPosition .
67 | * if this view touched, it will follow the touch position.
68 | *
69 | * we get position information from this.
70 | */
71 | private Point mRockerPosition;
72 | /**
73 | * The Rocker power.
74 | * Means distance between touched position and the center.
75 | * Always between 0.0d - 1.0d.
76 | */
77 | private double mRockerPower;
78 |
79 |
80 | private int mAreaRadius = -1;
81 | private int mRockerRadius = -1;
82 |
83 | private int mAreaColor;
84 | private int mRockerColor;
85 | private Bitmap mAreaBitmap;
86 | private Bitmap mRockerBitmap;
87 |
88 |
89 | private RockerListener mListener;
90 | public static final int EVENT_ACTION = 1;
91 | public static final int EVENT_CLOCK = 2;
92 | private int mRefreshCycle = 1000;
93 | /**
94 | * Indicates if the view is touched and kept holding.
95 | */
96 | private boolean isHeld = false;
97 |
98 |
99 | /*Life Cycle***********************************************************************************/
100 |
101 | public RockerView(Context context) {
102 | this(context, null);
103 | }
104 |
105 | public RockerView(Context context, AttributeSet attrs) {
106 | this(context, attrs, 0);
107 | }
108 |
109 | public RockerView(Context context, AttributeSet attrs, int defStyleAttr) {
110 | super(context, attrs, defStyleAttr);
111 |
112 | // init attrs
113 | initAttrs(context, attrs);
114 |
115 | // set paint
116 | setPaint();
117 |
118 | if (isInEditMode()) {
119 | return;
120 | }
121 |
122 | // config surfaceView
123 | configSurfaceView();
124 |
125 | // config surfaceHolder
126 | configSurfaceHolder();
127 | }
128 |
129 | private void initAttrs(Context context, AttributeSet attrs) {
130 |
131 | DEFAULT_AREA_RADIUS = DensityUtils.dip2px(context, 75);
132 | DEFAULT_ROCKER_RADIUS = DensityUtils.dip2px(context, 25);
133 |
134 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.viewsupport);
135 |
136 | mAreaRadius = ta.getDimensionPixelOffset(R.styleable.viewsupport_area_radius, DEFAULT_AREA_RADIUS);
137 | mRockerRadius = ta.getDimensionPixelOffset(R.styleable.viewsupport_rocker_radius, DEFAULT_ROCKER_RADIUS);
138 | mRefreshCycle = ta.getInteger(R.styleable.viewsupport_refresh_cycle, DEFAULT_REFRESH_CYCLE);
139 |
140 | Drawable area_bg = ta.getDrawable(R.styleable.viewsupport_area_background);
141 | Drawable rocker_bg = ta.getDrawable(R.styleable.viewsupport_rocker_background);
142 |
143 | if (area_bg instanceof BitmapDrawable) {
144 | mAreaBitmap = ((BitmapDrawable) area_bg).getBitmap();
145 | } else if (area_bg instanceof ColorDrawable) {
146 | mAreaBitmap = null;
147 | mAreaColor = ((ColorDrawable) area_bg).getColor();
148 | } else {
149 | mAreaBitmap = null;
150 | mAreaColor = DEFAULT_AREA_COLOR;
151 | }
152 |
153 | if (rocker_bg instanceof BitmapDrawable) {
154 | mRockerBitmap = ((BitmapDrawable) rocker_bg).getBitmap();
155 | } else if (rocker_bg instanceof ColorDrawable) {
156 | mRockerBitmap = null;
157 | mRockerColor = ((ColorDrawable) rocker_bg).getColor();
158 | } else {
159 | mRockerBitmap = null;
160 | mRockerColor = DEFAULT_ROCKER_COLOR;
161 | }
162 | }
163 |
164 | private void setPaint() {
165 | mPaint = new Paint();
166 | mPaint.setAntiAlias(true);
167 | }
168 |
169 | private void configSurfaceView() {
170 | setKeepScreenOn(true); // do not lock screen when surfaceView is running.
171 | setFocusable(true); // make sure this surfaceView can get focus from keyboard.
172 | setFocusableInTouchMode(true); // make sure this surfaceView can get focus from touch.
173 | setZOrderOnTop(true); // make sure this surface is placed on top of the window
174 | }
175 |
176 | private void configSurfaceHolder() {
177 | mHolder = getHolder();
178 | mHolder.addCallback(this);
179 | mHolder.setFormat(PixelFormat.TRANSPARENT); //设置背景透明
180 | }
181 |
182 | @Override
183 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
184 | int measureWidth = 0, measureHeight = 0;
185 | int defaultWidth = (mAreaRadius + mRockerRadius)*2;
186 | int defalutHeight = defaultWidth;
187 |
188 | int widthsize = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
189 | int widthmode = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
190 |
191 | int heightsize = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
192 | int heightmode = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式
193 |
194 | if (widthmode == MeasureSpec.AT_MOST || widthmode == MeasureSpec.UNSPECIFIED || widthsize < 0) {
195 | measureWidth = defaultWidth;
196 | } else {
197 | measureWidth = widthsize;
198 | }
199 |
200 |
201 | if (heightmode == MeasureSpec.AT_MOST || heightmode == MeasureSpec.UNSPECIFIED || heightsize < 0) {
202 | measureHeight = defalutHeight;
203 | } else {
204 | measureHeight = heightsize;
205 | }
206 |
207 | setMeasuredDimension(measureWidth, measureHeight);
208 | }
209 |
210 | @Override
211 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
212 | super.onSizeChanged(w, h, oldw, oldh);
213 |
214 | mAreaPosition = new Point(w / 2, h / 2);
215 | mRockerPosition = new Point(mAreaPosition);
216 |
217 | // this need subtract the view padding
218 | int tempRadius = Math.min(w - getPaddingLeft() - getPaddingRight(), h - getPaddingTop() - getPaddingBottom());
219 | tempRadius /= 2;
220 | if (mAreaRadius == -1)
221 | mAreaRadius = (int) (tempRadius * 0.75);
222 | if (mRockerRadius == -1)
223 | mRockerRadius = (int) (tempRadius * 0.25);
224 | }
225 |
226 |
227 | @Override
228 | public void surfaceCreated(SurfaceHolder holder) {
229 | try {
230 | drawOk = true;
231 | mViewThead = new HandlerThread("RockerViewSurface");
232 | mViewThead.start();
233 | mHandlerThread = new HandlerThread("RockerViewListener");
234 | mHandlerThread.start();
235 |
236 | Handler viewHandler = new Handler(mViewThead.getLooper());
237 | viewHandler.post(this);
238 |
239 | mHandler = new Handler(mHandlerThread.getLooper());
240 | mHandler.post(new Runnable() {
241 | @Override
242 | public void run() {
243 | listenerCallback();
244 | Log.v(TAG, "isHeld = " + isHeld);
245 | if(isHeld) {
246 | mRefreshCycle = 30;
247 | } else {
248 | mRefreshCycle = 1500;
249 | }
250 | mHandler.postDelayed(this, mRefreshCycle);
251 | }
252 | });
253 | } catch (Exception e) {
254 | e.printStackTrace();
255 | }
256 | }
257 |
258 | @Override
259 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
260 |
261 | @Override
262 | public void surfaceDestroyed(SurfaceHolder holder) {
263 | try {
264 | drawOk = false;
265 | mHandler.removeCallbacks(null);
266 | mHandlerThread.quit();
267 | mViewThead.quit();
268 | } catch (Exception e) {
269 | e.printStackTrace();
270 | }
271 | }
272 |
273 | @Override
274 | protected void onVisibilityChanged(View changedView, int visibility) {
275 | super.onVisibilityChanged(changedView, visibility);
276 | if(visibility==VISIBLE) {
277 | drawOk = true;
278 | } else {
279 | drawOk = false;
280 | }
281 | }
282 |
283 | /*Event Response*******************************************************************************/
284 |
285 | @Override
286 | public boolean onTouchEvent(MotionEvent event) {
287 | try {
288 | double power = getDistance(mAreaPosition.x, mAreaPosition.y, event.getX(), event.getY());
289 |
290 | if (event.getAction() == MotionEvent.ACTION_DOWN) {
291 | //如果屏幕接触点不在摇杆挥动范围内,则不处理
292 | if (power > mAreaRadius) {
293 | return true;
294 | }
295 | isHeld = true;
296 | }
297 |
298 | if (event.getAction() == MotionEvent.ACTION_MOVE) {
299 | if (power <= mAreaRadius) {
300 | //如果手指在摇杆活动范围内,则摇杆处于手指触摸位置
301 | mRockerPosition.set((int) event.getX(), (int) event.getY());
302 | mRockerPower = power / mAreaRadius;
303 | } else {
304 | //设置摇杆位置,使其处于手指触摸方向的 摇杆活动范围边缘
305 | mRockerPosition = MathUtils.getPointByCutLength(mAreaPosition,
306 | new Point((int) event.getX(), (int) event.getY()), mAreaRadius);
307 | mRockerPower = 1.0d;
308 | }
309 | if (mListener != null) {
310 | float radian = MathUtils.getRadian(mAreaPosition, new Point((int) event.getX(), (int) event.getY()));
311 | mListener.callback(EVENT_ACTION, getAngleConvert(radian), mRockerPower);
312 | }
313 | }
314 | //如果手指离开屏幕,则摇杆返回初始位置
315 | if (event.getAction() == MotionEvent.ACTION_UP) {
316 | isHeld = false;
317 | mRockerPosition = new Point(mAreaPosition);
318 | if (mListener != null) {
319 | mListener.callback(EVENT_ACTION, -1, 0);
320 | }
321 | }
322 | if (event.getAction() == MotionEvent.ACTION_CANCEL) {
323 | isHeld = false;
324 | }
325 | } catch (Exception e) {
326 | e.printStackTrace();
327 | }
328 | return true;
329 | }
330 |
331 |
332 | /*Thread - draw view***************************************************************************/
333 |
334 | @Override
335 | public void run() {
336 | if (isInEditMode()) {
337 | return;
338 | }
339 |
340 | Canvas canvas = null;
341 |
342 | while (drawOk) {
343 | try {
344 | canvas = mHolder.lockCanvas();
345 | if(canvas == null) {
346 | drawOk = false;
347 | break;
348 | }
349 | canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
350 | // draw area
351 | drawArea(canvas);
352 | // draw rocker
353 | drawRocker(canvas);
354 |
355 | } catch (Exception e) {
356 | e.printStackTrace();
357 | } finally {
358 | if (canvas != null) {
359 | mHolder.unlockCanvasAndPost(canvas);
360 | }
361 | }
362 | }
363 | }
364 |
365 | private void drawArea(Canvas canvas) {
366 |
367 | if (null != mAreaBitmap) {
368 | mPaint.setColor(Color.BLACK);
369 | Rect src = new Rect(0, 0, mAreaBitmap.getWidth(), mAreaBitmap.getHeight());
370 | Rect dst = new Rect(
371 | mAreaPosition.x - mAreaRadius,
372 | mAreaPosition.y - mAreaRadius,
373 | mAreaPosition.x + mAreaRadius,
374 | mAreaPosition.y + mAreaRadius);
375 | canvas.drawBitmap(mAreaBitmap, src, dst, mPaint);
376 | } else {
377 | mPaint.setColor(mAreaColor);
378 | canvas.drawCircle(mAreaPosition.x, mAreaPosition.y, mAreaRadius, mPaint);
379 | }
380 | }
381 |
382 | private void drawRocker(Canvas canvas) {
383 | if (null != mRockerBitmap) {
384 | mPaint.setColor(Color.BLACK);
385 | Rect src = new Rect(0, 0, mRockerBitmap.getWidth(), mRockerBitmap.getHeight());
386 | Rect dst = new Rect(
387 | mRockerPosition.x - mRockerRadius,
388 | mRockerPosition.y - mRockerRadius,
389 | mRockerPosition.x + mRockerRadius,
390 | mRockerPosition.y + mRockerRadius);
391 | canvas.drawBitmap(mRockerBitmap, src, dst, mPaint);
392 | } else {
393 | mPaint.setColor(mRockerColor);
394 | canvas.drawCircle(mRockerPosition.x, mRockerPosition.y, mRockerRadius, mPaint);
395 | }
396 | }
397 |
398 | private void listenerCallback() {
399 | if (mListener != null) {
400 | if (mRockerPosition.x == mAreaPosition.x && mRockerPosition.y == mAreaPosition.y) {
401 | mListener.callback(EVENT_CLOCK, -1, 0);
402 | } else {
403 | float radian = MathUtils.getRadian(mAreaPosition, new Point(mRockerPosition.x, mRockerPosition.y));
404 | mListener.callback(EVENT_CLOCK, RockerView.this.getAngleConvert(radian), mRockerPower);
405 | }
406 | }
407 | }
408 |
409 | //获取摇杆偏移角度 0-360°
410 | private int getAngleConvert(float radian) {
411 | int tmp = (int) Math.round(radian / Math.PI * 180);
412 | if (tmp < 0) {
413 | return -tmp;
414 | } else {
415 | return 180 + (180 - tmp);
416 | }
417 | }
418 |
419 | // for preview
420 | @Override
421 | protected void onDraw(Canvas canvas) {
422 | if (isInEditMode()) {
423 | canvas.drawColor(Color.WHITE);
424 | drawArea(canvas);
425 | drawRocker(canvas);
426 | }
427 | }
428 |
429 | /*Getter Setter********************************************************************************/
430 |
431 | public int getAreaRadius() {
432 | return mAreaRadius;
433 | }
434 |
435 | public void setAreaRadius(int areaRadius) {
436 | mAreaRadius = areaRadius;
437 | }
438 |
439 | public int getRockerRadius() {
440 | return mRockerRadius;
441 | }
442 |
443 | public void setRockerRadius(int rockerRadius) {
444 | mRockerRadius = rockerRadius;
445 | }
446 |
447 | public Bitmap getAreaBitmap() {
448 | return mAreaBitmap;
449 | }
450 |
451 | public void setAreaBitmap(Bitmap areaBitmap) {
452 | mAreaBitmap = areaBitmap;
453 | }
454 |
455 | public Bitmap getRockerBitmap() {
456 | return mRockerBitmap;
457 | }
458 |
459 | public void setRockerBitmap(Bitmap rockerBitmap) {
460 | mRockerBitmap = rockerBitmap;
461 | }
462 |
463 | public int getRefreshCycle() {
464 | return mRefreshCycle;
465 | }
466 |
467 | public void setRefreshCycle(int refreshCycle) {
468 | mRefreshCycle = refreshCycle;
469 | }
470 |
471 | public int getAreaColor() {
472 | return mAreaColor;
473 | }
474 |
475 | public void setAreaColor(int areaColor) {
476 | mAreaColor = areaColor;
477 | mAreaBitmap = null;
478 | }
479 |
480 | public int getRockerColor() {
481 | return mRockerColor;
482 | }
483 |
484 | public void setRockerColor(int rockerColor) {
485 | mRockerColor = rockerColor;
486 | mRockerBitmap = null;
487 | }
488 |
489 | public void setListener(@NonNull RockerListener listener) {
490 | mListener = listener;
491 | }
492 |
493 | /*Rocker Listener******************************************************************************/
494 |
495 | public interface RockerListener {
496 | /**
497 | * you can get some event from this method
498 | *
499 | * @param eventType The event type, EVENT_ACTION or EVENT_CLOCK
500 | * @param currentAngle The current angle
501 | */
502 | void callback(int eventType, int currentAngle, double power);
503 | }
504 |
505 | private static double getDistance(double x1, double y1, double x2, double y2) {
506 | return Math.sqrt(Math.pow(x1 - x2, 2.0D) + Math.pow(y1 - y2, 2.0D));
507 | }
508 | }
509 |
--------------------------------------------------------------------------------