In navigation test scenarios often need location simulation and route playback, recorded through the LocationManager.setTestProviderLocation()
method to achieve simulated status, if the application to be tested does not support TestProviderLocation
simulation location input, you can consider starting with the HAL layer, the hook the system’s default GPS implementation.
1, Android simulation permission open configuration
In the version of Android 6.0 or below, you need to check the switch of simulated positioning in the settings, in 6.0 or above it is changed to select the application of simulated positioning, the corresponding opening configuration is different, the same is in AndroidManifest.xml
both need to configure the following two permissions.
1
2
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
1)Android 6.0 or below turn on the analog positioning switch
1
|
Settings.Secure.putInt(getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 1);
|
To enable mock positioning in this way, you need to configure the following system permissions in AndroidManifest.xml
, and the application also needs to be signed by the system, which can’t be achieved by this way for non-system applications.
1
|
ndroid:sharedUserId="android.uid.system"
|
1
|
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
|
This operation can be configured to bypass system privilege configuration by means of the adb shell command:
1
|
adb shell settings put secure mock_location 1
|
2) Android 6.0 or above code configuration to choose the application of analog positioning
In Android version above 6.0, you need to set the mock_location permission of the specified package name to allow.
1
2
3
4
5
6
7
8
9
10
11
|
try {
String mockLocationPkgName = getPackageName();
PackageManager mPackageManager = getPackageManager();
final ApplicationInfo ai = mPackageManager.getApplicationInfo(
mockLocationPkgName, PackageManager.MATCH_DISABLED_COMPONENTS);
AppOpsManager mAppsOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mAppsOpsManager.setMode(AppOpsManager.OPSTR_MOCK_LOCATION, ai.uid,
mockLocationPkgName, AppOpsManager.MODE_ALLOWED);
} catch (PackageManager.NameNotFoundException e) {
/* ignore */
}
|
Also in AndroidManifest.xml you need to configure the following permissions, unfortunately this way also needs to be compiled with the system signature and source code, only the system level application can be used.
1
|
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
|
In order to display the simulated positioning application in the simulated positioning option of the settings screen, you need to configure the ACCESS_MOCK_LOCATION permission.
1
|
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
|
The above configuration can also be implemented via the adb shell command, with the <package>
parameter replaced by the package name of your own application.
1
|
adb shell appops set <package> android:mock_location allow
|
Turn off simulated positioning privileges with the following command.
1
|
adb shell appops set <package> android:mock_location deny
|
To query which applications have simulated location permissions turned on, use the following command.
1
|
adb shell appops query-op android:mock_location allow
|
The package name list parameter of the application will be output after execution.
2, Android simulation positioning implementation
1) Analog positioning switch check
The first is the code first determine whether the simulation positioning permission is on, 6.0 or above can only be determined by adding a positioning listener if there is an exception.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
boolean mockPermission = false;
if (Build.VERSION.SDK_INT <= 22) {//below 6.0
mockPermission = Settings.Secure.getInt(getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) == 1;
} else {
try {
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
mockPermission = false;
return;
}
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
}
@Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
@Override
public void onProviderEnabled(String s) {
}
@Override
public void onProviderDisabled(String s) {
}
});
mockPermission = true;
} catch (SecurityException e) {
mockPermission = false;
}
}
|
You can see the source code implementation of adding location listener, and take the source code implementation of Android 7.0 as reference.
After LocationManager calls the interface, the final call is to LocationManagerService
In the requestLocationUpdates
method or getLastLocation
method, the checkPackageName
method is called
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private void checkPackageName(String packageName) {
if (packageName == null) {
throw new SecurityException("invalid package name: " + packageName);
}
int uid = Binder.getCallingUid();
String[] packages = mPackageManager.getPackagesForUid(uid);
if (packages == null) {
throw new SecurityException("invalid UID " + uid);
}
for (String pkg : packages) {
if (packageName.equals(pkg)) return;
}
throw new SecurityException("invalid package name: " + packageName);
}
|
If the application calling the requestLocationUpdates
method does not have the permission to simulate location, it will throw a SecurityException
exception. In addition, requestLocationUpdates
needs to be called in the main thread, if it is called in a sub-thread, you also need to pass a looper
parameter, otherwise it will report an error when instantiating ListenerTransport
.
Look at the constructor of ListenerTransport
, if you add a listener in a sub-thread and don’t pass a Loop, an exception is thrown when initializing mListenerHandler
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
ListenerTransport(LocationListener listener, Looper looper) {
mListener = listener;
if (looper == null) {
mListenerHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
_handleMessage(msg);
}
};
} else {
mListenerHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
_handleMessage(msg);
}
};
}
}
|
Then add the corresponding Provider, set the switch state to true, and configure the state to AVAILABLE.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
LocationProvider provider = locationManager.getProvider(LocationManager.GPS_PROVIDER);
if (provider != null) {
locationManager.addTestProvider(
provider.getName()
, provider.requiresNetwork()
, provider.requiresSatellite()
, provider.requiresCell()
, provider.hasMonetaryCost()
, provider.supportsAltitude()
, provider.supportsSpeed()
, provider.supportsBearing()
, provider.getPowerRequirement()
, provider.getAccuracy());
} else {
locationManager.addTestProvider(LocationManager.GPS_PROVIDER, true, true, false, false, true, true,
true, Criteria.POWER_LOW, Criteria.ACCURACY_FINE);
}
locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);
locationManager.setTestProviderStatus(LocationManager.GPS_PROVIDER, LocationProvider.AVAILABLE, null, System.currentTimeMillis());
|
2) setTestProviderLocation call
The call code example is as follows. The parameters of latitude, longitude, vehicle speed, positioning accuracy, bearing and altitude are set according to the actual requirements.
1
2
3
4
5
6
7
8
9
10
11
12
|
Location loc = new Location(LocationManager.GPS_PROVIDER);
loc.setLongitude(24.522301);
loc.setLatitude(118.115756);
loc.setSpeed(60);
loc.setAccuracy(Criteria.ACCURACY_HIGH);
loc.setBearing(0);
loc.setAltitude(0);
loc.setTime(System.currentTimeMillis());
if (Build.VERSION.SDK_INT >= 17) {
loc.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER,loc);
|
Let’s see why we need to set elapsedRealtimeNanos
and time
parameters. Below SDK version 17, Location
(Android 4.1.1) did not have setElapsedRealtimeNanos
method, starting from SDK version 17, Location
(Android 4.2) added this method, when calling setTestProviderLocation
to set the location information, the Android SDK version 17 or above will do the verification of the location parameter completeness, the version below 17 will do the complement automatically, and the version from 17 will throw the exception directly.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public void setTestProviderLocation(String provider, Location loc) {
if (!loc.isComplete()) {
IllegalArgumentException e = new IllegalArgumentException(
"Incomplete location object, missing timestamp or accuracy? " + loc);
if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN) {
// just log on old platform (for backwards compatibility)
Log.w(TAG, e);
loc.makeComplete();
} else {
// really throw it!
throw e;
}
}
try {
mService.setTestProviderLocation(provider, loc, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
|
Then look at the makeComplete
and isComplete
logic processing in the Location
class. isComplete
has provider
, Accuracy
, mTime
and mElapsedRealtimeNanos judgments inside.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@SystemApi
public void makeComplete() {
if (mProvider == null) mProvider = "?";
if (!hasAccuracy()) {
mFieldsMask |= HAS_ACCURACY_MASK;
mAccuracy = 100.0f;
}
if (mTime == 0) mTime = System.currentTimeMillis();
if (mElapsedRealtimeNanos == 0) mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
}
@SystemApi
public boolean isComplete() {
if (mProvider == null) return false;
if (!hasAccuracy()) return false;
if (mTime == 0) return false;
if (mElapsedRealtimeNanos == 0) return false;
return true;
}
|
The above is a single point of the simulation of location implementation, if you want to achieve route playback simulation, just request the route location point array data in the background, after every 1 second refresh call setTestProviderLocation
interface can be.
After the simulated location operation, you need to remove the simulated location object to avoid the location information or use the parameters of the simulated location interface, if not removed the next time you use it and call the Provider
with the same name will also throw an exception.
1
|
locationManager.removeTestProvider(LocationManager.GPS_PROVIDER);
|
Reference https://www.chenwenguan.com/android-mock-location/