(See below) On Tue, Apr 26, 2011 at 11:40 PM, Vikram Bodicherla < vikram.bodiche...@mchron.com> wrote:
> So coming back to my question, any advice on design for now? > > The android developers blog suggests abstracting APIs like the > Contacts API. But it is not practical to go around abstracting all > system APIs. Neither would be want to put in model-specific if-else > clauses at different places in the application. How can we stand on > middle-ground here? > > Bob, can you please elaborate on your method? How can a system of > rules be designed so as to capture differences in EXIF timestamps that > I mentioned? Well, abstracting APIs is sort of a different-but-related issue. The big advantage of abstracting the API is that it lets you handle all of the conditionalization in a single place, rather than throughout your code. If the system API in question is defined as an interface, this can be done as a fairly simple proxy object that delegates to the standard system object, with whatever workarounds on specific methods. I have a methodology for doing that: You make your proxy object's InvocationHandler take two values: 1) the base system object, that implements the interface. 2) an override object, that also contains the base system object, and defines any methods you want to override. The InvocationHandler uses reflection to determine whether the override object defines the method -- if so, it invokes that one, which may delegate to the base object as well. Otherwise, it invokes the base object. You only have to define this InvocationHandler class once, and use it for every object you want to abstract, and you can implement caching of the lookup, so it can be as efficient as any reflection-based operation. This is very similar to how I do mock objects -- a topic for another day. Now, as for the rules, the easiest way would be a simple (but fictional) example: <rules> <!-- Joe's fixed Nexus build. --> <rule manufacturer='HTC' model='Nexus One' fingerprint='3a4cda03924df08234092487'> <attribute name='exif-timezone'>UTC</attribute> </rule> <!-- Old builds think everyone is in California --> <rule manufacturer='HTC' model='Nexus One' int_sdk='<8'> <attribute name='exif-timezone'>America/Los_Angeles</attribute> </rule> <!-- But otherwise, it does what most cameras do -- which is the most stupid thing possible. --> <rule manufacturer='HTC' model='Nexus One'> <attribute name='exif-timezone'>LOCAL</attribute> </rule> <!-- But Motorola tried to get it right --> <rule manufacturer='Motorola'> <attribute name='exif-timezone'>UTC</attribute> </rule> <!-- But if we don't know anything specific, assume stupid. --> <!-- Note that our TimeZone converter will need to special-case LOCAL to return TimeZone.getDefault() --> <rule> <!-- default rule --> <attribute name='exit-timezone'>LOCAL</attribute> </rule> </rules> When you build your app, you download and include the current version of this database. When you start the app the first time, and periodically thereafter, you try to update it. You can cache the results and discard any that aren't relevant to this device -- you can also include the relevant information in the query and reduce how much has to be sent to the device. (Of course, this requires internet permission, but it's an optional feature. There could also be a service app that publishes this information to other applications). You process each rule in reverse order; if the attributes match the values in android.os.Build and Build.VERSION, set those attributes in a Map<String,String>. Then look it up via Platform.getAttribute("exif-timezone", TimeZone.class) -- the TimeZone.class indicates that you want a TimeZone value; there'd be a set of available conversions. Or perhaps converter objects, e.g. public class Platform { ... public static Converter<String> TYPE_STRING = new StringConverter(); public static Converter<Integer> TYPE_INTEGER = new IntegerConverter(); public static Converter<Timezone> TYPE_TIMEZONE = new TimeZoneConverter(); ... /** * Obtain a platform attribute in the desired form. */ public static <T> T getAttribute(String name, Converter<T> converter) { ... } /** * The abstraction interface I mentioned earlier. */ public static <T, IMPL extends T> T customize(Class<T> iface, IMPL baseObject, Object override) { if (override instanceof OverrideHandler) { ((OverrideHandler)override).setBaseObject(baseObject); } return Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, new OverrideInvocationHandler(baseObject, override)); } } (This makes it extensible -- you can store things like base64-encoded images, young children, or small planetoids). Unfortunately, ExifInterface *IS* *NOT* *AN* *INTERFACE* -- boo hiss! -- so you can't use my Platform.customize method. (I have built systems that would automatically construct a suitable class using byte-code generation, but that won't work on Android). However, since ExifInterface is not declared final, and is directly constructed by the programmer, we can subclass it instead: public class ExifReader extends ExifInterface { public ExifReader(String filename) { super(filename); } public static final String ATTR_EXIF_TIMEZONE = "exif-timezone"; @Override public String getAttribute(String tag) { if (TAG_DATETIME.equals(tag)) { TimeZone tz = Platform.getAttribute(ATTR_EXIF_TIMEZONE, TYPE_TIMEZONE); return parseAndFixTime(super.getAttribute(tag), tz); } return super.getAttribute(tag); } } Unfortunately -- this still isn't quite right for this application -- because this assumes it's the current platform that took the pictures. Image files can be moved from device to device. So skipping a few more details in our Platform class, what we really want to do is: public class ExifReader extends ExifInterface { public ExifReader(String filename) { super(filename); } public static final String ATTR_EXIF_TIMEZONE = "exif-timezone"; @Override public String getAttribute(String tag) { if (TAG_DATETIME.equals(tag)) { String make = super.getAttribute(TAG_MAKE); String model = super.getAttribute(TAG_MODEL); Platform platform = new Platform(make, model); // Override manufacturer/model, leave rest unspecified. TimeZone tz = platform.getPlatformAttribute(ATTR_EXIF_TIMEZONE, TYPE_TIMEZONE); return parseAndFixTime(super.getAttribute(tag), tz); } return super.getAttribute(tag); } } The values of TAG_MAKE and TAG_MODEL may not correspond exactly to those in Build.MANUFACTURER and Build.MODEL; the rules will need to take into account any differences, as well as any non-Android devices which may appear -- Nikon, Lexica, etc. -- You received this message because you are subscribed to the Google Groups "Android Developers" group. To post to this group, send email to android-developers@googlegroups.com To unsubscribe from this group, send email to android-developers+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/android-developers?hl=en