diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index be3b5af..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -FakeGps \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 4f23291..df527ea 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -3,21 +3,14 @@ diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 622f5c8..a973a9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "name.caiyao.fakegps" minSdkVersion 9 targetSdkVersion 22 - versionCode 3 - versionName "1.2.0 beta" + versionCode 6 + versionName "1.2.2" } buildTypes { release { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bbb859a..3366b0b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -45,6 +45,10 @@ android:value="e34a748230a8123b72fd71a6f1604946" /> + \ No newline at end of file diff --git a/app/src/main/java/name/caiyao/fakegps/DbHelper.java b/app/src/main/java/name/caiyao/fakegps/DbHelper.java new file mode 100644 index 0000000..a6d2a00 --- /dev/null +++ b/app/src/main/java/name/caiyao/fakegps/DbHelper.java @@ -0,0 +1,27 @@ +package name.caiyao.fakegps; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +/** + * Created by 蔡小木 on 2016/5/11 0011. + */ +public class DbHelper extends SQLiteOpenHelper { + + private static final String DB_NAME = "fake.db"; + public static final String TABLE_NAME = "fake"; + public DbHelper(Context context) { + super(context, DB_NAME, null, 1); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS "+TABLE_NAME + "(key TEXT PRIMARY KEY,value TEXT)"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + } +} diff --git a/app/src/main/java/name/caiyao/fakegps/GpsUtils.java b/app/src/main/java/name/caiyao/fakegps/GpsUtils.java new file mode 100644 index 0000000..9927436 --- /dev/null +++ b/app/src/main/java/name/caiyao/fakegps/GpsUtils.java @@ -0,0 +1,89 @@ +package name.caiyao.fakegps; + +/** + * Created by 蔡小木 on 2016/5/24 0024. + */ + +public class GpsUtils { + + static double x_pi = 3.14159265358979324 * 3000.0 / 180.0; + // π + static double pi = 3.1415926535897932384626; + // 长半轴 + static double a = 6378245.0; + // 扁率 + static double ee = 0.00669342162296594323; + + private GpsUtils(){ + } + + /** + * GCJ02(火星坐标系)转GPS84 + * + * @param lng 火星坐标系的经度 + * @param lat 火星坐标系纬度 + * @return WGS84坐标数组 + */ + public static double[] gcj02towgs84(double lng, double lat) { + if (out_of_china(lng, lat)) { + return new double[] { lng, lat }; + } + double dlat = transformlat(lng - 105.0, lat - 35.0); + double dlng = transformlng(lng - 105.0, lat - 35.0); + double radlat = lat / 180.0 * pi; + double magic = Math.sin(radlat); + magic = 1 - ee * magic * magic; + double sqrtmagic = Math.sqrt(magic); + dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi); + dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * pi); + double mglat = lat + dlat; + double mglng = lng + dlng; + return new double[] { lng * 2 - mglng, lat * 2 - mglat }; + } + + /** + * 纬度转换 + * + * @param lng + * @param lat + * @return + */ + public static double transformlat(double lng, double lat) { + double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); + ret += (20.0 * Math.sin(6.0 * lng * pi) + 20.0 * Math.sin(2.0 * lng * pi)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lat * pi) + 40.0 * Math.sin(lat / 3.0 * pi)) * 2.0 / 3.0; + ret += (160.0 * Math.sin(lat / 12.0 * pi) + 320 * Math.sin(lat * pi / 30.0)) * 2.0 / 3.0; + return ret; + } + + /** + * 经度转换 + * + * @param lng + * @param lat + * @return + */ + public static double transformlng(double lng, double lat) { + double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); + ret += (20.0 * Math.sin(6.0 * lng * pi) + 20.0 * Math.sin(2.0 * lng * pi)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lng * pi) + 40.0 * Math.sin(lng / 3.0 * pi)) * 2.0 / 3.0; + ret += (150.0 * Math.sin(lng / 12.0 * pi) + 300.0 * Math.sin(lng / 30.0 * pi)) * 2.0 / 3.0; + return ret; + } + + /** + * 判断是否在国内,不在国内不做偏移 + * + * @param lng + * @param lat + * @return + */ + public static boolean out_of_china(double lng, double lat) { + if (lng < 72.004 || lng > 137.8347) { + return true; + } else if (lat < 0.8293 || lat > 55.8271) { + return true; + } + return false; + } +} diff --git a/app/src/main/java/name/caiyao/fakegps/MainActivity.java b/app/src/main/java/name/caiyao/fakegps/MainActivity.java index 570b219..ac2679e 100644 --- a/app/src/main/java/name/caiyao/fakegps/MainActivity.java +++ b/app/src/main/java/name/caiyao/fakegps/MainActivity.java @@ -9,10 +9,12 @@ import android.location.LocationManager; import android.net.Uri; import android.os.Bundle; +import android.provider.Settings; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -37,12 +39,14 @@ public class MainActivity extends AppCompatActivity implements AMap.OnMapClickLi private AMap aMap; private LatLng latLng; private SharedPreferences sharedPreferences; + private long mExitTime = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sharedPreferences = getSharedPreferences("locationHistory", Context.MODE_PRIVATE); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); @@ -52,7 +56,14 @@ protected void onCreate(Bundle savedInstanceState) { aMap = mv.getMap(); double lat = Double.parseDouble(sharedPreferences.getString("lat", "0.0")); double lon = Double.parseDouble(sharedPreferences.getString("lon", "0.0")); - if (lat != 0.0 && lon != 0.0){ + if (lat != 0.0 && lon != 0.0) { + latLng = new LatLng(lat, lon); + aMap.clear(); + MarkerOptions markerOptions = new MarkerOptions(); + markerOptions.position(latLng); + markerOptions.draggable(true); + markerOptions.title("经度:" + latLng.longitude + ",纬度:" + latLng.latitude); + aMap.addMarker(markerOptions); aMap.moveCamera(CameraUpdateFactory.changeLatLng(new LatLng(lat, lon))); aMap.moveCamera(CameraUpdateFactory.zoomTo(aMap.getMaxZoomLevel())); } @@ -79,11 +90,26 @@ protected void onResume() { locationManager.requestLocationUpdates(mockProviderName, 0, 0, this); } catch (Exception e) { e.printStackTrace(); - Toast.makeText(this, "请打开模拟位置权限!", Toast.LENGTH_SHORT).show(); + showDialog(); } mv.onResume(); } + private void showDialog() { + new AlertDialog.Builder(this).setTitle("需要打开模拟位置").setMessage("是否跳转到开发者选项打开模拟位置").setPositiveButton("确认", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS); + startActivity(intent); + } + }).setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }).show(); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); @@ -113,13 +139,16 @@ public boolean onOptionsItemSelected(MenuItem item) { android.location.Criteria.ACCURACY_FINE); locationManager.setTestProviderEnabled(mockProviderName, true); locationManager.requestLocationUpdates(mockProviderName, 0, 0, this); - startService(new Intent(MainActivity.this, MockGpsService.class).putExtra("action", MockGpsService.ACTION_START).putExtra("location", latLng.latitude + ":" + latLng.longitude)); + double[] gpsLocation = GpsUtils.gcj02towgs84(latLng.longitude, latLng.latitude); + startService(new Intent(MainActivity.this, MockGpsService.class).putExtra("action", MockGpsService.ACTION_START).putExtra("location", gpsLocation[1] + ":" + gpsLocation[0])); + Toast.makeText(this, "位置模拟成功,重启需定位的应用以生效!", Toast.LENGTH_LONG).show(); } catch (Exception e) { e.printStackTrace(); - Toast.makeText(this, "请打开模拟位置权限!", Toast.LENGTH_SHORT).show(); + showDialog(); } break; case R.id.stop: + Toast.makeText(this, "位置模拟关闭,重启需定位的应用以生效!", Toast.LENGTH_LONG).show(); startService(new Intent(MainActivity.this, MockGpsService.class).putExtra("action", MockGpsService.ACTION_STOP)); break; case R.id.search: @@ -127,6 +156,7 @@ public boolean onOptionsItemSelected(MenuItem item) { final EditText et_key = (EditText) view.findViewById(R.id.key); new AlertDialog.Builder(this).setView(view) .setTitle("搜索位置") + .setMessage("只能搜索国内地点,如需定位国外请拖拽地图选取!定位到国外使用Google地图的需要翻墙!") .setPositiveButton("搜索", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -156,6 +186,22 @@ protected void onPause() { mv.onPause(); } + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if ((System.currentTimeMillis() - mExitTime) > 2000) {// + // 如果两次按键时间间隔大于2000毫秒,则不退出 + Toast.makeText(this, "再按一次退出程序,停止模拟位置!", Toast.LENGTH_SHORT).show(); + mExitTime = System.currentTimeMillis();// 更新mExitTime + } else { + System.exit(0);// 否则退出程序 + } + return true; + } + return super.onKeyDown(keyCode, event); + + } + private void search(final String key) { PoiSearch.Query query = new PoiSearch.Query(key, null, null); query.setPageSize(10); @@ -215,7 +261,6 @@ public void onMapClick(LatLng latLng) { MarkerOptions markerOptions = new MarkerOptions(); markerOptions.position(latLng); markerOptions.draggable(true); - Log.i("TAG", "经度:" + latLng.longitude + ",纬度:" + latLng.latitude); markerOptions.title("经度:" + latLng.longitude + ",纬度:" + latLng.latitude); aMap.addMarker(markerOptions); this.latLng = latLng; diff --git a/app/src/main/java/name/caiyao/fakegps/MainHook.java b/app/src/main/java/name/caiyao/fakegps/MainHook.java index db162ed..ddfe6ef 100644 --- a/app/src/main/java/name/caiyao/fakegps/MainHook.java +++ b/app/src/main/java/name/caiyao/fakegps/MainHook.java @@ -1,16 +1,12 @@ package name.caiyao.fakegps; import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.database.Cursor; +import android.net.Uri; import android.os.Build; import android.provider.Settings; -import android.telephony.CellIdentityCdma; -import android.telephony.CellIdentityGsm; -import android.telephony.CellIdentityLte; -import android.telephony.CellIdentityWcdma; -import android.telephony.CellInfoCdma; -import android.telephony.CellInfoGsm; -import android.telephony.CellInfoLte; -import android.telephony.CellInfoWcdma; import android.telephony.CellLocation; import android.telephony.gsm.GsmCellLocation; @@ -19,6 +15,7 @@ import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; @@ -26,10 +23,36 @@ * Created by 蔡小木 on 2016/4/17 0017. */ public class MainHook implements IXposedHookLoadPackage { + + public static boolean isHook = false; + @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { - if (!loadPackageParam.packageName.contains("tencent") && !loadPackageParam.packageName.contains("alibaba") && !loadPackageParam.packageName.contains("baidu")) + final Object activityThread = XposedHelpers.callStaticMethod(XposedHelpers.findClass("android.app.ActivityThread", null), "currentActivityThread"); + final Context systemContext = (Context) XposedHelpers.callMethod(activityThread, "getSystemContext"); + Uri uri = Uri.parse("content://name.caiyao.fakegps.SettingProvider/fake"); + Cursor cursor = systemContext.getContentResolver().query(uri, null, null, null, null); + if (cursor != null && cursor.moveToNext()) { + String key = cursor.getString(cursor.getColumnIndex("key")); + String value = cursor.getString(cursor.getColumnIndex("value")); + if (key.equals("hook")) { + if (value.equals("1")) { + isHook = true; + } else if (value.equals("0")) { + isHook = false; + } + } + cursor.close(); + }else{ + XposedBridge.log("cusor is null"); + } + + if ((loadPackageParam.appInfo.flags & ApplicationInfo.FLAG_SYSTEM) > 0) + return; + XposedBridge.log(loadPackageParam.packageName + ",hook;" + isHook); + if (!isHook) { return; + } XposedHelpers.findAndHookMethod("android.provider.Settings.Secure", loadPackageParam.classLoader, "getString", ContentResolver.class, String.class, new XC_MethodHook() { @Override @@ -192,14 +215,14 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable { "getAllCellInfo", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { - param.setResult(getCell(0, 0, 0, 0, 0, 0)); + param.setResult(new ArrayList<>()); } }); XposedHelpers.findAndHookMethod("android.telephony.PhoneStateListener", loadPackageParam.classLoader, "onCellInfoChanged", List.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - param.setResult(getCell(0, 0, 0, 0, 0, 0)); + param.setResult(new ArrayList<>()); } }); } @@ -286,42 +309,4 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable { } }); } - - private static ArrayList getCell(int mcc, int mnc, int lac, int cid, int sid, int networkType) { - ArrayList arrayList = new ArrayList(); - CellInfoGsm cellInfoGsm = (CellInfoGsm) XposedHelpers.newInstance(CellInfoGsm.class); - XposedHelpers.callMethod(cellInfoGsm, "setCellIdentity", XposedHelpers.newInstance(CellIdentityGsm.class, new Object[]{Integer.valueOf(mcc), Integer.valueOf(mnc), Integer.valueOf( - lac), Integer.valueOf(cid)})); - CellInfoCdma cellInfoCdma = (CellInfoCdma) XposedHelpers.newInstance(CellInfoCdma.class); - XposedHelpers.callMethod(cellInfoCdma, "setCellIdentity", XposedHelpers.newInstance(CellIdentityCdma.class, new Object[]{Integer.valueOf(lac), Integer.valueOf(sid), Integer.valueOf(cid), Integer.valueOf(0), Integer.valueOf(0)})); - CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) XposedHelpers.newInstance(CellInfoWcdma.class); - XposedHelpers.callMethod(cellInfoWcdma, "setCellIdentity", XposedHelpers.newInstance(CellIdentityWcdma.class, new Object[]{Integer.valueOf(mcc), Integer.valueOf(mnc), Integer.valueOf(lac), Integer.valueOf(cid), Integer.valueOf(300)})); - CellInfoLte cellInfoLte = (CellInfoLte) XposedHelpers.newInstance(CellInfoLte.class); - XposedHelpers.callMethod(cellInfoLte, "setCellIdentity", XposedHelpers.newInstance(CellIdentityLte.class, new Object[]{Integer.valueOf(mcc), Integer.valueOf(mnc), Integer.valueOf(cid), Integer.valueOf(300), Integer.valueOf(lac)})); - switch (networkType) { - case 1: - case 2: - arrayList.add(cellInfoGsm); - break; - case 13: - arrayList.add(cellInfoLte); - break; - case 4: - case 5: - case 6: - case 7: - case 12: - case 14: - arrayList.add(cellInfoCdma); - break; - case 3: - case 8: - case 9: - case 10: - case 15: - arrayList.add(cellInfoWcdma); - break; - } - return arrayList; - } } diff --git a/app/src/main/java/name/caiyao/fakegps/MockGpsService.java b/app/src/main/java/name/caiyao/fakegps/MockGpsService.java index 45e2e50..009642c 100644 --- a/app/src/main/java/name/caiyao/fakegps/MockGpsService.java +++ b/app/src/main/java/name/caiyao/fakegps/MockGpsService.java @@ -2,11 +2,15 @@ import android.app.PendingIntent; import android.app.Service; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.database.sqlite.SQLiteDatabase; import android.graphics.BitmapFactory; +import android.location.Criteria; import android.location.Location; import android.location.LocationManager; +import android.os.Build; import android.os.IBinder; import android.os.SystemClock; import android.support.v7.app.NotificationCompat; @@ -18,10 +22,17 @@ public class MockGpsService extends Service { public static final String ACTION_STOP = "name.caiyao.fakegps.STOP_FAKE"; UpdateGPSThread currentThread = null; + private SQLiteDatabase mSQLiteDatabase; public MockGpsService() { } + @Override + public void onCreate() { + super.onCreate(); + mSQLiteDatabase = new DbHelper(this).getWritableDatabase(); + } + @Override public IBinder onBind(Intent intent) { return null; @@ -34,8 +45,16 @@ public int onStartCommand(Intent intent, int flags, int startId) { currentThread = new UpdateGPSThread(); currentThread.mLocation = intent.getStringExtra("location"); currentThread.start(); + ContentValues contentValues = new ContentValues(); + contentValues.put("key", "hook"); + contentValues.put("value", "1"); + mSQLiteDatabase.insertWithOnConflict(DbHelper.TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE); } if (intent.getStringExtra("action").equalsIgnoreCase(ACTION_STOP)) { + ContentValues contentValues = new ContentValues(); + contentValues.put("key", "hook"); + contentValues.put("value", "0"); + mSQLiteDatabase.insertWithOnConflict(DbHelper.TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE); if (currentThread != null) { currentThread.Running = false; currentThread.interrupt(); @@ -83,13 +102,15 @@ public void run() { location.setAltitude(2.0f); location.setAccuracy(3.0f); createProgressNotification(location); - location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); + } try { locationManager.addTestProvider("gps", "requiresNetwork".equals(""), "requiresSatellite".equals(""), "requiresCell".equals(""), "hasMonetaryCost".equals(""), "supportsAltitude".equals(""), "supportsSpeed".equals(""), - "supportsBearing".equals(""), android.location.Criteria.POWER_LOW, - android.location.Criteria.ACCURACY_FINE); + "supportsBearing".equals(""), Criteria.POWER_LOW, + Criteria.ACCURACY_FINE); locationManager.setTestProviderLocation("gps", location); } catch (SecurityException e) { e.printStackTrace(); diff --git a/app/src/main/java/name/caiyao/fakegps/SettingProvider.java b/app/src/main/java/name/caiyao/fakegps/SettingProvider.java new file mode 100644 index 0000000..bfda26f --- /dev/null +++ b/app/src/main/java/name/caiyao/fakegps/SettingProvider.java @@ -0,0 +1,49 @@ +package name.caiyao.fakegps; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.support.annotation.Nullable; + +/** + * Created by 蔡小木 on 2016/5/11 0011. + */ +public class SettingProvider extends ContentProvider { + private SQLiteDatabase mSQLiteDatabase; + + @Override + public boolean onCreate() { + mSQLiteDatabase = new DbHelper(getContext()).getWritableDatabase(); + return true; + } + + @Nullable + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return mSQLiteDatabase.query(DbHelper.TABLE_NAME,projection,selection,selectionArgs,null,null,sortOrder,null); + } + + @Nullable + @Override + public String getType(Uri uri) { + return null; + } + + @Nullable + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +}