7

For some application on which I was working, for devices with API level 19 I'm getting exception

Caused by: java.lang.RuntimeException: Subclasses of PreferenceActivity must override isValidFragment(String) to verify that the Fragment class is valid! com... has not checked if fragment com...$. is valid.

Then, I found out that for those applications android frameworks protected boolean isValidFragment(String fragmentName) is getting called, which has code

if (getApplicationInfo().targetSdkVersion  >= android.os.Build.VERSION_CODES.KITKAT) {
        throw new RuntimeException(
                "Subclasses of PreferenceActivity must override isValidFragment(String)"
                + " to verify that the Fragment class is valid! " + this.getClass().getName()
                + " has not checked if fragment " + fragmentName + " is valid.");
    } else {
        return true;
}

Then I tried to replicate the error

I took my sample app's code from Preferences Activity Example and added line <uses-sdk android:targetSdkVersion="19" /> in manifest.

But strangely, I'm not getting the error(isValidFragment() not getting called in that case).

So please tell me how to replicate that error in my sample app.

Shirish Herwade
  • 11,461
  • 20
  • 72
  • 111
  • If I wrote a piece of app and published 3 years ago, or so, why can't let Google to run and instead setting this ANR – matheszabi Oct 20 '16 at 21:16

3 Answers3

21

The answer to your question is in this post. This is a duplicate question:

isValidFragment Android API 19

--Updated--

Here is what the solution is:

Basically, whichever Activity is using your fragment "com...$" in the error above, you must update it with the fix below. You should update all the Activities in your project with this fix for any Acitvity that uses a Fragment.

The documentation states:

protected boolean isValidFragment (String fragmentName)

Added in API level 19

Subclasses should override this method and verify that the given fragment is a 
valid type to be attached to this activity. The default implementation returns 
true for apps built for android:targetSdkVersion older than KITKAT. For later 
versions, it will throw an exception.

You can fix this error by overriding this method to the Activity/FragmentActivity:

@Override
protected boolean isValidFragment (String fragmentName) {
  return [YOUR_FRAGMENT_NAME_HERE].class.getName().equals(fragmentName);
}

If you are being lazy and just want to test out if this fix works before coding all your fragments into this method, you can simply return true without any checking:

@Override
protected boolean isValidFragment (String fragmentName) {
  return true;
}

I had the same issues when testing on the emulator and this was the solution.

Community
  • 1
  • 1
Camille Sévigny
  • 5,104
  • 4
  • 38
  • 61
  • First of all, it's not duplicate because in the link you given he is unable to remove the error, and I'm unable to reproduce it in my sample app. Also my question is, when it's get called rather than how to fix it – Shirish Herwade Mar 31 '14 at 07:38
  • And second thing, can you please elaborate on the linked article? It is often considered a bad answer to link articles as answers. And also I'm not able to find answer to my question there in your link. – Shirish Herwade Mar 31 '14 at 07:38
  • this is not working when You have several classes like fragments in the Activity – user1616685 Jul 29 '15 at 15:08
  • +1 For showing the "lazy" solution - which of course should not be used in production code. But for the sake of completeness it should be included in the discussion if only to indicate that it shouldn't be used. – RenniePet Sep 23 '16 at 04:11
6

Seems to be a bug or a 4.4 security restriction. Workaraound is to use anything below 19 that is still compatible with PreferenceActivity, and bite the bullet for compiling with an older target.

I am using the headers "pattern" for the PreferenceActivity (overriding public void onBuildHeaders(List<Header> target)), and I assume the OP is too, most likely being the place where stuff happens and crashes.

In my case, I have narrowed this exception to <uses-sdk android:targetSdkVersion="19" />, and anything in [14-18] build targets will compile and run without issues.

Suggestion (for Eclipse): I never messed directly messed with such stuff, but I'm assuming if you compile your PreferenceActivity (and maybe fragments) on a different project, targeting 18 or under (pun not intended :O ), and then using that project as a library for your main project targeting KitKat (19), perhaps you can avoid the crash scenario at run-time while still using the features you need from the latest build (as long as those features aren't in the build-18-bound PreferenceActivity). If this does not succeed, try with that project in jar form (pre-compiled) instead of using project as library.

UPDATE: also take note of Camille Sévigny's answer. If the issue has anything to do with that other question (50% chance IMHO), all apps targeting API 18 are vulnerable to fragment injection attacks (see his linked question).

leRobot
  • 1,497
  • 1
  • 18
  • 30
  • this is bad idea!!! - especially when start preference activity and u will set extras -> Intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMEN,Fragment.class.getName()); Intent.putExtra(PreferenceActivity.EXTRA_NO_HEADERS, true); then u are in black hole :) - my vote down! – ceph3us Jan 14 '16 at 17:53
  • so you just lost reputation to vote down my answer, which already has an april 3 '14 update stating it IS a bad idea. More power to you then :P – leRobot Jan 14 '16 at 19:50
  • I'm pointing out only that using onBuildsHeaders could be never called so u can't relay on this method to set fragments for validation - isValidFragment is called while transaction is commited to swap fragments – ceph3us Jan 14 '16 at 20:19
5

Here you go!

Slap this in there, and you're good!

Collect all the inner classes found in this PreferenceActivity. I chose to put the list in a static field variable:

public class whatever extends PreferenceActivity {

    static final Class<?>[] INNER_CLASSES = 
                                     whatever.class.getDeclaredClasses();

Then, override the method, ValidFragment, and ensure the fragment about to be displayed is one the 'parent' activity is aware of:

 /**
 *  Google found a 'security vulnerability' and imposed this hack.
 *  Have to check this fragment was actually conceived by this activity.
 */
@Override
protected boolean isValidFragment(String fragmentName) {

    Boolean knownFrag = false;

    for (Class<?> cls : INNER_CLASSES) {

       if ( cls.getName().equals(fragmentName) ){

           knownFrag = true;

           break;
       }
    }           

    return knownFrag;
}
Drawn
  • 435
  • 1
  • 7
  • 20