2

I am trying to write a custom camera app, and so to get some ideas for my camera, I downloaded the google camera app and have stripped out features I don't need.

I am having an issue, however, with part of the code that reads in an XML file and inflates that XML into custom preference objects for use on the camera screen. It's a cool feature that I would like to use, but unfortunately it is erroring and I am having trouble figuring out why.

Let's start by explaining what they are doing. First, there is an xml file with the preferences. Each @string corresponds to a field in strings.xml and each @array corresponds to an array in arrays.xml. All of this code is Google's that I pulled down from their repository.

<PreferenceGroup
        xmlns:camera="http://schemas.android.com/apk/lib/com.android.gallery3d"
        camera:title="@string/pref_camera_settings_category">
    <IconListPreference
            camera:key="pref_camera_flashmode_key"
            camera:defaultValue="@string/pref_camera_flashmode_default"
            camera:title="@string/pref_camera_flashmode_title"
            camera:icons="@array/camera_flashmode_icons"
            camera:largeIcons="@array/camera_flashmode_largeicons"
            camera:entries="@array/pref_camera_flashmode_entries"
            camera:entryValues="@array/pref_camera_flashmode_entryvalues" />
    <IconListPreference
            camera:key="pref_camera_exposure_key"
            camera:defaultValue="@string/pref_exposure_default"
            camera:title="@string/pref_exposure_title"
            camera:singleIcon="@drawable/ic_exposure_holo_light" />
    <IconListPreference
            camera:key="pref_camera_scenemode_key"
            camera:defaultValue="@string/pref_camera_scenemode_default"
            camera:title="@string/pref_camera_scenemode_title"
            camera:singleIcon="@drawable/ic_scn_holo_light"
            camera:entries="@array/pref_camera_scenemode_entries"
            camera:entryValues="@array/pref_camera_scenemode_entryvalues" />
    <IconListPreference
            camera:key="pref_camera_whitebalance_key"
            camera:defaultValue="@string/pref_camera_whitebalance_default"
            camera:title="@string/pref_camera_whitebalance_title"
            camera:icons="@array/whitebalance_icons"
            camera:largeIcons="@array/whitebalance_largeicons"
            camera:entries="@array/pref_camera_whitebalance_entries"
            camera:entryValues="@array/pref_camera_whitebalance_entryvalues" />
    <RecordLocationPreference
            camera:key="pref_camera_recordlocation_key"
            camera:defaultValue="@string/pref_camera_recordlocation_default"
            camera:title="@string/pref_camera_recordlocation_title"
            camera:icons="@array/camera_recordlocation_icons"
            camera:largeIcons="@array/camera_recordlocation_largeicons"
            camera:entries="@array/pref_camera_recordlocation_entries"
            camera:entryValues="@array/pref_camera_recordlocation_entryvalues" />
    <ListPreference
            camera:key="pref_camera_picturesize_key"
            camera:title="@string/pref_camera_picturesize_title"
            camera:entries="@array/pref_camera_picturesize_entries"
            camera:entryValues="@array/pref_camera_picturesize_entryvalues" />
    <ListPreference
            camera:key="pref_camera_focusmode_key"
            camera:defaultValue="@array/pref_camera_focusmode_default_array"
            camera:title="@string/pref_camera_focusmode_title"
            camera:entries="@array/pref_camera_focusmode_entries"
            camera:entryValues="@array/pref_camera_focusmode_entryvalues" />
    <IconListPreference
            camera:key="pref_camera_id_key"
            camera:defaultValue="@string/pref_camera_id_default"
            camera:title="@string/pref_camera_id_title"
            camera:icons="@array/camera_id_icons"
            camera:entries="@array/camera_id_entries"
            camera:largeIcons="@array/camera_id_largeicons" />
    <ListPreference
            camera:key="pref_camera_hdr_key"
            camera:defaultValue="@string/pref_camera_hdr_default"
            camera:title="@string/pref_camera_scenemode_entry_hdr"
            camera:entries="@array/pref_camera_hdr_entries"
            camera:entryValues="@array/pref_camera_hdr_entryvalues" />
    <CountDownTimerPreference
            camera:key="pref_camera_timer_key"
            camera:defaultValue="@string/pref_camera_timer_default"
            camera:title="@string/pref_camera_timer_title" />
    <ListPreference
            camera:key="pref_camera_timer_sound_key"
            camera:defaultValue="@string/pref_camera_timer_sound_default"
            camera:title="@string/pref_camera_timer_sound_title"
            camera:entries="@array/pref_camera_timer_sound_entries"
            camera:entryValues="@array/pref_camera_timer_sound_entryvalues" />
</PreferenceGroup>

Each element (including PreferenceGroup) corresponds to a class.

Next is the PreferenceInflator. Here is the method that parses the xml:

private CameraPreference inflate(XmlPullParser parser) {

    AttributeSet attrs = Xml.asAttributeSet(parser);
    ArrayList<CameraPreference> list = new ArrayList<CameraPreference>();
    Object args[] = new Object[]{mContext, attrs};

    try {
        for (int type = parser.next();
                type != XmlPullParser.END_DOCUMENT; type = parser.next()) {
            if (type != XmlPullParser.START_TAG) continue;
            CameraPreference pref = newPreference(parser.getName(), args);

            int depth = parser.getDepth();
            if (depth > list.size()) {
                list.add(pref);
            } else {
                list.set(depth - 1, pref);
            }
            if (depth > 1) {
                ((PreferenceGroup) list.get(depth - 2)).addChild(pref);
            }
        }

        if (list.size() == 0) {
            throw new InflateException("No root element found");
        }
        return list.get(0);
    } catch (XmlPullParserException e) {
        throw new InflateException(e);
    } catch (IOException e) {
        throw new InflateException(parser.getPositionDescription(), e);
    }
}

And newPreference is where my error is occurring. This code actually works for the PreferenceGroup but fails on any of the children.

private CameraPreference newPreference(String tagName, Object[] args) {
    String name = PACKAGE_NAME + "." + tagName;
    Constructor<?> constructor = sConstructorMap.get(name);
    try {
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to
            // add it
            Class<?> clazz = mContext.getClassLoader().loadClass(name);
            constructor = clazz.getConstructor(CTOR_SIGNATURE);
            sConstructorMap.put(name, constructor);
        }
        return (CameraPreference) constructor.newInstance(args);
    } catch (NoSuchMethodException e) {
        throw new InflateException("Error inflating class " + name, e);
    } catch (ClassNotFoundException e) {
        throw new InflateException("No such class: " + name, e);
    } catch (Exception e) {
        throw new InflateException("While create instance of " + name, e);
    }
}

The error is

android.view.InflateException: While create instance of com.mynamespace.camera.IconListPreference

IconListPreference extends from ListPreference which is where the code throws the error in the contructor.

public class ListPreference extends CameraPreference {
    private static final String TAG = "ListPreference";
    private final String mKey;
    private String mValue;
    private final CharSequence[] mDefaultValues;

    private CharSequence[] mEntries;
    private CharSequence[] mEntryValues;
    private boolean mLoaded = false;

    public ListPreference(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.ListPreference, 0, 0);

        mKey = Util.checkNotNull(
                a.getString(R.styleable.ListPreference_key));

        // We allow the defaultValue attribute to be a string or an array of
        // strings. The reason we need multiple default values is that some
        // of them may be unsupported on a specific platform (for example,
        // continuous auto-focus). In that case the first supported value
        // in the array will be used.
        int attrDefaultValue = R.styleable.ListPreference_defaultValue;
        TypedValue tv = a.peekValue(attrDefaultValue);
        if (tv != null && tv.type == TypedValue.TYPE_REFERENCE) {
            mDefaultValues = a.getTextArray(attrDefaultValue);
        } else {
            mDefaultValues = new CharSequence[1];
            mDefaultValues[0] = a.getString(attrDefaultValue);
        }

        setEntries(a.getTextArray(R.styleable.ListPreference_entries));
        setEntryValues(a.getTextArray(
                R.styleable.ListPreference_entryValues));
        a.recycle();
    }

}

The problem is this line, which throws an error when null is found.

mKey = Util.checkNotNull(
                a.getString(R.styleable.ListPreference_key));

So now we go down the rabbit hole a little further. attrs.xml looks like this

<resources>
    <declare-styleable name="CameraPreference">
        <attr name="title" format="string" />
    </declare-styleable>
    <declare-styleable name="ListPreference">
        <attr name="key" format="string" />
        <attr name="defaultValue" format="string|reference" />
        <attr name="entryValues" format="reference" />
        <attr name="entries" format="reference" />
    </declare-styleable>
    <declare-styleable name="IconIndicator">
        <attr name="icons" format="reference" />
        <attr name="modes" format="reference" />
    </declare-styleable>
    <declare-styleable name="IconListPreference">
        <!-- If a preference does not have individual icons for each entry, it can has a single icon to represent it. -->
        <attr name="singleIcon" format="reference" />
        <attr name="icons" />
        <attr name="largeIcons" format="reference" />
        <attr name="images" format="reference" />
    </declare-styleable>
</resources>

When I loaded the preferences xml file initially, the namespace looked like

http://schemas.android.com/apk/res/com.android.gallery3d

which I changed so that it would build

http://schemas.android.com/apk/lib/com.android.gallery3d

Debugging and pouring through the code, I feel like there is something obvious I am missing as to why this fails to load.

EDIT:

In playing with the code, I discovered that attrs is not being set correctly. It must be set through each iteration of the XML Parse. Once I did that, I started actually getting information in attrs.

Then, I discovered that the TypedArray, even with data in attrs, is not populating with anything. I'm still not sure why that is.

As much as I hate to do it, I may just have to try and use attrs instead of the TypedArray and do a little more manual work to pull the xml into the object.

Josh
  • 16,286
  • 25
  • 113
  • 158

0 Answers0