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.
Related
Since Google has announced that chromebook also support "Android Application" so I also wanted to support my app on chromebook although it is running fine with few exception which I need to fix.
I want to write code in such a way that that is will execute only for chromebook and will not execute for android phones and tablet.
I have check with Chromebook documentation in android developer site, I didn't get any such API which tell that your app is running in chrome book environment.
Suggestion from ARC Beta documentation did not work:
If you need to check if your app is running on Chrome OS, look for chromium as the android.os.Build.BRAND and android.os.Build.MANUFACTURER.
Both return google on an ASUS Chromebook.
Finally I figure out a way to know if app in running in ARC:
context.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
Jan 15, 2023 Note-- Jump to the bottom of this answer to read how Google has changed their own method for checking YET AGAIN.
(Or keep reading for the history of the ARC check.)
Another method Google uses in their own code (updated several times now from link) is to check if Build.DEVICE ends with "_cheets". I don't know if ending device names like this is some kind of long-term strategy or a fast workaround, but it's also worth a look in addition to dex's proposed solution.
FWIW, since ARCWelder's method is deprecated and there's no official documentation on this (yet), I've also started a discussion in the XDA forums here for people to discuss what works/doesn't work on various devices.
Update 5/18: Looks like the code above was moved and updated, so Google's new ARC check as of May 2018 is here, particularly in this bit:
... } else if (Build.DEVICE != null && Build.DEVICE.matches(ARC_DEVICE_PATTERN)) {
mFormFactor = FORM_FACTOR_ARC;
} else { ...
where ARC_DEVICE_PATTERN is defined as
private static final String ARC_DEVICE_PATTERN = ".+_cheets|cheets_.+";
So it's not just a device ending with _cheets. It can start with cheets_ as well.
Update 8/26/20 -- As of 7 months ago, the source has been moved around from FormFactors.java to FeatureSupport.java. If you were looking for where it went- here it the code as of today.
public static boolean isArc() {
return (Build.DEVICE != null && Build.DEVICE.matches(".+_cheets|cheets_.+"));
}
The test remains the same.
Jan 15, 2023 -- The code has changed again! isArc() is now built into the FeatureUtil class (see commit here) The current version of isArc() :
/** Returns {#code true} if device is an ARC++ device. */
public static boolean isArc() {
return hasAnySystemFeature(ARC_FEATURE, ARC_DEVICE_MANAGEMENT_FEATURE);
}
Where ARC_FEATURE and ARC_DEVICE_MANAGEMENT_FEATURE are defined like this:
public static final String ARC_FEATURE = "org.chromium.arc";
public static final String ARC_DEVICE_MANAGEMENT_FEATURE = "org.chromium.arc.device_management";
the function hasAnySystemFeature() simply checks individual features and returns true if any is true.
Therefore the following might work as a simple standalone check in kotlin (where context is the activity context):
fun isArc(): Boolean {
return ((context.packageManager.hasSystemFeature("org.chromium.arc")) || (context.packageManager.hasSystemFeature("org.chromium.arc.device_management")))
Note this is similar to #dex's answer below, but includes both tests used by the Android source.
Incidentally, from looking at the code linked above you can also check other device characteristics like like isWatch(), isTV(), isAutomotive(), isPC(), isVrHeadset(), isLowRam(), etc. using similar feature checks.
PackageManager pm = context.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_PC))
// it's a chromebook
I found the solution in Android CTS code.
public static boolean isArc(#NonNull Context context) {
PackageManager pm = context.getPackageManager();
return pm.hasSystemFeature( "org.chromium.arc" ) || pm.hasSystemFeature( "org.chromium.arc.device_management" );
}
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.
Right now when user wants to download an application, he can do it using Android Market application installed on his phone or do it via web Market.
Is it possible to figure out, which way was this application installed ?
Found this in the Android Cracking Blog:
private boolean InstalledFromMarketEasy() {
String pname = this.getPackageName();
PackageManager pm = this.getPackageManager();
String installPM = pm.getInstallerPackageName(pname);
if ( installPM == null ) {
// Definitely not installed from Android Market
return false;
}
else if ( installPM.equals("com.google.android.feedback") ) {
// Installed from the Android Market
return true;
}
return false;
}
According to the comments however, on some phones this does still return null.
Try this method to see if it returns null with your phone. If it doesn't, have a look if installPM changes when using Android Market Vending instead of the WebInterface.
If it doesn't change, I don't think it is possible to check how it was installed, as the Push-Notification is received by Vending.apk.
Better it should be
else if ( installPM.equals("com.android.vending") ) {
// Installed from the Android Market
return true;
}
Is it possble to target Android 2.1 and use CalendarProvider on devices with Android 4.0 and higher or is it can only be achieved by creating 2 separate APKs?
That depends on what you mean by "target".
If you mean "set targetSdkVersion" to Android 2.1, you can still use whatever APIs you want, so long as you only try calling them when you are running on a device that has them.
If you mean "set the build target" to Android 2.1, you can still use whatever APIs you want, so long as you use reflection to access the ones that are newer than API Level 7. Since CalendarContract is a content provider, that mostly is a matter of accessing various static data members, such as CONTENT_URI. Here is an example of using reflection to get at a CONTENT_URI value:
private static Uri CONTENT_URI=null;
static {
int sdk=new Integer(Build.VERSION.SDK).intValue();
if (sdk>=5) {
try {
Class<?> clazz=Class.forName("android.provider.ContactsContract$Contacts");
CONTENT_URI=(Uri)clazz.getField("CONTENT_URI").get(clazz);
}
catch (Throwable t) {
Log.e("PickDemo", "Exception when determining CONTENT_URI", t);
}
}
else {
CONTENT_URI=Contacts.People.CONTENT_URI;
}
}
(note: this example is designed to run on Android 1.5 and higher -- depending on your minSdkVersion, you could use Build.VERSION.SDK_INT instead of new Integer(Build.VERSION.SDK).intValue()).
If by "target" you mean something else, then we would need clarification of your use of the verb "target".
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