49

When I try my app with Android KitKat I have an error in PreferenceActivity.

Subclasses of PreferenceActivity must override isValidFragment(String) to verify that the Fragment class is valid! com.crbin1.labeltodo.ActivityPreference has not checked if fragment com.crbin1.labeltodo.StockPreferenceFragment is valid

In documentation I find the following explanation

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.

I don't find any example to resolve the problem.

Community
  • 1
  • 1
crbin1
  • 2,219
  • 3
  • 22
  • 29

10 Answers10

65

Try this... this is how we check validity of fragment.

protected boolean isValidFragment(String fragmentName) {
  return StockPreferenceFragment.class.getName().equals(fragmentName);
}
Alex Lockwood
  • 83,063
  • 39
  • 206
  • 250
user2098324
  • 972
  • 10
  • 17
  • "StockPreferenceFragment" is the Fragment class you wanna use. David, posted an answer below, which shows a possible solution. However, is there maybe a cleaner solution, to incoperate more than one Fragment name? – Wolkenjaeger Dec 31 '13 at 17:59
  • 4
    Does it have to be done FOR EVERY FRAGMENT? or it's enough the PREFERENCE one?! – Phantômaxx Jan 02 '14 at 08:13
  • 1
    @Wolkenjaeger you van keep your fragment names inside an array, and read from there. The API compares the name, so you must find a way for your code to match the String. It shouldn't be hard to abstract it enough using standard String practices. After all, you're the one responsible for returning a simple boolean to the system. It's up to you to create your own code logic. – davidcesarino Jan 03 '14 at 02:22
  • @Tobor AFAIK, no, just for Fragments used under PreferenceActivity. See the security report posted below in the answer with two links. – davidcesarino Jan 03 '14 at 02:24
  • 2
    So in the PreferenceActivity I override this method and return **true**... It seems pretty a **stupid thing**, uh? – Phantômaxx Jan 03 '14 at 06:52
  • 2
    @Tobor why is it stupid? It's a simple security check to make sure the fragment called at the time is an authorized fragment (those that you recognize). It's pointless if you just `return true` without any logic at all. It would be the same as the White House authorizing anyone and everyone that walks true the public gate at the street, but correct if they `return true` only to those that work there. – davidcesarino Jan 28 '14 at 15:22
  • 2
    What is terribly lame is that the default implementation you get when you add a PreferenceActivity via Android Studio this issue is not resolved. – Roel Sep 16 '15 at 11:13
  • 1
    If I use many fragments? Your validation only uses **one** fragment –  Jun 12 '16 at 13:59
24

Out of pure curiosity, you can also do this as well:

@Override
protected boolean isValidFragment(String fragmentName) {
    return MyPreferenceFragmentA.class.getName().equals(fragmentName)
            || MyPreferenceFragmentB.class.getName().equals(fragmentName)
            || // ... Finish with your last fragment.

;}
davidcesarino
  • 16,160
  • 16
  • 68
  • 109
20

I found I could grab a copy of my fragment names from my header resource as it was loaded:

public class MyActivity extends PreferenceActivity
{
    private static List<String> fragments = new ArrayList<String>();

    @Override
    public void onBuildHeaders(List<Header> target)
    {
        loadHeadersFromResource(R.xml.headers,target);
        fragments.clear();
        for (Header header : target) {
            fragments.add(header.fragment);
        }
    }
...
    @Override
    protected boolean isValidFragment(String fragmentName)
    {
        return fragments.contains(fragmentName);
    }
}

This way I don't need to remember to update a list of fragments buried in the code if I want to update them.

I had hoped to use getHeaders() and the existing list of headers directly, but it seems the activity is destroyed after onBuildHeaders() and recreated before isValidFragment() is called.

This may be because the Nexus 7 I'm testing on doesn't actually do two-pane preference activities. Hence the need for the static list member as well.

lane
  • 633
  • 12
  • 18
  • Great solution! I had problem with flavors as i had different Fragments loading in dev,test,live. This worked like a dream – Nabdreas Jan 23 '15 at 11:01
  • Nice Solution!Worked like a charm! – void pointer Jul 17 '15 at 07:26
  • Cleaner, better solution :) – Al-Mothafar Aug 24 '15 at 19:09
  • I don't see the security flaw Ofek Ron alluded to in his rewriting of this, the best solution. – John Oct 17 '15 at 22:25
  • 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 :) – ceph3us Jan 14 '16 at 17:55
18

This API was added due to a newly discovered vulnerability. Please see http://ibm.co/1bAA8kF or http://ibm.co/IDm2Es

December 10, 2013 "We have recently disclosed a new vulnerability to the Android Security Team. [...] To be more accurate, any App which extended the PreferenceActivity class using an exported activity was automatically vulnerable. A patch has been provided in Android KitKat. If you wondered why your code is now broken, it is due to the Android KitKat patch which requires applications to override the new method, PreferenceActivity.isValidFragment, which has been added to the Android Framework." -- From the first link above

lilbyrdie
  • 1,887
  • 2
  • 20
  • 24
Roee Hay
  • 181
  • 2
  • 8
    Hi @RoeeHay, can you please elaborate on the linked article? It is often considered a bad answer to link articles as answers. It can you please point out the important points in the vulnerability? I see that the article itself is written by you and explains quite a lot, on *why* it was introduced. I would love to upvote this answer, as it by far explains why rather than how to fix the issue. – Avinash R Dec 23 '13 at 06:47
3

Verified with an actual 4.4 device:

(1) if your proguard.cfg file has this line (which many define anyway):

-keep public class com.fullpackage.MyPreferenceFragment

(2) than the most efficient implementation would be:

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class EditPreferencesHC extends PreferenceActivity {
...
   protected boolean isValidFragment (String fragmentName) {

     return "com.fullpackage.MyPreferenceFragment".equals(fragmentName);

   }
}
Community
  • 1
  • 1
Amir Uval
  • 14,425
  • 4
  • 50
  • 74
3

I am not sure if lane's implementation is free of the vulnerabilities discussed here but if it is, then i think a better solution would be to avoid using that static list and simply do the following :

 @Override
    protected boolean isValidFragment(String fragmentName)
    {
        ArrayList<Header> target = new ArrayList<>();
        loadHeadersFromResource(R.xml.pref_headers, target);
        for (Header h : target) {
            if (fragmentName.equals(h.fragment)) return true;
        }
        return false;
    }
Community
  • 1
  • 1
Ofek Ron
  • 8,354
  • 13
  • 55
  • 103
  • as i said to @lane answer - using onBuildHeaders (List<>) method to set fragment list is bad idea - your approach is much more safe - my vote up! but u aan improve it little more - create private target and fill it once if null – ceph3us Jan 14 '16 at 17:58
0

this is my solution:

  • if u need dynamic rebuild headers
  • if u use extras to start preference activity - onBuildHeaders() approach will fail! (with below start intent extras - why ??? - simple because onBuildHeaders() is never called):

    Intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMEN,Fragment.class.getName()); Intent.putExtra(PreferenceActivity.EXTRA_NO_HEADERS, true);

This is example class:

/**
 * Preference Header for showing settings and add view as two panels for tablets
 * for ActionBar we need override onCreate and setContentView
 */
public class SettingsPreferenceActivity extends PreferenceActivity {

    /** valid fragment list declaration */
    private List<String> validFragmentList;

    /** some example irrelevant class for holding user session  */
    SessionManager _sessionManager;

    @Override
    public void onBuildHeaders(List<Header> target) {
        /** load header from res */
        loadHeadersFromResource(getValidResId(), target);
    }

    /**
     * this API method was added due to a newly discovered vulnerability.
     */
    @Override
    protected boolean isValidFragment(String fragmentName) {
        List<Header> headers = new ArrayList<>();
        /** fill fragments list */
        tryObtainValidFragmentList(getValidResId(), headers);
        /** check  id valid */
        return validFragmentList.contains(fragmentName);
    }

    /** try fill list of valid fragments */
    private void tryObtainValidFragmentList(int resourceId, List<Header> target) {  
        /** check for null */
        if(validFragmentList==null) {
            /** init */
            validFragmentList = new ArrayList();
        } else {
            /** clear */
            validFragmentList.clear();
        }
        /** load headers to list */
        loadHeadersFromResource(resourceId, target);
        /** set headers class names to list */
        for (Header header : target) {
            /** fill */
            validFragmentList.add(header.fragment);
        }
    }

    /** obtain valid res id to build headers */
    private int getValidResId() {
        /** get session manager */
        _sessionManager = SessionManager.getInstance();
        /** check if user is authorized */
        if (_sessionManager.getCurrentUser().getWebPart().isAuthorized()) {
            /** if is return full preferences header */
            return R.xml.settings_preferences_header_logged_in;
        } else {
            /** else return short header */
            return R.xml.settings_preferences_header_logged_out;
        }
    }
}
ceph3us
  • 7,326
  • 3
  • 36
  • 43
0

Here's my headers_preferences.xml file:

<?xml version="1.0" encoding="utf-8"?>  
<preference-headers  
xmlns:android="http://schemas.android.com/apk/res/android">  

    <header  

        android:fragment="com.gammazero.signalrocket.AppPreferencesFragment$Prefs1Fragment"  
        android:title="Change Your Name" />  

    <header  
        android:fragment="com.gammazero.signalrocket.AppPreferencesFragment$Prefs2Fragment"  
        android:title="Change Your Group''s Name" />  

    <header  
        android:fragment="com.gammazero.signalrocket.AppPreferencesFragment$Prefs3Fragment"  
        android:title="Change Map View" />  

</preference-headers>  

In my PreferencesActivity where the isValidFragment code occurs, I just turned it on its head:

@Override
protected boolean isValidFragment(String fragmentName)
{
  //  return AppPreferencesFragment.class.getName().contains(fragmentName);
    return fragmentName.contains (AppPreferencesFragment.class.getName());
}

As long as I use the AppPreferencesFragment string at the start of all my fragment names, they all validate just fine.

0

my solution (instead of creating ArrayList of class) since the fragments that are loaded suppose to be subclass of PreferenceFragment.class run this check in the @OverRide method

@Override
protected boolean isValidFragment(String fragmentName) {
    try {
        Class cls = Class.forName(fragmentName);
        return (cls.getSuperclass().equals(PreferenceFragment.class));
                                  // true if superclass is PreferenceFragmnet
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    return false;
}
ItzikH
  • 21
  • 5
0
@Override
protected boolean isValidFragment (String fragmentName) {
    for (Class<?> cls : ImePreferences.class.getDeclaredClasses()) {
        if (cls.getName().equals(fragmentName)){return true;}
    }
    return false;
}
goti
  • 1
  • 2
    Hello there, thanks for your first contribution! Can you please add a few more details (why should someone us this code snippet instead of one of the other answers)? – Carsten Hagemann Feb 22 '21 at 09:34