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
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.