felipe.rce
felipe.rce

Reputation: 237

Test provider for mock location not working on Android 10

Mock location are not working on Android 10, crash when addTestProvider are called:

2020-11-30 00:25:16.855 13189-13256/br.com.tupinikimtecnologia.fakegpslocation E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-2
    Process: br.com.tupinikimtecnologia.fakegpslocation, PID: 13189
    java.lang.IllegalArgumentException: Provider "gps" already exists
        at android.os.Parcel.createException(Parcel.java:2075)
        at android.os.Parcel.readException(Parcel.java:2039)
        at android.os.Parcel.readException(Parcel.java:1987)
        at android.location.ILocationManager$Stub$Proxy.addTestProvider(ILocationManager.java:2022)
        at android.location.LocationManager.addTestProvider(LocationManager.java:1461)
        at br.com.tupinikimtecnologia.fakegpslocation.feature.mock.view.MapsActivity.setMock(MapsActivity.kt:100)
        at br.com.tupinikimtecnologia.fakegpslocation.feature.mock.view.MapsActivity.access$setMock(MapsActivity.kt:34)
        at br.com.tupinikimtecnologia.fakegpslocation.feature.mock.view.MapsActivity$onCreate$1.invokeSuspend(MapsActivity.kt:65)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
     Caused by: android.os.RemoteException: Remote stack trace:
        at com.android.server.LocationManagerService.addTestProvider(LocationManagerService.java:3536)
        at android.location.ILocationManager$Stub.onTransact(ILocationManager.java:958)
        at android.os.Binder.execTransactInternal(Binder.java:1021)
        at android.os.Binder.execTransact(Binder.java:994)
2020-11-30 00:25:16.857 13189-13256/br.com.tupinikimtecnologia.fakegpslocation I/Process: Sending signal. PID: 13189 SIG: 9

Code:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)

        val mapFragment = supportFragmentManager
                .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)

        ActivityCompat.requestPermissions(this, arrayOf<String>(Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.ACCESS_COARSE_LOCATION), ACCESS_LOCATION_CODE)


        mLocationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager

        if (isMockLocationEnabled()) {
            GlobalScope.launch {
                while (true) {
                    setMock(LocationManager.GPS_PROVIDER, 39.0293211, 125.6020307);
                    setMock(LocationManager.NETWORK_PROVIDER, 39.0293211, 125.6020307);
                }
            }
        } else {
            AlertDialog.Builder(this)
                    .setTitle(R.string.dev_settings_title_dialog)
                    .setMessage(R.string.dev_settings_msg_dialog)
                    .setPositiveButton(android.R.string.ok) { dialog, which ->
                        Toast.makeText(this, R.string.dev_settings_msg_toast, Toast.LENGTH_LONG).show()
                        startActivity(Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS))
                    }
                    .show()

        }

    }
    

    private fun isMockLocationEnabled(): Boolean {
        val isMockLocation: Boolean
        isMockLocation = try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                val opsManager = getSystemService(APP_OPS_SERVICE) as AppOpsManager
                Objects.requireNonNull(opsManager).checkOp(AppOpsManager.OPSTR_MOCK_LOCATION, Process.myUid(), BuildConfig.APPLICATION_ID) === AppOpsManager.MODE_ALLOWED
            } else {
                Settings.Secure.getString(contentResolver, "mock_location") != "0"
            }
        } catch (e: Exception) {
            return false
        }
        return isMockLocation
    }

    private fun setMock(provider: String, latitude: Double, longitude: Double) {
        mLocationManager?.addTestProvider(
                provider,
                false,
                false,
                false,
                false,
                false,
                true,
                true,
                android.location.Criteria.POWER_LOW,
                android.location.Criteria.ACCURACY_FINE
        )

        val newLocation = Location(provider)
        newLocation.latitude = latitude
        newLocation.longitude = longitude
        newLocation.altitude = 3.0
        newLocation.time = System.currentTimeMillis()
        newLocation.speed = 0.01f
        newLocation.bearing = 1f
        newLocation.accuracy = 3f
        newLocation.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            newLocation.bearingAccuracyDegrees = 0.1f
            newLocation.verticalAccuracyMeters = 0.1f
            newLocation.speedAccuracyMetersPerSecond = 0.01f
        }
        mLocationManager?.setTestProviderEnabled(provider, true)
        mLocationManager?.setTestProviderLocation(provider, newLocation)
    }

Crash on:

mLocationManager?.addTestProvider(
                provider,
                false,
                false,
                false,
                false,
                false,
                true,
                true,
                android.location.Criteria.POWER_LOW,
                android.location.Criteria.ACCURACY_FINE
        )

It is crashing only on Android 10, I tried to change the provider name, but it don't work, all names are crashing. I think it is not a permission issue, because I tested with ACCESS_MOCK_LOCATION, ACCESS_FINE_LOCATION and INTERNET enabled

Upvotes: 6

Views: 3109

Answers (2)

blackkara
blackkara

Reputation: 5052

The first thing, when you add test provider (i.e "my_provider"), it's actually interpreted real-like provider as "gps", but for test purposes. So if you ask to android, for locations sources, then it could tell you;

"gps", "network", "some_provider"

So, why you got exception ?

Actually you are already tend to get exceptions, but it's handled by the system. If you try to add already known provider like "gps" as test provider, then the system tries to add it into location sources. So it's better to wrap mocking operations for known providers with try catch

To mock gps provider without exception, try this

  public void mockGps(Location location) throws SecurityException {
    location.setProvider(GPS_PROVIDER);
    try{
        // @throws IllegalArgumentException if a provider with the given name already exists
        mLocationManager.addTestProvider(GPS_PROVIDER, false, false, false, false, false, true, true, 0, 5);
    }  catch (IllegalArgumentException ignored){}

    try{
        // @throws IllegalArgumentException if no provider with the given name exists
        mLocationManager.setTestProviderEnabled(GPS_PROVIDER, true);
    } catch (IllegalArgumentException ignored){
        mLocationManager.addTestProvider(GPS_PROVIDER, false, false, false, false, false, true, true, 0, 5);
    }

    try{
        // @throws IllegalArgumentException if no provider with the given name exists
        mLocationManager.setTestProviderLocation(GPS_PROVIDER, location);
    } catch (IllegalArgumentException ignored){
        mLocationManager.addTestProvider(GPS_PROVIDER, false, false, false, false, false, true, true, 0, 5);
        mLocationManager.setTestProviderEnabled(GPS_PROVIDER, true);
        mLocationManager.setTestProviderLocation(GPS_PROVIDER, location);
    }
}

Upvotes: 4

Martin Zeitler
Martin Zeitler

Reputation: 76639

This is not really coming from the mocking, but the LocationManager's .addTestProvider(). Adding the same one test-provider twice will in every case not work out, according to the source code. For later API levels, just use .setTestProviderLocation(String provider, Location loc) instead of trying to add that mocked provider with the duplicate name; or at least remove the existing one, before trying to add another one with the same name.

Upvotes: 3

Related Questions