(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='&lt;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

Reply via email to