├── 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 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | ![1](png/1.png) 32 | ![1](png/2.png) 33 | ![1](png/3.png) 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 | ![1](png/1.png) 30 | ![2](png/2.png) 31 | ![3](png/3.png) 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 | --------------------------------------------------------------------------------