1 引言
模拟定位是为了解决软件测试过程中需要对定位进行变更的需求问题。
1.1 步骤
1)设备需要先打开开发者模式,将其中的“允许模拟位置”打开(android6.0开始改为"选择模拟位置信息应用")。
2)编写或安装相应的模拟定位的apk,实现模拟定位。
1.2 检测方法
1)android6.0之前:使用Settings.Secure.ALLOW_MOCK_LOCATION判断
2)android6.0开始:调用addTestProvider()方法,该方法在android.app.AppOpsManager#OPSTR_MOCK_LOCATION的值不为MODE_ALLOWED时会抛SecurityException异常。
2 模拟定位apk代码
private LocationManager mLocManager;
mLocManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mLocManager.addTestProvider(LocationManager.GPS_PROVIDER,
"requiresNetwork" == "",
"requiresSatellite" == "",
"requiresCell" == "",
"hasMonetaryCost" == "",
"supportsAltitude" == "",
"supportsSpeed" == "",
"supportsBearing" == "",
Criteria.NO_REQUIREMENT,
Criteria.ACCURACY_COARSE);
// 创建新的Location对象,并设定必要的属性值
Location newLocation = new Location(LocationManager.GPS_PROVIDER);
newLocation.setLatitude(39.820015);
newLocation.setLongitude(116.813752);
newLocation.setAccuracy(500);
newLocation.setTime(System.currentTimeMillis());
// 这里一定要设置nonasecond单位的值,否则是没法持续收到监听的
newLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
// 开启测试Provider
mLocManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);
mLocManager.setTestProviderStatus(LocationManager.GPS_PROVIDER,
LocationProvider.AVAILABLE,
null,
System.currentTimeMillis());
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
3000,
10.0f,
new MyLocationListener());
// 设置最新位置,一定要在requestLocationUpdate完成后进行,才能收到监听
mLocManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, newLocation);
代码的大致流程为:addTestProvider-->创建新的Location对象并初始化-->setTestProviderEnabled-->setTestProviderStatus-->requestLocationUpdates-->setTestProviderLocation
3 源码分析
源码路径:frameworks/base/location/java/android/location/LocationManager.java
3.1 addTestProvider
在LocationManager.java中该函数的主要流程为:
mService.addTestProvider(name, properties, mContext.getOpPackageName());
即调用mService的addTestProvider函数;
其中,mService为LocationManagerService
name为LocationManager.GPS_PROVIDER;
properties为传入的参数组合而成的值;
getOpPackageName的结果用于给AppOpsManager标记该app;
接下来看LocationManagerService的addTestProvider():
源码路径: /frameworks/base/services/core/java/com/android/server/LocationManagerService.java
@Override
public void addTestProvider(String name, ProviderProperties properties, String opPackageName) {
//检查权限,如果该app没有
if (!canCallerAccessMockLocation(opPackageName)) {
return;
}
if (LocationManager.PASSIVE_PROVIDER.equals(name)) {
throw new IllegalArgumentException("Cannot mock the passive location provider");
}
long identity = Binder.clearCallingIdentity();
synchronized (mLock) {
// remove the real provider if we are replacing GPS or network provider
if (LocationManager.GPS_PROVIDER.equals(name)
|| LocationManager.NETWORK_PROVIDER.equals(name)
|| LocationManager.FUSED_PROVIDER.equals(name)) {
LocationProviderInterface p = mProvidersByName.get(name);
if (p != null) {
removeProviderLocked(p);
}
}
addTestProviderLocked(name, properties);
updateProvidersLocked();
}
Binder.restoreCallingIdentity(identity);
}
由此,在后续获取位置信息时,得到的就是模拟的定位信息了。
addTestProviderLocked
private void addTestProviderLocked(String name, ProviderProperties properties) {
if (mProvidersByName.get(name) != null) {
throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
}
MockProvider provider = new MockProvider(name, this, properties);
addProviderLocked(provider);
mMockProviders.put(name, provider);
//关键函数,此处将GPS_PROVIDER放入到mLastLocation,而mLastLocation是在获取gps定位中存储定位信息的对象
mLastLocation.put(name, null);
mLastLocationCoarseInterval.put(name, null);
}
在其中创建一个MockProvider对象并加入到mMockProviders数组中。MockProvider的路径为:
frameworks/base/services/core/java/com/android/server/location/MockProvider.java
3.2 setTestProviderStatus
public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime) {
try {
mService.setTestProviderStatus(provider, status, extras, updateTime,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
直接调用mService(即LocationManagerService)的setTestProviderStatus:
public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime,
String opPackageName) {
if (!canCallerAccessMockLocation(opPackageName)) {
return;
}
synchronized (mLock) {
MockProvider mockProvider = mMockProviders.get(provider);
if (mockProvider == null) {
throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
}
mockProvider.setStatus(status, extras, updateTime);
}
}
调用到mockProvider的setStatus函数,将状态设为AVAILABLE
3.3 requestLocationUpdates
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
public void requestLocationUpdates(String provider, long minTime, float minDistance,
LocationListener listener, Looper looper) {
checkProvider(provider);
checkListener(listener);
LocationRequest request = LocationRequest.createFromDeprecatedProvider(
provider, minTime, minDistance, false);
requestLocationUpdates(request, listener, looper, null);
}
可以看到调用该函数是需要特定权限的,该函数的作用是设置定位更新的策略,此处传参代表最短每3秒及每10m进行一次更新。
3.4 setTestProviderLocation
调用的是mService的setTestProviderLocation函数:
@Override
public void setTestProviderLocation(String provider, Location loc, String opPackageName) {
if (!canCallerAccessMockLocation(opPackageName)) {
return;
}
synchronized (mLock) {
//从mockProviders里获取GPS对应的mockProvider实例
MockProvider mockProvider = mMockProviders.get(provider);
if (mockProvider == null) {
throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
}
// Ensure that the location is marked as being mock. There's some logic to do this in
// handleLocationChanged(), but it fails if loc has the wrong provider (bug 33091107).
Location mock = new Location(loc);
mock.setIsFromMockProvider(true);
if (!TextUtils.isEmpty(loc.getProvider()) && !provider.equals(loc.getProvider())) {
// The location has an explicit provider that is different from the mock provider
// name. The caller may be trying to fool us via bug 33091107.
EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
provider + "!=" + loc.getProvider());
}
// clear calling identity so INSTALL_LOCATION_PROVIDER permission is not required
long identity = Binder.clearCallingIdentity();
mockProvider.setLocation(mock);
Binder.restoreCallingIdentity(identity);
}
}
mockProvider.setLocation(mock):
public void setLocation(Location l) {
mLocation.set(l);
mHasLocation = true;
if (mEnabled) {
try {
mLocationManager.reportLocation(mLocation, false);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException calling reportLocation");
}
}
}
调用到reportLocation(mLocation, false),即又回到LocationManagerService中,reportLocation的作用是向mLocationHandler发送一个MSG_LOCATION_CHANGED消息,并进行处理。
mLocationHandler接受到消息后调用handleLocationChanged函数再调用到handleLocationChangedLocked函数,在此处才会对定位信息设置为模拟的定位信息,由此,之后由GPS_PROVIDER获取得到的定位信息都是模拟的定位信息。