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獲取得到的定位信息都是模擬的定位信息。