最近遇到一个需求,是需要做一个手机模拟位置的功能,功能场景不复杂,主要如下:
1、用户可以通过本软件进行位置设置
2、位置设置成功以后,手机中的其他软件当前的位置应该都是设置之后的经纬度
3、需要针对部分考勤软件做防检测
经过不断的努力,功能已完成,安装包和代码已上传至github,下载地址:https://github.com/MockLocation/dingdian
您也可以直接下载安装:http://app.yuyanda.com/amc2
核心代码如下:
1、添加应用分身
private InstallResult installPackageImpl(String path, InstallOptions options) {
long installTime = System.currentTimeMillis();
if (path == null) {
return InstallResult.makeFailure("path = NULL");
}
File packageFile = new File(path);
if (!packageFile.exists() || !packageFile.isFile()) {
return InstallResult.makeFailure("Package File is not exist.");
}
VPackage pkg = null;
try {
pkg = PackageParserEx.parsePackage(packageFile);
} catch (Throwable e) {
e.printStackTrace();
}
if (pkg == null || pkg.packageName == null) {
return InstallResult.makeFailure("Unable to parse the package.");
}
InstallResult res = new InstallResult();
res.packageName = pkg.packageName;
// PackageCache holds all packages, try to check if we need to update.
VPackage existOne = PackageCacheManager.get(pkg.packageName);
PackageSetting existSetting = existOne != null ? (PackageSetting) existOne.mExtras : null;
if (existOne != null) {
if (options.updateStrategy == InstallOptions.UpdateStrategy.IGNORE_NEW_VERSION) {
res.isUpdate = true;
return res;
}
if (!isAllowedUpdate(existOne, pkg, options.updateStrategy)) {
return InstallResult.makeFailure("Not allowed to update the package.");
}
res.isUpdate = true;
VActivityManagerService.get().killAppByPkg(res.packageName, VUserHandle.USER_ALL);
}
boolean useSourceLocationApk = options.useSourceLocationApk;
if (existOne != null) {
PackageCacheManager.remove(pkg.packageName);
}
PackageSetting ps;
if (existSetting != null) {
ps = existSetting;
} else {
ps = new PackageSetting();
}
boolean support64bit = false, support32bit = false;
boolean checkSupportAbi = true;
Set<String> abiList = NativeLibraryHelperCompat.getSupportAbiList(packageFile.getPath());
if (abiList.isEmpty()) {
support64bit = true;
support32bit = true;
} else {
if (NativeLibraryHelperCompat.contain64bitAbi(abiList)) {
support64bit = true;
}
if (NativeLibraryHelperCompat.contain32bitAbi(abiList)) {
support32bit = true;
}
}
if (support32bit) {
if (support64bit) {
ps.flag = PackageSetting.FLAG_RUN_BOTH_32BIT_64BIT;
} else {
ps.flag = PackageSetting.FLAG_RUN_32BIT;
}
} else {
ps.flag = PackageSetting.FLAG_RUN_64BIT;
}
NativeLibraryHelperCompat.copyNativeBinaries(packageFile, VEnvironment.getAppLibDirectory(pkg.packageName));
if (!useSourceLocationApk) {
File privatePackageFile = VEnvironment.getPackageResourcePath(pkg.packageName);
try {
FileUtils.copyFile(packageFile, privatePackageFile);
} catch (IOException e) {
privatePackageFile.delete();
return InstallResult.makeFailure("Unable to copy the package file.");
}
packageFile = privatePackageFile;
VEnvironment.chmodPackageDictionary(packageFile);
}
if (support64bit && !useSourceLocationApk) {
// V64BitHelper.copyPackage64(packageFile.getPath(), pkg.packageName);
}
ps.appMode = useSourceLocationApk ? MODE_APP_USE_OUTSIDE_APK : MODE_APP_COPY_APK;
ps.packageName = pkg.packageName;
ps.appId = VUserHandle.getAppId(mUidSystem.getOrCreateUid(pkg));
if (res.isUpdate) {
ps.lastUpdateTime = installTime;
} else {
ps.firstInstallTime = installTime;
ps.lastUpdateTime = installTime;
for (int userId : VUserManagerService.get().getUserIds()) {
boolean installed = userId == 0;
ps.setUserState(userId, false/*launched*/, false/*hidden*/, installed);
}
}
PackageParserEx.savePackageCache(pkg);
PackageCacheManager.put(pkg, ps);
mPersistenceLayer.save();
if (support32bit && !useSourceLocationApk) {
try {
DexOptimizer.optimizeDex(packageFile.getPath(), VEnvironment.getOdexFile(ps.packageName).getPath());
} catch (IOException e) {
e.printStackTrace();
}
}
if (options.notify) {
notifyAppInstalled(ps, -1);
}
res.isSuccess = true;
return res;
}
2、启动应用
public boolean launchApp(final int userId, String packageName, boolean preview) {
Context context = VirtualCore.get().getContext();
VPackageManager pm = VPackageManager.get();
Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
intentToResolve.addCategory(Intent.CATEGORY_INFO);
intentToResolve.setPackage(packageName);
List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, intentToResolve.resolveType(context), 0, userId);
// Otherwise, try to find a main launcher activity.
if (ris == null || ris.size() <= 0) {
// reuse the intent instance
intentToResolve.removeCategory(Intent.CATEGORY_INFO);
intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
intentToResolve.setPackage(packageName);
ris = pm.queryIntentActivities(intentToResolve, intentToResolve.resolveType(context), 0, userId);
}
if (ris == null || ris.size() <= 0) {
return false;
}
ActivityInfo info = ris.get(0).activityInfo;
final Intent intent = new Intent(intentToResolve);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName(info.packageName, info.name);
if (!preview || VActivityManager.get().isAppRunning(info.packageName, userId, true)) {
VActivityManager.get().startActivity(intent, userId);
} else {
intent.putExtra("_VA_|no_animation", true);
WindowPreviewActivity.previewActivity(userId, info);
VirtualRuntime.getUIHandler().postDelayed(new Runnable() {
@Override
public void run() {VActivityManager.get().startActivity(intent, userId);
}
}, 400L);
}
return true;
}
3、对requestLocationUpdates这个关键函数进行拦截
@SkipInject
static class RequestLocationUpdates extends ReplaceLastPkgMethodProxy {
public RequestLocationUpdates() {
super("requestLocationUpdates");
}
public RequestLocationUpdates(String name) {
super(name);
}
@Override
public Object call(final Object who, Method method, Object... args) throws Throwable {
if (isFakeLocationEnable()) {
VLocationManager.get().requestLocationUpdates(args);
return 0;
} else {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
LocationRequest request = (LocationRequest) args[0];
fixLocationRequest(request);
}
}
return super.call(who, method, args);
}
}
4、提供虚拟位置
private boolean notifyLocation(final Object ListenerTransport, final Location location, boolean post) {
checkWork();
if (ListenerTransport == null) {
return false;
}
if (!post) {
try {
mirror.android.location.LocationManager.ListenerTransport.mListener.get(ListenerTransport).onLocationChanged(location);
return true;
} catch (Throwable e) {
}
return false;
}
mWorkHandler.post(new Runnable() {
@Override
public void run() {
try {
mirror.android.location.LocationManager.ListenerTransport.mListener.get(ListenerTransport).onLocationChanged(location);
} catch (Throwable e) {
}
}
});
return true;
}
目前还存在不少问题,比如安卓10的适配不是很好,后来使用epic框架,勉强可以使用,但是效率不是很好,低配置的手机使用起来会有卡顿的情况,还有部分手机不能兼容,启动分身的时候会崩溃