I am developing an android app, which will receive the current location and display it on the map with a marker and then will keep updating that in regular intervals. I followed the following tutorials:
Google Maps Android API v2
Retrieving the Current Location
Receiving Location Updates
I wanted to test my app with mock locations so I followed this tutorial:
Testing Using Mock Locations
However, because of Google's confusing documentation despite my best attempts the first approach failed. I was having this issue:
Android LocationClient mock location not set
Then, I decided to use the mock location provider app and discovered that even though the manifest of the downloaded app has android:minSdkVersion="7", it has a SendMockLocationService.java file which has two function calls elapsedRealtimeNanos() and setElapsedRealtimeNanos() which were added in API Level 17. I need to run this mock location provider app in a device with API Level 8. So, I have changed the line:
elapsedTimeNanos = SystemClock.elapsedRealtimeNanos();
to
elapsedTimeNanos = SystemClock.elapsedRealtime()*1000000;
It is not clear to me how to change this line:
mockLocation.setElapsedRealtimeNanos(elapsedTimeNanos);
How can I change the above-mentioned line so that the mock location provider app becomes compatible with API Level 8?
I updated the sample project you referenced to build with gradle this evening, and noticed the same issue with the use of setElapsedRealtimeNanos, introduced in API level 17. The app worked fine on my 4.4 device, but would crash with a NoSuchMethodError on my 4.1 device.
I went digging in the source code of the location class to see exactly what changed for API 17. Here's the corresponding diff:
https://android.googlesource.com/platform/frameworks/base/+/2eeeec248a38ff33999c83f4b8d5bab7d50e79d2%5E%21/
If you search for usages of the newly-introduced field mElapsedRealtimeNano, you'll see that its value is only queried in a single spot (+ retained to indicate newly-inserted lines):
+ public boolean isComplete() {
+ if (mProvider == null) return false;
+ if (!mHasAccuracy) return false;
+ if (mTime == 0) return false;
+ if (mElapsedRealtimeNano == 0) return false;
+ return true;
+ }
The newly-introduced notion of a Location being 'complete' is also only used in a single spot:
public void setTestProviderLocation(String provider, Location loc) {
+ if (!loc.isComplete()) {
+ if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN) {
+ // for backwards compatibility, allow mock locations that are incomplete
+ Log.w(TAG, "Incomplete Location object", new Throwable());
+ loc.makeComplete();
+ } else {
+ throw new IllegalArgumentException(
+ "Location object not complete. Missing timestamps or accuracy?");
+ }
+ }
+
// original setTestProviderLocation code is then executed here
}
Since this validation is missing from the Location class before API 17, the app should work just fine on all API levels >= 7 if you wrap both calls to SystemClock.elapsedRealtimeNanos() in version checks, i.e.
elapsedTimeNanos = SystemClock.elapsedRealtimeNanos();
should be replaced by
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
elapsedTimeNanos = SystemClock.elapsedRealtimeNanos();
}
and
mockLocation.setElapsedRealtimeNanos(elapsedTimeNanos);
should be replaced by
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
mockLocation.setElapsedRealtimeNanos(elapsedTimeNanos);
}
within the SendMockLocationService class. Android Studio will complain that elapsedTimeNanos may not have been initialized - you can safely initialize it with value 0 to resolve this.
With these changes, the app runs fine on my 4.1 device. Let me know if you have any problems on older versions of Android.
Edit: repository for my updated version of the Google sample app.
Related
I have an application (call it A) that runs a service that mocks the location obtained from a bluetooth GPS in order to use it in another application (call it B):
A (with service that gets bluetooth GPS location)
Mock location in android system
B (get location from Android System)
Everything works on Android 7.0, but in Android 8.0 (Oreo) the application B does not read the location obtained from the bluetooth, that is, I think, beacuse of a problem in mocking the location, because the log always prints this line:
E PassiveLocationListener_FLP: isFromMockProvider, return
The code I'm using to mock the location is:
private void changeToMockLocation() {
Log.i(TAG, "changeToMockLocation()");
Location newLocation = new Location(PROVIDER_NAME);
newLocation.setLatitude(mNMEAData.getLatitude());
newLocation.setLongitude(mNMEAData.getLongitude());
newLocation.setAccuracy(mNMEAData.getAccuracy());
newLocation.setTime(System.currentTimeMillis());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
newLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
// Set mock location.
if (ActivityCompat.checkSelfPermission(
mContext, android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
try {
mLocationManager.setTestProviderLocation(PROVIDER_NAME, newLocation);
} catch (Exception e) {
Log.e(TAG, "Error while setting test provider location: " + e);
}
}
}
The thing is, I don't see the exception printed, so I think the process is right but is like Android doesn't allow me to do so for some reason I can't figure out.
In the manifest I declared the ALLOW_MOCK_LOCATION permission, and the app is allowed to mock location in Developer Settings.
What am I doing wrong?
Thanks in advance and have a nice day.
There seems to be a bug in the (Samsung?) Developer Settings screen in Oreo. I also had my app set up as mock provider and with the permission in the manifest. For me the problem was solved by removing the app as mock location provider and adding it back again:
Developer options -> Mock location app -> No apps
Developer options -> Mock location app -> Your app
I still see these messages in the logcat though:
E/PassiveLocationListener_FLP: isFromMockProvider, return
I/LocationManagerService: remove xxxxxxx
I/LocationManagerService: removeUpdates, receiver.requestedID = xxxxxxx, receiver.mIdentity.mPid = 2908, receiver.mIdentity.mUid = 1000
D/SLocation: removeGpsStatusListener
D/ListenerMonitor_FLP: removeListener, not existed listener android, xxxxxxx in mListenerIdMap
E/RequestManager_FLP: [LocationManagerService] Location remove xxxxxxx from system
I'm trying to set some protection against people using mock locations to manipulate my app. I realise that it's impossible to prevent 100%... I'm just trying to do what I can.
The app uses Google location services (part of play services) in its main activity.
The onLocationChanged() method is:
public void onLocationChanged(Location location) {
this.mCurrentLocation = location;
if (Build.VERSION.SDK_INT > 17 && mCurrentLocation.isFromMockProvider()) {
Log.i(TAG, "QQ Location is MOCK");
// TODO: add code to stop app
// otherwise, currently, location will never be updated and app will never start
} else {
Double LAT = mCurrentLocation.getLatitude();
Double LON = mCurrentLocation.getLongitude();
if ((LAT.doubleValue() > 33.309171) || (LAT.doubleValue() < 33.226442) || (LON.doubleValue() < -90.790165) || (LON.doubleValue() > -90.707081)) {
buildAlertMessageOutOfBounds();
} else if (waiting4FirstLocationUpdate) {
Log.i(TAG, "YYY onLocationChanged() determines this is the FIRST update.");
waiting4FirstLocationUpdate = false;
setContentView(R.layout.activity_main);
startDisplayingLists();
}
}
}
The location services work perfectly and all is well with the app in general, but when I run the app in an emulator with Android Studio (Nexus One API 23), and I set the location using extended controls (mock), the app just continues to work as normal, and so it seems that the condition:
if (Build.VERSION.SDK_INT > 17 && mCurrentLocation.isFromMockProvider())
Is returning false.
This doesn't make any sense to me. Does anyone know why this would happen?
Thanks!
The short answer: .isFromMockProvider is unreliable. Some fake locations are not properly detected as such.
I have spent an extensive amount of time researching this and written a detailed blog post about it.
I also spent time to find a solution to reliably suppress mock locations across all recent/relevant Android versions and made a utility class, called the LocationAssistant, that does the job.
In a nutshell (using the aforementioned LocationAssistant):
Set up permissions in your manifest and Google Play Services in your gradle file.
Copy the file LocationAssistant.java to your project.
In the onCreate() method of your Activity, instantiate a LocationAssistant with the desired parameters. For example, to receive high-accuracy location updates roughly every 5 seconds and reject mock locations, call new LocationAssistant(this, this, LocationAssistant.Accuracy.HIGH, 5000, false). The last argument specifies that mock locations shouldn't be allowed.
Start/stop the assistant with your Activity and notify it of permission/location settings changes (see the documentation for details).
Enjoy location updates in onNewLocationAvailable(Location location). If you chose to reject mock locations, the callback is only invoked with non-mock locations.
There are some more methods to implement, but essentially this is it. Obviously, there are some ways to get around mock provider detection with rooted devices, but on stock, non-rooted devices the rejection should work reliably.
As far as I understand, Open Mobile API is bundled with the Android ROMs created by manufacturers. We are using an SDK that is heavily using Open Mobile API, and found out, that some vendors create ROMs, where the version of the Open Mobile API is incompatible with the version of Android. This results in a disaster, that when we try to use the mentioned SDK, the application crashes. Because the SDK starts a new thread, and crashes on it. We cannot even put the whole logic in a try-catch block because of all this is running in a separate thread.
We decided to check the version of Android and the Open Mobile API, and see if they are incompatible, and if they are, completely disable the functionality that requires it.
Is there a way to determine the version of the preinstalled Open Mobile API? If there is, how can I do it?
That depends on what version you actually want to find out: the version of the SmartcardService system component or the version of the Open Mobile API framework.
Finding the version of the SmartcardService system application
The most obvious way would be to check the version information of the SmartcardService application package:
final String SMARTCARD_SERVICE_PACKAGE = "org.simalliance.openmobileapi.service";
PackageInfo pi = getPackageManager().getPackageInfo(SMARTCARD_SERVICE_PACKAGE, 0);
String versionName = pi.versionName;
String versionCode = pi.versionCode;
Typical values for versionName are "2.3.0" (versionCode = 1), "2.4.0" (versionCode = 3), "3.0.0" (versionCode = 4), "3.1.0" (versionCode = 5), and "4.0.0" (versionCode = 8). Thus, you can determine the exact version of SEEK that the SmartcardService was forked from.
Unfortunately, several OEMs (e.g. Samsung) decided to remove the version information from the application package. Consequently, this is not as reliable as one might expect.
Another method that allows you to distinguish between implementations based on SEEK versions < 4.0.0 and SEEK versions >= 4.0.0 is to check the intent filter of the SmartcardService component:
final String SMARTCARD_SERVICE_PACKAGE = "org.simalliance.openmobileapi.service";
final String SMARTCARD_SERVICE_CLASS = "org.simalliance.openmobileapi.service.SmartcardService";
final String SMARTCARD_SERVICE_ACTION_V4 = "org.simalliance.openmobileapi.BIND_SERVICE";
final String SMARTCARD_SERVICE_ACTION_PRE4 = "org.simalliance.openmobileapi.service.ISmartcardService";
Intent intent = new Intent();
intent.setClassName(SMARTCARD_SERVICE_PACKAGE, SMARTCARD_SERVICE_CLASS);
intent.setAction(SMARTCARD_SERVICE_ACTION_V4);
ResolveInfo ri = getPackageManager().resolveService(intent, 0);
if (ri != null) {
// is version >= 4.0.0
} else {
intent.setAction(SMARTCARD_SERVICE_ACTION_PRE4);
ResolveInfo ri = getPackageManager().resolveService(intent, 0);
if (ri != null) {
// is version < 4.0.0
} else {
// is unknown version
}
}
Yet another method that allows you to distinguish between SEEK < 4.0.0 and SEEK >= 4.0.0 is to check if the SmartcardService holds the permission BIND_TERMINAL, a permission introduced in SEEK 4.0.0:
final String SMARTCARD_SERVICE_PACKAGE = "org.simalliance.openmobileapi.service";
final String PERMISSION_BIND_TERMINAL = "org.simalliance.openmobileapi.BIND_TERMINAL";
if (PackageManager.PERMISSION_GRANTED == getPackageManager().checkPermission(PERMISSION_BIND_TERMINAL, SMARTCARD_SERVICE_PACKAGE)) {
// is version >= 4.0.0
} else {
// is version < 4.0.0
}
Finding the version of the Open Mobile API framework
Starting with SEEK version 4.0.0 the SEService class of the Open Mobile API framework exposes a method getVersion() that returns the version string of the implemented Open Mobile API specification ("3.0" for SEEK 4.0.0). Thus, you could query that method to find the implemented Open Mobile API version:
Class cls = org.simalliance.openmobileapi.SEService.class;
Method getVersion = null;
try {
getVersion = cls.getDeclaredMethod("getVersion");
} catch (NoSuchMethodException e) {}
if (getVersion != null) {
// probably SEEK >= 4.0.0
} else {
// probably SEEK < 4.0.0
}
Further, if you have an instance of the SEService object, you could invoke the getVersion() method to find the implemented Open Mobile API specification version:
If your application was compiled against SEEK < 4.0.0:
if (getVersion != null) {
String version = (String)getVersion.invoke(seService);
}
If your application was compiled against SEEK >= 4.0.0:
if (getVersion != null) {
String version = seService.getVersion();
}
Note that trying to obtain an instance of the SEService class may result in exactly the undesired behavior that you found in the first place since the constructor of the SEService class will automatically initiate the connection to the SmartcardService.
Similar to discovering the getVersion() method, you could also try to discover methods in the API that are specific to a certain version of the Open Mobile API specification. For instance, you could test for the existence of the method
public Channel openBasicChannel(byte[] aid, byte p2);
in the Session class (org.simalliance.openmobileapi.Session). This method was introduced in version 3.0 of the specification.
However, you should be aware that detection based on the framework classes will only work if your app uses the Open Mobile API framework classes that are shipped with the target device and does not package its own version of the relevant framework classes. Else you would only detect what you packed into your app and not what's available on the system.
The Open Mobile API framework that is preinstalled on a device would usually be compatible with its backend (SMartcardService) on the same device. Since you seem to have version conflicts it is likely the case that your app packages its own version of the Open Mobile API framework that is incomatible with the target Android version and the Smartcard system service installed on the target device. This is something that you simply should not do.
Log.d(TAG, "Build.VERSION_CODES.ICE_CREAM_SANDWICH: " + Build.VERSION_CODES.ICE_CREAM_SANDWICH);
I write code like this, I used the sdk4.0 to compile this android program, so it didn't cause compile error. When I run this program in my phone that running android 2.3.4, it run well.
Why? I am confused that version 2.3.4 (api level 10) has Build.VERSION_CODES.ICE_CREAM_SANDWICH property? And when I used sdk2.3.4 will cause compile error.
More
I test some code like these below,
private ScaleGestureDetector mScaleGestureDetector;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR_MR1) {
mScaleGestureDetector = new ScaleGestureDetector(this, new MyOnScaleGestureListener());
}
this code will run well on android 1.6 api level 4, but
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR_MR1) {
Log.d(TAG, "getx(0): " + event.getX(0));
}
this program run failed on android 1.6 api level 4.
They both run on android 2.3.4 well.
why? (In ScaleGestureDetector class use the event.getX(0) (since api level 5) too)
I test some code more..
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Fragment f = new Fragment();
}
When I run it on android 1.6 emulator it throw java.lang.VerifyError, but on my phone running android 2.3.4 it throws java.lang.NoClassDefFoundError.
Why??
It's not as strange as it seems. It has to do with how Java treat constant values of primitives. During compilation, the value of the constant is put in the byte code, not a reference to the actual constant.
For example:
Log.d(TAG, "Build.VERSION_CODES.ICE_CREAM_SANDWICH: " + Build.VERSION_CODES.ICE_CREAM_SANDWICH);
will actucally be translated by the compiler to:
Log.d(TAG, "Build.VERSION_CODES.ICE_CREAM_SANDWICH: " + 14);
so the reference to the actual constant (and class) is removed.
Regarding the code that doesn't run for you, it has to do with that the MotionEvent.getX(int n) method wasn't available until api level 5. Before that, multitouch wasn't supported and thus no need for any other method than getX().
It doesn't matter if you actually call the method that doesn't exist. The error is appearing while the class is being loaded and verified by the platform. You most likely get a VerifyError in the log, since it will discover that you're trying to call a non-existent method during the verification.
On the other hand, if you try to use a class that doesn't exist, you will get a ClassNotFoundException instead. Note that sometimes, a class exists in Android even if the documentation doesn't say so. Some classes existed in early versions of Android but weren't exposed until later. Some have even gone the other way.
So:
Trying to use a class that doesn't exist - ClassNotFoundException
Trying to use a method that doesn't exist on a class that exists - VerifyError
(When it comes to using Fragments, they are available for earlier versions with the standalone Android Support Library)
I have released an app on the market with minSDK set to 4 (Android 1.6) but now I want to release an update with features unavailable in 1.6 so I need a higher minSDK.
So, my question is: Will users running 1.6 be notified of this update?...and if yes will they be able to download/install it?
No they shouldn't be notified of the update. The market will filter the application out all together and they will no longer be able to see it or receive updates.
If you want to add features that use a higher api level but not exclude user's of a lower api level you can use some reflection to enable this:
public static Method getExternalFilesDir;
try {
Class<?> partypes[] = new Class[1];
partypes[0] = String.class;
getExternalFilesDir = Context.class.getMethod("getExternalFilesDir", partypes);
} catch (NoSuchMethodException e) {
Log.e(TAG, "getExternalFilesDir isn't available in this devices api");
}
This piece of code is saying:
Within the Context.class have I got this method
getExternalFilesDir (API level 9)
If so instantiate the variable getExternalFilesDir as a reflective call to this method else leave it as null.
Then later on you can simply do
// If the user's device is API9 or above
if(getExternalFilesDir != null){
// Invoke is basically the same as doing Context.getExternalFilesDir(var1, var2);
getExternalFilesDir.invoke(variable1, variable2);
} else {
// User cannot use this method do something else
}
Hope that helps