warren-bank / Android-Mock-Location

Android app that mocks the GPS and Network location providers.
GNU General Public License v2.0
98 stars 20 forks source link

Android 13, cannot start service #14

Open schklom opened 1 year ago

schklom commented 1 year ago

GrapheneOS user, on Pixel 6a, not rooted

type: crash
osVersion: google/bluejay/bluejay:13/TQ2A.230305.008.E1/2023040400:user/release-keys
package: com.github.warren_bank.mock_location:2020003
process: com.github.warren_bank.mock_location
processUptime: 132 + 326 ms

java.lang.RuntimeException: Unable to start service com.github.warren_bank.mock_location.service.LocationService@f469562 with Intent { act=START cmp=com.github.warren_bank.mock_location/.service.LocationService (has extras) }: java.lang.IllegalStateException
    at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4677)
    at android.app.ActivityThread.-$$Nest$mhandleServiceArgs(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2177)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:201)
    at android.os.Looper.loop(Looper.java:288)
    at android.app.ActivityThread.main(ActivityThread.java:7891)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
    at com.android.internal.os.ExecInit.main(ExecInit.java:49)
    at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
    at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:355)
Caused by: java.lang.IllegalStateException
    at android.os.Parcel.createExceptionOrNull(Parcel.java:3029)
    at android.os.Parcel.createException(Parcel.java:3005)
    at android.os.Parcel.readException(Parcel.java:2981)
    at android.os.Parcel.readException(Parcel.java:2923)
    at android.location.ILocationManager$Stub$Proxy.addTestProvider(ILocationManager.java:2169)
    at android.location.LocationManager.addTestProvider(LocationManager.java:2131)
    at android.location.LocationManager.addTestProvider(LocationManager.java:2106)
    at android.location.LocationManager.addTestProvider(LocationManager.java:2080)
    at com.github.warren_bank.mock_location.service.looper.MockLocationProvider.<init>(Unknown Source:40)
    at com.github.warren_bank.mock_location.service.looper.MockLocationProviderManager.startMockingLocationNetwork(Unknown Source:7)
    at com.github.warren_bank.mock_location.service.looper.MockLocationProviderManager.startMockingLocation(Unknown Source:0)
    at com.github.warren_bank.mock_location.service.looper.LocationThread.startThread(Unknown Source:2)
    at com.github.warren_bank.mock_location.service.looper.LocationThreadManager.start(Unknown Source:36)
    at com.github.warren_bank.mock_location.service.LocationService.processIntent(Unknown Source:65)
    at com.github.warren_bank.mock_location.service.LocationService.onStart(Unknown Source:0)
    at com.github.warren_bank.mock_location.service.LocationService.onStartCommand(Unknown Source:0)
    at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4659)
    ... 11 more
Caused by: android.os.RemoteException: Remote stack trace:
    at com.android.internal.util.Preconditions.checkState(Preconditions.java:215)
    at com.android.internal.util.Preconditions.checkState(Preconditions.java:200)
    at com.android.server.location.LocationManagerService.addLocationProviderManager(LocationManagerService.java:350)
    at com.android.server.location.LocationManagerService.getOrAddLocationProviderManager(LocationManagerService.java:342)
    at com.android.server.location.LocationManagerService.addTestProvider(LocationManagerService.java:1319)
warren-bank commented 1 year ago

IllegalStateException is right.. I walked through the internal Android source to try to figure out why this is being called.. and it makes absolutely no sense:

  1. getOrAddLocationProviderManager checks whether an instance of LocationProviderManager has already been added having the specified name
    • one of: _LocationManager.NETWORKPROVIDER, _LocationManager.FUSEDPROVIDER, or _LocationManager.GPSPROVIDER
  2. if none is found, then it:
    • uses the constructor to create a new instance of LocationProviderManager
    • before adding the new instance, checks one more time whether an instance of LocationProviderManager has already been added having the specified name
    • if one is found, then it throws an IllegalStateException
      • this is what you're seeing
      • I just can't figure out how your OS is finding itself in this state..
      • do you also observe this same error when using other GPS mocking apps?
code trace... ```text https://github.com/warren-bank/Android-Mock-Location/blob/service/android-studio-project/Mock-my-GPS/src/main/java/com/github/warren_bank/mock_location/service/looper/MockLocationProvider.java#L39 LocationManager lm = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE); lm.addTestProvider(providerName, false, false, false, false, false, true, true, powerUsage, accuracy); https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android13-platform-release/services/core/java/com/android/server/location/LocationManagerService.java#1306 public void addTestProvider(String provider, ProviderProperties properties, List extraAttributionTags, String packageName, String attributionTag) { final LocationProviderManager manager = getOrAddLocationProviderManager(provider); manager.setMockProvider(new MockLocationProvider(properties, identity, new ArraySet<>(extraAttributionTags))); } https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android13-platform-release/services/core/java/com/android/server/location/LocationManagerService.java#335 private LocationProviderManager getOrAddLocationProviderManager(String providerName) { for (LocationProviderManager manager : mProviderManagers) { if (providerName.equals(manager.getName())) { return manager; } } LocationProviderManager manager = new LocationProviderManager(mContext, mInjector, providerName, mPassiveManager); addLocationProviderManager(manager, null); } https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android13-platform-release/services/core/java/com/android/server/location/LocationManagerService.java#345 private void addLocationProviderManager(LocationProviderManager manager, @Nullable AbstractLocationProvider realProvider) { Preconditions.checkState(getLocationProviderManager(manager.getName()) == null); } https://android.googlesource.com/platform/frameworks/base/+/2dd4825/core/java/com/android/internal/util/Preconditions.java#72 public static void checkState(final boolean expression) { if (!expression) { throw new IllegalStateException(); } } https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android13-platform-release/services/core/java/com/android/server/location/LocationManagerService.java#313 LocationProviderManager getLocationProviderManager(String providerName) { if (providerName == null) { return null; } for (LocationProviderManager manager : mProviderManagers) { if (providerName.equals(manager.getName())) { return manager; } } return null; } https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android13-platform-release/services/core/java/com/android/server/location/LocationManagerService.java#394 void onSystemThirdPartyAppsCanStart() { //... addLocationProviderManager(networkManager, networkProvider); addLocationProviderManager(fusedManager, fusedProvider); addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider()); } https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android13-platform-release/services/core/java/com/android/server/location/provider/LocationProviderManager.java#1433 public LocationProviderManager(Context context, Injector injector, String name, @Nullable PassiveLocationProviderManager passiveManager) { mName = Objects.requireNonNull(name); } https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android13-platform-release/services/core/java/com/android/server/location/provider/LocationProviderManager.java#1517 public String getName() { return mName; } ```
warren-bank commented 1 year ago

also interesting is how your observations are very likely the underlying cause for issue #11.. which pertains exclusively to Pixel users (versions 6 and 7).

They report that the service needs to be restarted (ex: start/stop/start).. for it to work on the Pixel.

Does this workaround also "fix" your issue?

schklom commented 1 year ago

Thank you for the fast reply :)

My phone behaves the same with the GPS mock location app Private Location (com.wesaphzt.privatelocation). Check out https://github.com/wesaphzt/privatelocation/issues/43#issuecomment-1501333976 for the logs. The service does not start either, but is caused by a SecurityException instead.

With your app selected in Developer Options, the app crashes as soon as I click on Start. When I open the app again, the button available is Start, not Stop. Clicking on it crashes the app. Force-closing the app then opening it again does not change anything. My location on e.g. Google Maps is always my true location.

One maybe interesting thing is that sometimes, after clicking the Start button, the app crashes but there is the popup "Mock My GPS click to open" at the top of my screen for a few seconds. The location is not changed at that time either, but I did not expect the app to crash and still show me the popup.

When your app is not selected in the Developer Settings, it does not crash at all after starting the service, but Google Maps still shows my true location.

Same behavior with Private Location.

Could it be an Android bug? Your app works well on my Android 11.

warren-bank commented 1 year ago

I wouldn't say that it's an Android bug.. because the app works fine on all versions of AOSP (including Android 13) on every device that I've ever tested.

I'm more inclined to say that there's something unusual about the Pixel, because I've only ever received inexplicable error reports about app crashes from users who have it installed on various versions of a Pixel. I don't own one.. so I haven't been able to diagnose. I was hoping that other (more popular) apps that have similar behavior could diagnose and fix the problem.. for themselves.. but also applicable here. I haven't looked in a while.. maybe that has already happened.. idk.. I'll check.

warren-bank commented 1 year ago
schklom commented 1 year ago

Yes, some of the log is exactly the same.

Strangely, the app works well with CalyxOS on the Pixel 6 Pro. I only have one Pixel phone and it is my daily driver, so I don't want to install CalyxOS to see if it works.

If I find an emulator for Pixel phones, I will try both apps.

Let me know if you want me to test a version of your app on my Pixel 6a before releasing it publicly.

warren-bank commented 1 year ago

That would be super helpful!

If.. my summary of the AOSP source is correct, then a super janky workaround would be to:

warren-bank commented 1 year ago

OK. v2.2.1 (this commit) tests this workaround. All it does is add a retry counter when attempting to add a provider.. and if it fails this number of times.. then it quietly stops trying to mock that particular provider. Currently, the retry count is set to 3.. which seems more than generous.

I'd really appreciate it if you could test whether (or not) it has any effect on your ability to use the app on your Pixel device.

schklom commented 1 year ago

It works! Thank you so much :)

All it does is add a retry counter when attempting to add a provider

Speaking of providers, could this problem be from my phone lacking Google Play Services? I remember that Google made a fused provider to locate the phone with the network or something. GrapheneOS by default does not have Google Play Services, and advises to install them on the Work Profile. If yes, it would make that it works on CalyxOS because it uses MicroG.

warren-bank commented 1 year ago

seriously?.. that worked?! :smiley:

On the one hand, looking at the AOSP code.. it kinda makes sense that it would. On the other hand, the cause for this error happening (only on Pixels) is still inexplicable.. and this workaround is (for lack of a better word).. dumb.

Well.. I'm very glad it's now working on Pixel!

To answer your question about Google Play Services.. definitely not related. My code doesn't use that at all. It only uses what is available from AOSP. When mocking is enabled and working.. Google's fused provider will also get its location coordinates from the mocked providers. Not so long ago, I received a feature request to add support for use (of this app) with UnifiedNlp.. which is typically installed as a part of microG.. which (forgive me if this is an over-simplification) aims to reimplement many of Google's services with open-source code.. UnifiedNlp being the equivalent for Google's Location Service. That's essentially the purpose for the 2nd (optional) APK in my releases.. it's a plugin for UnifiedNlp that depends upon my main app.. and allows UnifiedNlp to receive mock location data without requiring that my main app be configured (in Developer settings) as the default app to use for location mocking.

schklom commented 1 year ago

this workaround is (for lack of a better word).. dumb

If I get it right, all that changed is that the mock code is run at most 3 times instead of just once? I agree, this workaround is dumb. But hey, as long as it works :laughing:

warren-bank commented 1 year ago

you got it right.. if at first you don't succeed.. try.. try.. try again.

well, thanks for providing the stack trace from logcat.. without that, I would never have tried this.

schklom commented 1 year ago

I have no idea how you thought of this fix by reading the log, but I'm not sure I want to know x)

I don't think I have anything else to add at this point. I'm leaving the issue opened in case you prefer to close it when the fix is released in public, close it if you don't :)

I will link this issue to Private Location and Fake Traveler, they look similar.

thestinger commented 1 year ago

@warren-bank Non-Pixel phones rarely update to the latest AOSP releases. There are monthly and quarterly releases of AOSP, not only yearly. They don't increment the main user-facing version number but the current release as of April is essentially 13.2.1 (first monthly update of QPR2) while other vendors are on the initial 13.0.0. April release of Android 13 QPR2 is the only actively developed release of Android. Android 11, 12 and 13 receive backports of a subset of the security patches, which are listed in the Android Security Bulletins. Most vendors don't bother actively shipping the monthly/quarterly releases and just stick to the one they started with for a long time or even for the whole release cycle. You're comparing devices running an old version of Android 13 to current Android 13.

warren-bank commented 1 year ago

@thestinger Hi. Since you're so much more familiar with the AOSP code base, so I'll defer to you and ask if you can figure out why the Pixel 6+ is throwing this IllegalStateException?

In the 2nd comment in this issue, I added an expandable "code trace" section that refers to AOSP code at the tag: android13-platform-release. Is there a better (more recent) tag that I should be looking at.. to see what has changed? My interpretation of the code in this tag is given in my comment.. and I can't see how Preconditions.checkState() in addLocationProviderManager() could possibly get called.. by using addTestProvider() as an entry point.

Maybe the code in a different tag will call Preconditions.checkState() more often.. in ways that I can make some sense of. Despite not knowing how this precondition is failing.. the assertion here is that it will only fail once.. and by trying a 2nd time.. it will eventually succeed. I'm not sure if that's what's actually happening.. or if the particular provider silently fails to initialize after a certain number of retries.. and the app continues on its merry way without it.

Any insight would be appreciated. Your expertise is always welcome.

update: Would the android13-qpr2-s1-release tag correspond to v13.2.1 that you mentioned?

thestinger commented 1 year ago

That's not a correct tag for the OS. Bear in mind that tags are made across the entire source tree for everything built from AOSP including platform-tools, the SDK API declarations, etc.

The latest Android release for April is android-13.0.0_r38, which is essentially Android 13.2.1 but they don't update the user facing version for each quarterly and monthly release.

Android 11, 12 and 13 receive a subset of security patches backported. There are not actively developed LTS release branches of the OS. The only maintained version of the OS is Android 13 QPR2 which was first released in March 2023 and now has an initial monthly release for April.

warren-bank commented 1 year ago

update: got it.. I'll compare this code and maybe it'll give some added insight.

thanks for the tag.. I hate to admit it, but I can't figure out AOSP tagging.. at all

thestinger commented 1 year ago

@warren-bank

update: Would the android13-qpr2-s1-release tag correspond to v13.2.1 that you mentioned?

The android13-qpr2-release branch corresponds to QPR2 (Quarterly Platform Release 2), i.e. what is essentially 13.2.x. The latest generic tag is android-13.0.0_r38 for TQ2A.230405.003. There are often device/carrier specific releases for Pixels shipping certain changes early and you're looking at an old branch for one of those.

thestinger commented 1 year ago

@warren-bank android13-release was the initial Android 13 stable branch. It hasn't been maintained since Android 13 QPR1 was released in December, which is android13-qpr1-release. That hasn't been maintained since Android 13 QPR2 was released in March which is android13-qpr2-release. Those are branches, not tags, and they receive monthly releases until the next quarterly or yearly release which replaces them. There are no LTS branches, only a single actively maintained branch. Bear in mind that there are device/carrier specific branches/tags shipping some changes early. Also bear in mind that there are many other things built from the overall AOSP sources such as the SDK, emulator, platform tools, kernels and so on. It's not correct to build the OS from a branch/tag existing to provide some component like platform-tools or vice versa.

warren-bank commented 1 year ago

anecdotally, the user who opened this issue says that this minor update works on his Pixel 6a.. all that it does is catch the IllegalStateException and try again.. which we've been joking is a "dumb" fix.. but we can't argue with results.

thestinger commented 1 year ago

The reason you think there's Pixel specific behavior is just because other devices aren't shipping current Android 13 but rather a release from months ago with a subset of backported security patches applied combined with their own substantial changes and perhaps certain backports.

warren-bank commented 1 year ago

Would that also hold true for LineageOS nightly releases?

thestinger commented 1 year ago

It depends on the device and how far behind they are at a given moment. After all, it takes them half a year to migrate to new yearly releases. They do follow along with the monthly and quarterly releases when they're on the latest yearly release, with potentially days or in some cases weeks of delay.

thestinger commented 1 year ago

You should be able to replicate the issue on up-to-date LineageOS 20 as long as the release for the device you test is up-to-date enough.

warren-bank commented 1 year ago

well.. I feel that I've taken up enough of your time. I really appreciate being able to pick your brain.

I'll dig into the more current code.. and see what I see.

On the up-side.. if this bug does eventually find its way into all newer devices.. then it'll get more attention from both users and developers of other location mocking apps.. and we'll be more likely to get to the bottom of its root cause once there's more pressure to do so.

Thanks again :smiley:

warren-bank commented 1 year ago

just a quick update (for anybody interested in tracing the cause for the error).. unfortunately, an inspection of the code in tag _android-13.0.0r38 didn't add any insight.. the relevent methods are entirely identical.. here is a copy of the earlier code trace w/ updated links:

code trace... ```text https://github.com/warren-bank/Android-Mock-Location/blob/service/v02.02.01/android-studio-project/Mock-my-GPS/src/main/java/com/github/warren_bank/mock_location/service/looper/MockLocationProvider.java#L43 private void startup(LocationManager lm, int powerUsage, int accuracy, int maxRetryCount, int currentRetryCount) { lm.addTestProvider(providerName, false, false, false, false, false, true, true, powerUsage, accuracy); } https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-13.0.0_r38/services/core/java/com/android/server/location/LocationManagerService.java#1306 public void addTestProvider(String provider, ProviderProperties properties, List extraAttributionTags, String packageName, String attributionTag) { final LocationProviderManager manager = getOrAddLocationProviderManager(provider); manager.setMockProvider(new MockLocationProvider(properties, identity, new ArraySet<>(extraAttributionTags))); } https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-13.0.0_r38/services/core/java/com/android/server/location/LocationManagerService.java#335 private LocationProviderManager getOrAddLocationProviderManager(String providerName) { for (LocationProviderManager manager : mProviderManagers) { if (providerName.equals(manager.getName())) { return manager; } } LocationProviderManager manager = new LocationProviderManager(mContext, mInjector, providerName, mPassiveManager); addLocationProviderManager(manager, null); } https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-13.0.0_r38/services/core/java/com/android/server/location/LocationManagerService.java#345 private void addLocationProviderManager(LocationProviderManager manager, @Nullable AbstractLocationProvider realProvider) { Preconditions.checkState(getLocationProviderManager(manager.getName()) == null); } https://android.googlesource.com/platform/frameworks/libs/modules-utils/+/refs/tags/android-13.0.0_r38/java/com/android/internal/util/Preconditions.java#199 https://android.googlesource.com/platform/frameworks/libs/modules-utils/+/refs/tags/android-13.0.0_r38/java/com/android/internal/util/Preconditions.java#213 public static void checkState(final boolean expression) { if (!expression) { throw new IllegalStateException(null); } } https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-13.0.0_r38/services/core/java/com/android/server/location/LocationManagerService.java#313 LocationProviderManager getLocationProviderManager(String providerName) { if (providerName == null) { return null; } for (LocationProviderManager manager : mProviderManagers) { if (providerName.equals(manager.getName())) { return manager; } } return null; } https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-13.0.0_r38/services/core/java/com/android/server/location/LocationManagerService.java#394 void onSystemThirdPartyAppsCanStart() { //... addLocationProviderManager(networkManager, networkProvider); addLocationProviderManager(fusedManager, fusedProvider); addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider()); } https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-13.0.0_r38/services/core/java/com/android/server/location/provider/LocationProviderManager.java#1434 public LocationProviderManager(Context context, Injector injector, String name, @Nullable PassiveLocationProviderManager passiveManager) { mName = Objects.requireNonNull(name); } https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-13.0.0_r38/services/core/java/com/android/server/location/provider/LocationProviderManager.java#1518 public String getName() { return mName; } ```
warren-bank commented 9 months ago

just a quick update.. I happened to notice that GrapheneOS has (quietly) acknowledged that this was a bug in their code, and has since fixed it.

related issues:

related pull requests:

thestinger commented 9 months ago

@warren-bank

I happened to notice that GrapheneOS has (quietly) acknowledged that this was a bug in their code, and has since fixed it.

No, that's not correct.

intended to fix this issue, but creates a secondary issue

This change fixed an Android app compatibility issue where not having a network location provider (which is meant to be supported) reduces app compatibility unnecessarily:

https://github.com/GrapheneOS/platform_frameworks_base/pull/380

This extends app compatibility further. Some apps using the network location provider weren't happy with the initial change alone and while it extended app compatibility, it also broke a smaller subset of apps which were already working:

https://github.com/GrapheneOS/platform_frameworks_base/pull/387

If your app doesn't work without these changes, it doesn't work on AOSP without Play services or another network location provider included in the OS. AOSP doesn't have network location included, which is not a bug and apps are not meant to break.

warren-bank commented 9 months ago

@thestinger

I apologize Daniel. I assumed that the (first 2) issues share a common cause. Suppose not.. my bad; didn't mean any offense. Thanks for the detailed explanation.

thestinger commented 9 months ago

Our changes may have fixed this issue but if that's the case, it was caused by an AOSP issue where it's unnecessarily harsh to apps which try to use network location when there isn't network location available.

thestinger commented 9 months ago

No need to apologize.