package preference; import android.content.Context; import android.content.res.XmlResourceParser; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.XmlRes; import android.util.AttributeSet; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import preference.internal.WearPreferenceScreen; /** * Used to parse preferences xml files into an object model. * * This can be subclassed to provide aliases for preference types (such as for removing package names from tags). */ public class XmlPreferenceParser { @NonNull WearPreferenceScreen parse(@NonNull final Context context, @XmlRes final int prefsResId) { try { final XmlResourceParser parser = context.getResources().getXml(prefsResId); return parseScreen(context, parser); } catch (XmlPullParserException | IOException e) { throw new RuntimeException("Error parsing preferences file", e); } } @NonNull private WearPreferenceScreen parseScreen(@NonNull final Context context, @NonNull final XmlResourceParser parser) throws XmlPullParserException, IOException { while(parser.getName() == null) { parser.next(); } if("PreferenceScreen".equals(parser.getName())) { final WearPreferenceScreen screen = new WearPreferenceScreen(context, parser); parsePreferences(context, parser, screen); return screen; } else { throw new IllegalArgumentException("Preferences file must start with a PreferenceScreen element"); } } private void parsePreferences(@NonNull final Context context, @NonNull final XmlResourceParser parser, @NonNull final WearPreferenceScreen screen) throws XmlPullParserException, IOException { if(parser.getEventType() == XmlResourceParser.START_TAG) { parser.next(); while(parser.getEventType() != XmlResourceParser.END_TAG) { parseItem(context, parser, screen); parser.next(); } } } private void parseItem(final Context context, final XmlResourceParser parser, final WearPreferenceScreen screen) throws XmlPullParserException, IOException { switch(parser.getName()) { case "PreferenceScreen": final WearPreferenceScreen childScreen = parseScreen(context, parser); screen.addChild(childScreen); break; default: parsePreference(context, parser, screen); break; } } private void parsePreference(@NonNull final Context context, @NonNull final XmlResourceParser parser, @NonNull final WearPreferenceScreen screen) throws XmlPullParserException, IOException { final String name = parser.getName(); WearPreference preference = parsePreference(context, name, parser); if(preference == null) { preference = parsePreferenceInternal(context, name, parser); } screen.addChild(preference); while(parser.getEventType() != XmlResourceParser.END_TAG) { parser.next(); } } @NonNull private WearPreference parsePreferenceInternal(@NonNull final Context context, @NonNull final String name, @NonNull final XmlResourceParser parser) { switch(parser.getName()) { case "ListPreference": return new WearListPreference(context, parser); case "SwitchPreference": case "CheckBoxPreference": return new WearTwoStatePreference(context, parser); default: return parsePreferenceUsingReflection(context, name, parser); } } @NonNull private WearPreference parsePreferenceUsingReflection(@NonNull final Context context, @NonNull final String name, @NonNull final XmlResourceParser parser) { try { final Class<?> cls = Class.forName(name); final Constructor<?> constructor = cls.getConstructor(Context.class, AttributeSet.class); return (WearPreference) constructor.newInstance(context, parser); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Unknown preference type \"" + name + "\". To use custom preference types you must create a custom XmlPreferenceParser, or use the fully qualified class name"); } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { throw new IllegalArgumentException("Custom preference type \"" + name + "\" must have a (Context, AttributeSet) constructor. Alternatively, use a custom XmlPreferenceParser to instantiate the preferences."); } } /** * Override this method when creating custom preference types in order to parse these types from a preferences xml file. * * Returning null will defer the parsing to internal (pre-made) preference types. * * @param context A Context that can be used to load styled attributes * @param preferenceType The name of the preference type to create (this is the xml tag name). * @param attrs The xml attributes contained in the xml tag * * @return A new {@link WearPreference} object that represents this type of preference. */ @Nullable protected WearPreference parsePreference(@NonNull final Context context, @NonNull final String preferenceType, @NonNull final AttributeSet attrs) { return null; } }