I am new in Android programming. I want to invoke a method in the class BatteryService by reflection. However, the following code fails above Android 5.0, including the newest Android 6.0.1, though it succeeds in Android 4.3. I have googled for days. But I cannot find any useful answers.
try{
Class myclass = Class.forName("com.android.server.BatteryService");
} catch (ClassNotFoundException e) {
e.printStackTrace();
Log.d("xx", "ClassNotFound!");
}
I have checked the source of Android 4.3, 5.0 and 6.0.1, and I am sure that the class BatteryService is in com.android.server. BTW, the modifier of BatteryService is public.
When running the above code, Android 5.0+ report an exception that the Class cannot be found. But the code works in Android 4.3. I wonder there are any new features introduced in Android 5.0 to preventing reflection?
Anyone knows the reason. Thanks a million!
As answered before, the classloader can't found the class. This is because the scope of the package is invisible if you don't have system's permissions (your apk should be installed in /system/app).
I recommend you to read this doc
You should avoid reflection and use the service class BatteryManager.
http://developer.android.com/reference/android/os/BatteryManager.html
Probably classloader doesn't know that class. App classloader and system classloader are not same.
EDITED
When starting systemserver, class path has set.
So, probably system app doesn't have this classloader.
https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/ZygoteInit.java#461
/**
* Finish remaining work for the newly forked system server process.
*/
private static void handleSystemServerProcess(
ZygoteConnection.Arguments parsedArgs)
throws ZygoteInit.MethodAndArgsCaller {
....
ClassLoader cl = null;
if (systemServerClasspath != null) {
cl = new PathClassLoader(systemServerClasspath, ClassLoader.getSystemClassLoader());
Thread.currentThread().setContextClassLoader(cl);
}
/*
* Pass the remaining arguments to SystemServer.
*/
RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
Related
I am trying to call createDisplay method on android.view.SurfaceControl using reflection but it is returning null on Marshmallow device. I Don't know why.
The reason to do is to create virtualdisplay without using MediaProjection API.
Following is the code.
Class surfaceControlClass = Class.forName("android.view.SurfaceControl");
Class cls = surfaceControlClass;
IBinder token = (IBinder) cls.getDeclaredMethod("createDisplay", new Class[]{String.class, Boolean.TYPE}).invoke(null, new Object[]{name, Boolean.valueOf(false)});
You need System Permission(android.permission.ACCESS_SURFACE_FLINGER) to do that, check your LogCat and you will find permission warning.
Actually you can't make it work unless you have the Platform Signature of your device.
But you can test it on emulator using the default AOSP signature.
I have an app which uses Google Maps (v1) and from the crash reports, I am seeing this exception from time to time:
java.lang.NoClassDefFoundError: android.security.MessageDigest
at com.google.android.maps.KeyHelper.getSignatureFingerprint(KeyHelper.java:60)
at com.google.android.maps.MapActivity.createMap(MapActivity.java:513)
at com.google.android.maps.MapActivity.onCreate(MapActivity.java:409)
I have defined
<uses-library
android:name="com.google.android.maps"
android:required="true" />
inside the application tag and I am extending MapActivity as well. The application works fine on most devices but there are some uncommon ones that report this exception, usually on Android 4.0.4 like Woxter Tablet PC 90BL, TAB9008GBBK and other generic names.
From what I read in Stackoverflow, it is a problem in the ROM and it can be solved by the user doing some advanced tricks but what I want is to prevent this crash, as I don't think it can be solved, I just want to inform the user (and thell him to buy a better device :) and disable maps functionality instead of crashing. But I can't find a way to handle this error or test it with the devices I have.
Also my main activity is based on MapActivity so I don't know how can I handle this exception before opening it.
Disclaimer: I've not come across this error on any of my apps / devices but I solved a similar problem. May be that same technique can help you.
Given that the class is either unavailable or an exception occurrs while loading the class, why not try to force load it when your application starts ? Class.forName("android.security.MessageDigest") should load the class and you can catch the Error thrown from that call. I know its dirty, but it should work. You can declare a custom Application class on the manifest to make this check.
Class loading test
try
{
Class.forName("android.security.MessageDigest");
}
catch (Throwable e1)
{
e1.printStackTrace();
//Bad device
}
You can also perform a litmus test and check the functionality of the class should the class loading succeed by digesting a simple String.
Functional test
try
{
MessageDigest digester = MessageDigest.getInstance("MD5");
digester.update("test".getBytes("UTF-8"));
byte[] digest = digester.digest();
}
catch (Throwable e1)
{
e1.printStackTrace();
// Class available but not functional
}
If the class loading / litmus test fails, update a shared preference flag and let the user know that his device sucks :)
Try to change the import android.security.MessageDigest to java.security.MessageDigest
by the look at this link:
What is 'android.security.MessageDigest''?
It looks that the android.security.MessageDigest was remove from Honeycomb so change it to the java one. and check this link as well:
http://productforums.google.com/forum/#!category-topic/maps/google-maps-for-mobile/KinrGn9DcIE
As been suggested there by #XGouchet:
Try downloading the latest version of the Google Maps API and rebuild your application with targetSDK set to the highest available (as of today it should be 17 / Jelly Bean).
The class android.security.MessageDigest is an abstract class (see MessageDigest API) what means that it can't be instantiated right away. So what happens is, that any time a device/app can't find an implementation of this class you will get the exception above, namely
java.lang.NoClassDefFoundError: android.security.MessageDigest
It's a good question why this happens. May be some phone vendors didn't ship their phone with the required library that actually implements this abstract class. I faced a similar issue with the TUN.ko module in the past.
Approach 1
What should help is, if you provide your own (empty) implementation of this class that "implements" the abstract classes and methods like this:
public class MessageDigestSpi extends Object {
byte[] engineDigest() { return new byte[0]; }
void engineReset() { }
void engineUpdate(byte[] input, int offset, int len) { }
}
public class MessageDigest extends MessageDigestSpi {
}
... and put those classes into the folder <src>/java/security/. So this way you provide your own implementation that is always found and might contain some code in order to inform the user or provide an alternative implementation.
So the remaining questions are: what does the app do, if the implementation is provided by the system, too and how to control that the system implementation is the first choice?
The answer: which implementation is chosen depends on the import order. Looking at Eclipse you can define the order in the project properties, Java build path, tab order and export. Be sure that you have any system libraries on top that might include the system implementation (most likely the Android libraries). This way the system searches in those libraries first. If nothing is found your classes get loaded and executed.
Approach 2
As an alternative to the implementation in an own abstract class you could of course simply instantiate the MessageDigest class, catch the NoClassDefFoundError exception and store the result for later evaluation:
import android.security.MessageDigest;
public class MessageDigestTester {
private static Boolean messageDigestAvailable = null;
public static Boolean isLibraryAvailable() {
if (messageDigestAvailable == null) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
messageDigestAvailable = true;
} catch (NoClassDefFoundError e) {
messageDigestAvailable = false;
}
}
return messageDigestAvailable;
}
}
Then use if (MessageDigestTester.isLibraryAvailable()) { } else { } in your code in order to encapsulate the usage of this library and to provide an alternative.
Approach two is easier to implement whereas approach one is the more sophisticated solution.
Hope this was helpful ... Cheers!
Hi to all android devlopers.
Please, help me out from this:
1)How do i use non public classes in android like telephony, android.telephony.CallManager in my android application?
2) How do I import this telephony packages in my activity class and make it allow to access its functionality?
You can't and you shouldn't. These internal classes/APIs can change at any time without warning during an Android upgrade. there's no guarantee that they're implemented in the exact same way across different vendors.
Such changes can cause your application to break.
You should only use the public classes included in the android.jar
Reflection?
ClassLoader classLoader = TestActivity.class.getClassLoader();
final ClassLoader classLoader = this.getClass().getClassLoader();
try {
final Class<?> classCallManager =
classLoader.loadClass("com.android.internal.telephony.CallManager");
} catch (final ClassNotFoundException e) {
Log.e("TestActivity", e);
}
And add
READ_PHONE_STATE
to your Manifest.xml
In Android I get the version of the SDK easily (Build.VERSION.SDK) but I need to use LabeledIntent only if the platform is newer than 1.6 (>Build.VERSION_CODES.DONUT)
I suppose that Reflection is necessary (I have read this link but it is not clear for a class or to me).
This is the code but it gives me an exception because in my Android 1.6, the compiler verifies if the package exists even if the condition is not applied:
Intent theIntent=....;
if(Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.DONUT)
{
try{
Intent intentChooser = Intent.createChooser(intent,"Choose between these programs");
Parcelable[] parcelable = new Parcelable[1];
parcelable[0] = new android.content.pm.LabeledIntent(theIntent, "", "Texto plano", 0);
intentChooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, parcelable);
activity.startActivity(intentChooser);
}
catch(Exception e)
{
activity.startActivity(theIntent);
}
} else
{
activity.startActivity(intentMedicamento);
}
HOW I SOLVED IT, SOME NOTES TO THE RIGHT ANSWER
#Commonsware show me the way to do it. We create a bridge class so that depending on the API LEVEL, you instance one class that uses an API LEVEL or another class that uses another API LEVEL.
The only detail one beginner could forget is that you have to compile your app with the newest SDK you are goint to make reference.
public abstract class LabeledIntentBridge {
public abstract Intent BuildLabeledIntent(String URL, Intent theintent);
public static final LabeledIntentBridge INSTANCE=buildBridge();
private static LabeledIntentBridge buildBridge() {
int sdk=new Integer(Build.VERSION.SDK).intValue();
if (sdk<5) {
return(new LabeledIntentOld());
}
return(new LabeledIntentNew());
}
}
So in the LabeledIntentNew, I included all the code that refers to LabeledIntent only available in API LEVEL 5. In LabeledIntentOld, I can implement another kind of control, in my case I return the intent itself without doing nothing more.
The call to this class is done like this:
LabeledIntentBridge.INSTANCE.BuildLabeledIntent(URLtest,theIntent);
Follow the wrapper class pattern documented in the page you linked to above.
You have to use reflection...
The idea is good, but in your code you refer to LabeledIntent which is not available in 1.6. So when your app runs against 1.6 devices, it cannot find the class and crashes.
So the idea is to write code where you don't refer to LabeledIntent when running in 1.6. To do this, you can write a wrapper class (LabeledIntentWrapper) which extends LabeledIntent and call it in your function. So, in 1.6, the device will see a reference to a known class: LabeledIntentWrapper.
isInitialStickyBroadcast() is obviously only available after 2.0 (SDK 5).
I'm getting this error:
"Uncaught handler: thread main exiting due to uncaught exception
java.lang.VerifyError"
It's only happening on 1.6. Android 2.0 and up doesn't have any problems, but that's the main point of all.
I can't catch the Error/Exception (java.lang.VerifyError), and I know it's being caused by calling isInitialStickyBroadcast() which is not available in SDK 4, that's why it's wrapped in the SDK check.
I just need this BroadcastReceiver to work on 2.0+ and not break in 1.6, it's an app in the market, the UNDOCK feature is needed for users on 2.0+ but obviously not in 1.6 but there is a fairly substantial number of users still on 1.6.
Here's an easy-to-read version of part of the code I'm using. Notice that it's wrapped in an SDK check to only run on 2.0+, but the VerifyError is still showing up.
private BroadcastReceiver mUndockedReceiver = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
//FROM ECLAIR FORWARD, BEFORE DONUT THIS INTENT WAS NOT IMPLEMENTED
if (Build.VERSION.SDK_INT >= 5)
{
if (!isInitialStickyBroadcast()) {
//Using constant instead of Intent.EXTRA_DOCK_STATE to avoid problems in older SDK versions
int dockState = intent.getExtras().getInt("android.intent.extra.DOCK_STATE", 1);
if (dockState == 0)
{
finish();
}
}
}
}
};
Your problem is that while you would not be executing isInitialStickyBroadcast(), the classloader attempts to resolve all methods when the class is loaded, so your SDK 4 devices fail at that point, since there is no isInitialStickyBroadcast().
You have two main options:
Use reflection.
Create two editions of your BroadcastReceiver, as public classes in their own files. One has the SDK 4 logic, one has the SDK 5+ logic. Register the one you want based on an SDK check at the time you call registerReceiver().