Initial checkin of RIM client from LifeMarks, after initial refactorings and style...
authorsrowen <srowen@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Mon, 4 Aug 2008 18:39:26 +0000 (18:39 +0000)
committersrowen <srowen@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Mon, 4 Aug 2008 18:39:26 +0000 (18:39 +0000)
git-svn-id: http://zxing.googlecode.com/svn/trunk@546 59b500cc-1b3d-0410-9834-0bbf25fbcc57

19 files changed:
AUTHORS
rim/src/com/google/zxing/client/rim/AboutScreen.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/AppPermissionsManager.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/Camera.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/HelpScreen.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/HistoryScreen.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/ImageCapturedJournalListener.java [deleted file]
rim/src/com/google/zxing/client/rim/QRCapturedJournalListener.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/SettingsScreen.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/ZXingLMMainScreen.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/ZXingMainScreen.java [deleted file]
rim/src/com/google/zxing/client/rim/ZXingUIApp.java [deleted file]
rim/src/com/google/zxing/client/rim/ZXingUiApplication.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/persistence/AppSettings.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/persistence/history/DecodeHistory.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/persistence/history/DecodeHistoryItem.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/util/Log.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/util/ReasonableTimer.java [new file with mode: 0644]
rim/src/com/google/zxing/client/rim/util/URLDecoder.java [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
index 93a7370..b1ab642 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -8,7 +8,9 @@ Christian Brunschen (Google)
 Daniel Switkin (Google)
 David Albert (Bug Labs)
 John Connolly (Bug Labs)
+Joseph Wain (Google)
 Matthew Schulkind (Google)
+Matt York (LifeMarks)
 Paul Hackenberger
 Sean Owen (Google)
-Vince Francis
\ No newline at end of file
+Vince Francis (LifeMarks)
diff --git a/rim/src/com/google/zxing/client/rim/AboutScreen.java b/rim/src/com/google/zxing/client/rim/AboutScreen.java
new file mode 100644 (file)
index 0000000..66d4135
--- /dev/null
@@ -0,0 +1,67 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim;\r
+\r
+import net.rim.device.api.ui.Field;\r
+import net.rim.device.api.ui.FieldChangeListener;\r
+import net.rim.device.api.ui.Manager;\r
+import net.rim.device.api.ui.Screen;\r
+import net.rim.device.api.ui.Ui;\r
+import net.rim.device.api.ui.UiEngine;\r
+import net.rim.device.api.ui.DrawStyle;\r
+import net.rim.device.api.ui.component.ButtonField;\r
+import net.rim.device.api.ui.component.LabelField;\r
+import net.rim.device.api.ui.container.MainScreen;\r
+import net.rim.device.api.ui.container.VerticalFieldManager;\r
+\r
+/**\r
+ * The screen used to display the application 'about' information.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+final class AboutScreen extends MainScreen {\r
+\r
+  AboutScreen() {\r
+    setTitle(new LabelField("ZXing - About", DrawStyle.ELLIPSIS | USE_ALL_WIDTH));\r
+    Manager vfm = new VerticalFieldManager(FIELD_HCENTER);\r
+    Field title = new LabelField("ZXing - BlackBerry Client", FIELD_HCENTER);\r
+    Field uri = new LabelField("http://code.google.com/p/zxing/", FIELD_HCENTER);\r
+    vfm.add(title);\r
+    vfm.add(uri);\r
+    Field okButton = new ButtonField("OK", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+    okButton.setChangeListener(new ButtonListener(this));\r
+    vfm.add(okButton);\r
+    add(vfm);\r
+  }\r
+\r
+  /**\r
+   * Used to close the screen when the ok button is pressed.\r
+   */\r
+  private static class ButtonListener implements FieldChangeListener {\r
+    private final Screen screen;\r
+    private ButtonListener(Screen screen) {\r
+      this.screen = screen;\r
+    }\r
+    public void fieldChanged(Field field, int context) {\r
+      UiEngine ui = Ui.getUiEngine();\r
+      ui.popScreen(screen);\r
+    }\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/AppPermissionsManager.java b/rim/src/com/google/zxing/client/rim/AppPermissionsManager.java
new file mode 100644 (file)
index 0000000..791fb5d
--- /dev/null
@@ -0,0 +1,67 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim;\r
+\r
+import com.google.zxing.client.rim.util.Log;\r
+import net.rim.device.api.applicationcontrol.ApplicationPermissions;\r
+import net.rim.device.api.applicationcontrol.ApplicationPermissionsManager;\r
+\r
+/**\r
+ * Requests the necessary permissions for the application.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+final class AppPermissionsManager {\r
+\r
+  private static final ApplicationPermissionsManager apm = ApplicationPermissionsManager.getInstance();\r
+\r
+  private AppPermissionsManager() {\r
+  }\r
+\r
+  /**\r
+   * Requests the required application permissions. Currently the required permissions are\r
+   * event injection (sending system level key strokes to other running applications) and\r
+   * accessing files (accessing the file when a qrcode image is saved to the file system).\r
+   */\r
+  static void setPermissions() {\r
+    setPermission(ApplicationPermissions.PERMISSION_EVENT_INJECTOR);\r
+    setPermission(ApplicationPermissions.PERMISSION_FILE_API);\r
+  }\r
+\r
+  private static boolean setPermission(int permission) {\r
+    boolean updatedPermissions = false;\r
+    ApplicationPermissions ap = apm.getApplicationPermissions();\r
+    if (ap.containsPermissionKey(permission)) {\r
+      int eventInjectorPermission = ap.getPermission(permission);\r
+      Log.info("permission (" + permission + "): " + eventInjectorPermission);\r
+      if (eventInjectorPermission != ApplicationPermissions.VALUE_ALLOW) {\r
+        Log.info("Setting permission to VALUE_ALLOW.");\r
+        ap.addPermission(permission);\r
+        updatedPermissions = apm.invokePermissionsRequest(ap);\r
+      }\r
+    } else {\r
+      Log.info("Setting permission (" + permission + ") to VALUE_ALLOW.");\r
+      ap.addPermission(permission);\r
+      updatedPermissions = apm.invokePermissionsRequest(ap);\r
+    }\r
+    Log.info("updatedPermissions: " + updatedPermissions);\r
+    return updatedPermissions;\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/Camera.java b/rim/src/com/google/zxing/client/rim/Camera.java
new file mode 100644 (file)
index 0000000..2107798
--- /dev/null
@@ -0,0 +1,213 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim;\r
+\r
+import com.google.zxing.client.rim.util.Log;\r
+import net.rim.blackberry.api.invoke.CameraArguments;\r
+import net.rim.blackberry.api.invoke.Invoke;\r
+import net.rim.device.api.system.Characters;\r
+import net.rim.device.api.system.EventInjector;\r
+import net.rim.device.api.ui.UiApplication;\r
+\r
+/**\r
+ * Singleton used to control access to the camera.\r
+ * Unfortunatly, the Camera API only allows invoking the camera.\r
+ * \r
+ * Note: This code still contains experimental code to determine and set the camera resolution by\r
+ * using system level key events, but didn't not function reliably and is not used.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+final class Camera {\r
+\r
+  /** milliseconds to wait before starting key strokes */\r
+  private static final int INITIALIZATION_TIME_MS = 500; // simulator seems to need >= 500\r
+  private static final int KEY_PAUSE_TIME_MS = 100; // simulator seems to need >= 100\r
+\r
+  private static Camera instance;\r
+\r
+  /** Attempting to set camera resolution is disabled. */\r
+  private final boolean setResolution = false;\r
+\r
+  private Camera() {\r
+  }\r
+\r
+  /**\r
+   * Returns the single instance of the camera.\r
+   */\r
+  static Camera getInstance() {\r
+    if (instance == null) {\r
+      instance = new Camera();\r
+    }\r
+    return instance;\r
+  }\r
+\r
+  /**\r
+   * Starts the blackberry camera application.\r
+   */\r
+  void invoke() {\r
+    Invoke.invokeApplication(Invoke.APP_TYPE_CAMERA, new CameraArguments());\r
+    if (setResolution) {\r
+      sleep(INITIALIZATION_TIME_MS);\r
+      setMinResolution();\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Exits the blackberry camera application.\r
+   */\r
+  void exit() {\r
+    if (setResolution) {\r
+      setMaxResolution(); // for now, we dont know the original resolution setting. Assume it was max res.\r
+      sleep(KEY_PAUSE_TIME_MS);\r
+    }\r
+    sleep(3000); // this sleep is needed for the esc to be processed(3000 originally)\r
+    UiApplication app = UiApplication.getUiApplication();\r
+    if (app != null) {\r
+      Log.info("active app: " + app.getClass().getName());\r
+      if (app.isForeground()) {\r
+        Log.info("Lifemarks is the foreground app.");\r
+      } else {\r
+        Log.info("Lifemarks is not the foreground app. Attempt to close camera.");\r
+        keyUpAndDown(Characters.ESCAPE); // need two (no timeout in between esc key presses seems to work best)\r
+        keyUpAndDown(Characters.ESCAPE);\r
+      }\r
+    } else {\r
+      Log.error("??? app is null ???");\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Sets the camera resolution to it's minimum.\r
+   * Note: currently disabled.\r
+   */\r
+  private static void setMaxResolution() {\r
+    Log.info("Setting resolution to max.");\r
+    accessResolutionMenuAfterSave();\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.CONTROL_DOWN);\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.CONTROL_DOWN); // min res\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    trackBallClick(); // out of res menu\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    trackBallClick(); // into res menu\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.CONTROL_UP);\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.CONTROL_UP); // max res\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    trackBallClick(); // out of res menu\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.ESCAPE); // out of options\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    trackBallClick(); // yes to changes, even if there werent really any!\r
+    Log.info("Finished setting resolution to max.");\r
+  }\r
+\r
+  /**\r
+   * Sets the camera resolution to it's maximum.\r
+   * Note: currently disabled.\r
+   */\r
+  private static void setMinResolution() {\r
+    Log.info("Setting resolution to min.");\r
+    accessResolutionMenu();\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.CONTROL_UP);\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.CONTROL_UP); // max res\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    trackBallClick(); // out of res menu\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    trackBallClick(); // into res menu\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.CONTROL_DOWN);\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.CONTROL_DOWN); // min res\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    trackBallClick(); // out of res menu\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.ESCAPE); // out of options\r
+    trackBallClick(); // yes to changes, even if there werent really any!\r
+  }\r
+\r
+  private static void accessResolutionMenu() {\r
+    keyUpAndDown(Characters.CONTROL_MENU);\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.CONTROL_DOWN);\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    trackBallClick();\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.CONTROL_DOWN);\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    keyUpAndDown(Characters.CONTROL_DOWN);\r
+    sleep(KEY_PAUSE_TIME_MS);\r
+    trackBallClick();\r
+  }\r
+\r
+  private static void accessResolutionMenuAfterSave() {\r
+    keyUpAndDown(Characters.CONTROL_MENU);\r
+    keyUpAndDown(Characters.CONTROL_DOWN, 6, 0); // seems to be down 6 items on bb and 4 on simulator\r
+    trackBallClick();\r
+    keyUpAndDown(Characters.CONTROL_DOWN);\r
+    keyUpAndDown(Characters.CONTROL_DOWN);\r
+    trackBallClick();\r
+  }\r
+\r
+  /**\r
+   * Puts the current thread to sleep for a given amount of time.\r
+   */\r
+  private static void sleep(int time) {\r
+    try {\r
+      Thread.sleep(time);\r
+    } catch (InterruptedException ie) {\r
+      // continue\r
+    }\r
+  }\r
+\r
+\r
+  private static void trackBallClick() {\r
+    EventInjector.invokeEvent(\r
+            new EventInjector.NavigationEvent(EventInjector.NavigationEvent.NAVIGATION_CLICK, 0, 0, 1));\r
+    EventInjector.invokeEvent(\r
+            new EventInjector.NavigationEvent(EventInjector.NavigationEvent.NAVIGATION_UNCLICK, 0, 0, 1));\r
+  }\r
+\r
+  /**\r
+   * Sends system level key events a given number of times with the given delay between them.\r
+   */\r
+  private static void keyUpAndDown(char character, int times, int delay) {\r
+    for (int i = 0; i < times; i++) {\r
+      keyUpAndDown(character);\r
+      if (delay > 0) {\r
+        sleep(delay);\r
+      }\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Sends one system level key event.\r
+   */\r
+  private static void keyUpAndDown(char character) {\r
+    EventInjector.invokeEvent(new EventInjector.KeyEvent(EventInjector.KeyEvent.KEY_DOWN, character, 0, 1));\r
+    EventInjector.invokeEvent(new EventInjector.KeyEvent(EventInjector.KeyEvent.KEY_UP, character, 0, 1));\r
+  }\r
+\r
+}\r
+\r
diff --git a/rim/src/com/google/zxing/client/rim/HelpScreen.java b/rim/src/com/google/zxing/client/rim/HelpScreen.java
new file mode 100644 (file)
index 0000000..f992cb4
--- /dev/null
@@ -0,0 +1,65 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim;\r
+\r
+import net.rim.device.api.ui.DrawStyle;\r
+import net.rim.device.api.ui.Field;\r
+import net.rim.device.api.ui.FieldChangeListener;\r
+import net.rim.device.api.ui.Screen;\r
+import net.rim.device.api.ui.Ui;\r
+import net.rim.device.api.ui.UiEngine;\r
+import net.rim.device.api.ui.Manager;\r
+import net.rim.device.api.ui.component.ButtonField;\r
+import net.rim.device.api.ui.component.LabelField;\r
+import net.rim.device.api.ui.container.MainScreen;\r
+import net.rim.device.api.ui.container.VerticalFieldManager;\r
+\r
+/**\r
+ * The screen used to display the application help information.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+final class HelpScreen extends MainScreen {\r
+\r
+  HelpScreen() {\r
+    setTitle(new LabelField("ZXing - Help", DrawStyle.ELLIPSIS | USE_ALL_WIDTH));\r
+    Manager vfm = new VerticalFieldManager(FIELD_HCENTER);\r
+    Field aboutText = new LabelField("help info...", FIELD_HCENTER);\r
+    vfm.add(aboutText);\r
+    Field okButton = new ButtonField("OK", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+    okButton.setChangeListener(new ButtonListener(this));\r
+    vfm.add(okButton);\r
+    add(vfm);\r
+  }\r
+\r
+  /**\r
+   * Closes the screen when the OK button is pressed.\r
+   */\r
+  private static class ButtonListener implements FieldChangeListener {\r
+    private final Screen screen;\r
+    private ButtonListener(Screen screen) {\r
+      this.screen = screen;\r
+    }\r
+    public void fieldChanged(Field field, int context) {\r
+      UiEngine ui = Ui.getUiEngine();\r
+      ui.popScreen(screen);\r
+    }\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/HistoryScreen.java b/rim/src/com/google/zxing/client/rim/HistoryScreen.java
new file mode 100644 (file)
index 0000000..781f2d0
--- /dev/null
@@ -0,0 +1,81 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim;\r
+\r
+import com.google.zxing.client.rim.persistence.history.DecodeHistory;\r
+import com.google.zxing.client.rim.persistence.history.DecodeHistoryItem;\r
+import com.google.zxing.client.rim.util.Log;\r
+import net.rim.blackberry.api.browser.Browser;\r
+import net.rim.blackberry.api.browser.BrowserSession;\r
+import net.rim.device.api.ui.DrawStyle;\r
+import net.rim.device.api.ui.Field;\r
+import net.rim.device.api.ui.FieldChangeListener;\r
+import net.rim.device.api.ui.Manager;\r
+import net.rim.device.api.ui.FieldLabelProvider;\r
+import net.rim.device.api.ui.component.ButtonField;\r
+import net.rim.device.api.ui.component.LabelField;\r
+import net.rim.device.api.ui.container.MainScreen;\r
+import net.rim.device.api.ui.container.VerticalFieldManager;\r
+\r
+/**\r
+ * The screen used to display the qrcode decoding history.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+final class HistoryScreen extends MainScreen {\r
+\r
+  HistoryScreen() {\r
+    setTitle(new LabelField("ZXing - History", DrawStyle.ELLIPSIS | USE_ALL_WIDTH));\r
+    Manager vfm = new VerticalFieldManager(FIELD_HCENTER | VERTICAL_SCROLL);\r
+    Log.debug("Num history items: " + DecodeHistory.getInstance().getNumItems());\r
+    DecodeHistory history = DecodeHistory.getInstance();\r
+    FieldChangeListener itemListener = new ButtonListener();\r
+    for (int i = 0; i < history.getNumItems(); i++) {\r
+      DecodeHistoryItem item = history.getItemAt(i);\r
+      Field labelButton = new ButtonField(item.getURI(), FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+      labelButton.setChangeListener(itemListener);\r
+      vfm.add(labelButton);\r
+    }\r
+\r
+    Field okButton = new ButtonField("OK", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+    okButton.setChangeListener(itemListener);\r
+    add(vfm);\r
+  }\r
+\r
+  /**\r
+   * Closes the screen when the OK button is pressed.\r
+   */\r
+  private static class ButtonListener implements FieldChangeListener {\r
+    public void fieldChanged(Field field, int context) {\r
+      if (field instanceof ButtonField) {\r
+        BrowserSession browserSession = Browser.getDefaultSession();\r
+        browserSession.displayPage(((FieldLabelProvider) field).getLabel());\r
+      }\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Overriding this method removes the save changes prompt\r
+   */\r
+  public boolean onSavePrompt() {\r
+    setDirty(false);\r
+    return true;\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/ImageCapturedJournalListener.java b/rim/src/com/google/zxing/client/rim/ImageCapturedJournalListener.java
deleted file mode 100644 (file)
index 3990575..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*\r
- * Copyright 2008 ZXing authors\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.google.zxing.client.rim;\r
-\r
-import net.rim.device.api.io.file.FileSystemJournal;\r
-import net.rim.device.api.io.file.FileSystemJournalEntry;\r
-import net.rim.device.api.io.file.FileSystemJournalListener;\r
-\r
-/**\r
- * @author Sean Owen (srowen@google.com)\r
- */\r
-final class ImageCapturedJournalListener implements FileSystemJournalListener {\r
-\r
-  private final ZXingMainScreen screen;\r
-  private long lastUSN;\r
-\r
-  ImageCapturedJournalListener(ZXingMainScreen screen) {\r
-    this.screen = screen;\r
-  }\r
-\r
-  public void fileJournalChanged() {\r
-    long nextUSN = FileSystemJournal.getNextUSN();\r
-    for (long lookUSN = nextUSN - 1; lookUSN >= lastUSN; --lookUSN) {\r
-      FileSystemJournalEntry entry = FileSystemJournal.getEntry(lookUSN);\r
-      if (entry == null) {\r
-        break;\r
-      }\r
-      if (entry.getEvent() == FileSystemJournalEntry.FILE_ADDED) {\r
-        screen.handleFile(entry.getPath());\r
-      }\r
-    }\r
-    lastUSN = nextUSN;\r
-  }\r
-\r
-\r
-}\r
diff --git a/rim/src/com/google/zxing/client/rim/QRCapturedJournalListener.java b/rim/src/com/google/zxing/client/rim/QRCapturedJournalListener.java
new file mode 100644 (file)
index 0000000..a13c7df
--- /dev/null
@@ -0,0 +1,51 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim;\r
+\r
+import com.google.zxing.client.rim.util.Log;\r
+import net.rim.device.api.io.file.FileSystemJournal;\r
+import net.rim.device.api.io.file.FileSystemJournalEntry;\r
+import net.rim.device.api.io.file.FileSystemJournalListener;\r
+\r
+import java.util.Date;\r
+\r
+/**\r
+ * The listener that is fired when an image file is added to the file system.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+final class QRCapturedJournalListener implements FileSystemJournalListener {\r
+\r
+  private final ZXingLMMainScreen screen;\r
+\r
+  QRCapturedJournalListener(ZXingLMMainScreen screen) {\r
+    this.screen = screen;\r
+  }\r
+\r
+  public void fileJournalChanged() {\r
+    long lookUSN = FileSystemJournal.getNextUSN() - 1; // the last file added to the filesystem\r
+    Log.debug("lookUSN: " + lookUSN);\r
+    FileSystemJournalEntry entry = FileSystemJournal.getEntry(lookUSN);\r
+    if (entry != null && entry.getEvent() == FileSystemJournalEntry.FILE_ADDED) {\r
+      Log.info("Got file: " + entry.getPath() + " @: " + new Date());\r
+      screen.imageSaved(entry.getPath());\r
+    }\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/SettingsScreen.java b/rim/src/com/google/zxing/client/rim/SettingsScreen.java
new file mode 100644 (file)
index 0000000..8a68ba6
--- /dev/null
@@ -0,0 +1,104 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim;\r
+\r
+import com.google.zxing.client.rim.persistence.AppSettings;\r
+import com.google.zxing.client.rim.persistence.history.DecodeHistory;\r
+import com.google.zxing.client.rim.util.Log;\r
+import net.rim.device.api.ui.Field;\r
+import net.rim.device.api.ui.FieldChangeListener;\r
+import net.rim.device.api.ui.Manager;\r
+import net.rim.device.api.ui.Screen;\r
+import net.rim.device.api.ui.Ui;\r
+import net.rim.device.api.ui.UiEngine;\r
+import net.rim.device.api.ui.DrawStyle;\r
+import net.rim.device.api.ui.component.ButtonField;\r
+import net.rim.device.api.ui.component.CheckboxField;\r
+import net.rim.device.api.ui.component.LabelField;\r
+import net.rim.device.api.ui.container.MainScreen;\r
+import net.rim.device.api.ui.container.VerticalFieldManager;\r
+\r
+/**\r
+ * Screen used to change application settings.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+final class SettingsScreen extends MainScreen {\r
+\r
+  private boolean changes;\r
+  private final AppSettings settings;\r
+  private final CheckboxField camResMsgCheckBox;\r
+\r
+  SettingsScreen() {\r
+    setTitle(new LabelField("ZXing - Settings", DrawStyle.ELLIPSIS | USE_ALL_WIDTH));\r
+    Manager vfm = new VerticalFieldManager(FIELD_HCENTER);\r
+\r
+    settings = AppSettings.getInstance();\r
+    Boolean cameraResMsgSetting = settings.getBooleanItem(AppSettings.SETTING_CAM_RES_MSG);\r
+    boolean cameraResMsgSettingBool = (cameraResMsgSetting != null) && cameraResMsgSetting.booleanValue();\r
+    // 0\r
+    camResMsgCheckBox = new CheckboxField("Don't show camera resolution message", cameraResMsgSettingBool);\r
+    camResMsgCheckBox.setChangeListener(new ButtonListener(this));\r
+    vfm.add(camResMsgCheckBox);\r
+\r
+    // 1\r
+    Field clearHistoryButton = new ButtonField("Clear History",FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+    clearHistoryButton.setChangeListener(new ButtonListener(this));\r
+    vfm.add(clearHistoryButton);\r
+\r
+    // 2\r
+    Field okButton = new ButtonField("OK", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+    okButton.setChangeListener(new ButtonListener(this));\r
+    vfm.add(okButton);\r
+\r
+    add(vfm);\r
+  }\r
+\r
+  /**\r
+   * Listens for button clicks and executes the appropriate action.\r
+   */\r
+  private final class ButtonListener implements FieldChangeListener {\r
+    private final Screen screen;\r
+    private ButtonListener(Screen screen) {\r
+      this.screen = screen;\r
+    }\r
+    public void fieldChanged(Field field, int context) {\r
+      Log.debug("Field: " + field.getIndex() + " , context: " + context);\r
+      switch (field.getIndex()) {\r
+        case 0:\r
+          settings.addItem(AppSettings.SETTING_CAM_RES_MSG,\r
+                           (camResMsgCheckBox.getChecked()) ? Boolean.TRUE : Boolean.FALSE);\r
+          changes = true;\r
+          break;\r
+        case 1:\r
+          // TODO confirm\r
+          DecodeHistory.getInstance().clear();\r
+          DecodeHistory.getInstance().persist();\r
+        case 2: //ok\r
+          if (changes) {\r
+            AppSettings.getInstance().persist();\r
+          }\r
+          UiEngine ui = Ui.getUiEngine();\r
+          ui.popScreen(screen);\r
+          break;\r
+      }\r
+    }\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/ZXingLMMainScreen.java b/rim/src/com/google/zxing/client/rim/ZXingLMMainScreen.java
new file mode 100644 (file)
index 0000000..1e8a2f5
--- /dev/null
@@ -0,0 +1,331 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim;\r
+\r
+import com.google.zxing.DecodeHintType;\r
+import com.google.zxing.MonochromeBitmapSource;\r
+import com.google.zxing.MultiFormatReader;\r
+import com.google.zxing.Reader;\r
+import com.google.zxing.ReaderException;\r
+import com.google.zxing.Result;\r
+import com.google.zxing.client.j2me.LCDUIImageMonochromeBitmapSource;\r
+import com.google.zxing.client.rim.persistence.AppSettings;\r
+import com.google.zxing.client.rim.persistence.history.DecodeHistory;\r
+import com.google.zxing.client.rim.persistence.history.DecodeHistoryItem;\r
+import com.google.zxing.client.rim.util.Log;\r
+import com.google.zxing.client.rim.util.ReasonableTimer;\r
+import com.google.zxing.client.rim.util.URLDecoder;\r
+import net.rim.blackberry.api.browser.Browser;\r
+import net.rim.blackberry.api.browser.BrowserSession;\r
+import net.rim.device.api.ui.DrawStyle;\r
+import net.rim.device.api.ui.Field;\r
+import net.rim.device.api.ui.FieldChangeListener;\r
+import net.rim.device.api.ui.Manager;\r
+import net.rim.device.api.ui.UiApplication;\r
+import net.rim.device.api.ui.component.ButtonField;\r
+import net.rim.device.api.ui.component.Dialog;\r
+import net.rim.device.api.ui.component.LabelField;\r
+import net.rim.device.api.ui.container.DialogFieldManager;\r
+import net.rim.device.api.ui.container.MainScreen;\r
+import net.rim.device.api.ui.container.PopupScreen;\r
+import net.rim.device.api.ui.container.VerticalFieldManager;\r
+\r
+import javax.microedition.io.Connector;\r
+import javax.microedition.io.file.FileConnection;\r
+import javax.microedition.lcdui.Image;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.util.Hashtable;\r
+\r
+/**\r
+ * The main appication menu screen.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+final class ZXingLMMainScreen extends MainScreen {\r
+\r
+  private final ZXingUiApplication app;\r
+  private final QRCapturedJournalListener imageListener;\r
+  private PopupScreen popup;\r
+  private final Reader reader;\r
+  private final Hashtable readerHints;\r
+\r
+  ZXingLMMainScreen() {\r
+    super(DEFAULT_MENU | DEFAULT_CLOSE);\r
+    setTitle(new LabelField("ZXing", DrawStyle.ELLIPSIS | USE_ALL_WIDTH));\r
+    setChangeListener(null);\r
+\r
+    Manager vfm = new VerticalFieldManager(USE_ALL_WIDTH);\r
+    FieldChangeListener buttonListener = new ButtonListener();\r
+\r
+    //0\r
+    Field snapButton = new ButtonField("Snap", FIELD_HCENTER | ButtonField.CONSUME_CLICK | USE_ALL_WIDTH);\r
+    snapButton.setChangeListener(buttonListener);\r
+    vfm.add(snapButton);\r
+\r
+    //1\r
+    Field historyButton = new ButtonField("History", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+    historyButton.setChangeListener(buttonListener);\r
+    vfm.add(historyButton);\r
+\r
+    //2\r
+    Field settingsButton = new ButtonField("Settings", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+    settingsButton.setChangeListener(buttonListener);\r
+    vfm.add(settingsButton);\r
+\r
+    //3\r
+    Field aboutButton = new ButtonField("About", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+    aboutButton.setChangeListener(buttonListener);\r
+    vfm.add(aboutButton);\r
+\r
+    //4\r
+    Field helpButton = new ButtonField("Help", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+    helpButton.setChangeListener(buttonListener);\r
+    vfm.add(helpButton);\r
+\r
+    vfm.setChangeListener(null);\r
+    add(vfm);\r
+\r
+\r
+    app = (ZXingUiApplication) UiApplication.getUiApplication();\r
+    imageListener = new QRCapturedJournalListener(this);\r
+\r
+    reader = new MultiFormatReader();\r
+    readerHints = new Hashtable(1);\r
+    readerHints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);\r
+  }\r
+\r
+\r
+  /**\r
+   * Handles the newly created file. If the file is a jpg image, from the camera, the images is assumed to be\r
+   * a qrcode and decoding is attempted.\r
+   */\r
+  void imageSaved(String imagePath) {\r
+    Log.info("Image saved: " + imagePath);\r
+    app.removeFileSystemJournalListener(imageListener);\r
+    if (imagePath.endsWith(".jpg") && imagePath.indexOf("IMG") >= 0) // a blackberry camera image file\r
+    {\r
+      Log.info("imageSaved - Got file: " + imagePath);\r
+      Camera.getInstance().exit();\r
+      Log.info("camera exit finished");\r
+      app.requestForeground();\r
+\r
+      DialogFieldManager manager = new DialogFieldManager();\r
+      popup = new PopupScreen(manager);\r
+      manager.addCustomField(new LabelField("Decoding image..."));\r
+\r
+      app.pushScreen(popup); // original\r
+      Log.info("started progress screen.");\r
+\r
+      Runnable fct = new FileConnectionThread(imagePath);\r
+      Log.info("Starting file connection thread.");\r
+      app.invokeLater(fct);\r
+      Log.info("Finished file connection thread.");\r
+    } else {\r
+      Log.error("Failed to locate camera image.");\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Closes the application and persists all required data.\r
+   */\r
+  public void close() {\r
+    app.removeFileSystemJournalListener(imageListener);\r
+    DecodeHistory.getInstance().persist();\r
+    super.close();\r
+  }\r
+\r
+  /**\r
+   * This method is overriden to remove the 'save changes' dialog when exiting.\r
+   */\r
+  public boolean onSavePrompt() {\r
+    setDirty(false);\r
+    return true;\r
+  }\r
+\r
+  /**\r
+   * Listens for selected buttons and starts the required screen.\r
+   */\r
+  private final class ButtonListener implements FieldChangeListener {\r
+    public void fieldChanged(Field field, int context) {\r
+      Log.debug("*** fieldChanged: " + field.getIndex());\r
+      switch (field.getIndex()) {\r
+        case 0: // snap\r
+          try {\r
+            app.addFileSystemJournalListener(imageListener);\r
+            Camera.getInstance().invoke(); // start camera\r
+            return;\r
+          }\r
+          catch (Exception e) {\r
+            Log.error("!!! Problem invoking camera.!!!: " + e);\r
+          }\r
+          break;\r
+        case 1: // history\r
+          app.pushScreen(new HistoryScreen());\r
+          break;\r
+        case 2: // settings\r
+          app.pushScreen(new SettingsScreen());\r
+          break;\r
+        case 3: //about\r
+          app.pushScreen(new AboutScreen());\r
+          break;\r
+        case 4: //help\r
+          app.pushScreen(new HelpScreen());\r
+          break;\r
+      }\r
+    }\r
+\r
+  }\r
+\r
+  /**\r
+   * Thread that decodes the newly created image. If the image is successfully decoded and the data is a URL,\r
+   * the browser is invoked and pointed to the given URL.\r
+   */\r
+  private final class FileConnectionThread implements Runnable {\r
+\r
+    private final String imagePath;\r
+\r
+    private FileConnectionThread(String imagePath) {\r
+      this.imagePath = imagePath;\r
+    }\r
+\r
+    public void run() {\r
+      FileConnection file = null;\r
+      InputStream is = null;\r
+      Image capturedImage = null;\r
+      try {\r
+        file = (FileConnection) Connector.open("file://" + imagePath, Connector.READ_WRITE);\r
+        is = file.openInputStream();\r
+        capturedImage = Image.createImage(is);\r
+      } catch (IOException e) {\r
+        Log.error("Problem creating image: " + e);\r
+        removeProgressBar();\r
+        invalidate();\r
+        showMessage("An error occured processing the image.");\r
+        return;\r
+      } finally {\r
+        if (is != null) {\r
+          try {\r
+            is.close();\r
+          } catch (IOException ioe) {\r
+          }\r
+        }\r
+        if (file != null && file.exists()) {\r
+          if (file.isOpen()) {\r
+            //file.close();\r
+          }\r
+          //file.delete();\r
+          Log.info("Deleted image file.");\r
+        }\r
+      }\r
+\r
+      if (capturedImage != null) {\r
+        Log.info("Got image...");\r
+        MonochromeBitmapSource source = new LCDUIImageMonochromeBitmapSource(capturedImage);\r
+        Result result;\r
+        ReasonableTimer decodingTimer = null;\r
+        try {\r
+          decodingTimer = new ReasonableTimer();\r
+          Log.info("Attempting to decode image...");\r
+          result = reader.decode(source, readerHints);\r
+          decodingTimer.finished();\r
+        } catch (ReaderException e) {\r
+          Log.error("Could not decode image: " + e);\r
+          decodingTimer.finished();\r
+          removeProgressBar();\r
+          invalidate();\r
+          boolean showResolutionMsg =\r
+                  !AppSettings.getInstance().getBooleanItem(AppSettings.SETTING_CAM_RES_MSG).booleanValue();\r
+          if (showResolutionMsg) {\r
+            showMessage("A QR Code was not found in the image. " +\r
+                        "We detected that the decoding process took quite a while. " +\r
+                        "It will be much faster if you decrease your camera's resolution (640x480).");\r
+          } else {\r
+            showMessage("A QR Code was not found in the image.");\r
+          }\r
+          return;\r
+        }\r
+        if (result != null) {\r
+          String resultText = result.getText();\r
+          Log.info("result: " + resultText);\r
+          if (isURI(resultText)) {\r
+            resultText = URLDecoder.decode(resultText);\r
+            removeProgressBar();\r
+            invalidate();\r
+            if (!decodingTimer.wasResonableTime() &&\r
+                !AppSettings.getInstance().getBooleanItem(AppSettings.SETTING_CAM_RES_MSG).booleanValue()) {\r
+              showMessage("We detected that the decoding process took quite a while. " +\r
+                          "It will be much faster if you decrease your camera's resolution (640x480).");\r
+            }\r
+            DecodeHistory.getInstance().addHistoryItem(new DecodeHistoryItem(resultText));\r
+            invokeBrowser(resultText);\r
+            return;\r
+          }\r
+        } else {\r
+          removeProgressBar();\r
+          invalidate();\r
+          showMessage("A QR Code was not found in the image.");\r
+          return;\r
+        }\r
+\r
+      }\r
+\r
+      removeProgressBar();\r
+      invalidate();\r
+    }\r
+\r
+    /**\r
+     * Quick check to see if the result of decoding the qr code was a valid uri.\r
+     */\r
+    private boolean isURI(String uri) {\r
+      return uri.startsWith("http://");\r
+    }\r
+\r
+    /**\r
+     * Invokes the web browser and browses to the given uri.\r
+     */\r
+    private void invokeBrowser(String uri) {\r
+      BrowserSession browserSession = Browser.getDefaultSession();\r
+      browserSession.displayPage(uri);\r
+    }\r
+\r
+    /**\r
+     * Syncronized version of removing progress dialog.\r
+     * NOTE: All methods accessing the gui that are in seperate threads should syncronize on app.getEventLock()\r
+     */\r
+    private void removeProgressBar() {\r
+      synchronized (app.getAppEventLock()) {\r
+        if (popup != null) {\r
+          app.popScreen(popup);\r
+        }\r
+      }\r
+    }\r
+\r
+    /**\r
+     * Syncronized version of showing a message dialog.\r
+     * NOTE: All methods accessing the gui that are in seperate threads should syncronize on app.getEventLock()\r
+     */\r
+    private void showMessage(String message) {\r
+      synchronized (app.getAppEventLock()) {\r
+        Dialog.alert(message);\r
+      }\r
+    }\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/ZXingMainScreen.java b/rim/src/com/google/zxing/client/rim/ZXingMainScreen.java
deleted file mode 100644 (file)
index 0d5a567..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-/*\r
- * Copyright 2008 ZXing authors\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.google.zxing.client.rim;\r
-\r
-import com.google.zxing.MonochromeBitmapSource;\r
-import com.google.zxing.MultiFormatReader;\r
-import com.google.zxing.Reader;\r
-import com.google.zxing.ReaderException;\r
-import com.google.zxing.Result;\r
-import com.google.zxing.client.j2me.LCDUIImageMonochromeBitmapSource;\r
-import net.rim.blackberry.api.invoke.CameraArguments;\r
-import net.rim.blackberry.api.invoke.Invoke;\r
-import net.rim.device.api.system.Characters;\r
-import net.rim.device.api.system.EventInjector;\r
-import net.rim.device.api.ui.UiApplication;\r
-import net.rim.device.api.ui.component.Dialog;\r
-import net.rim.device.api.ui.component.LabelField;\r
-import net.rim.device.api.ui.container.MainScreen;\r
-\r
-import javax.microedition.io.Connector;\r
-import javax.microedition.io.file.FileConnection;\r
-import javax.microedition.lcdui.Image;\r
-import java.io.IOException;\r
-import java.io.InputStream;\r
-\r
-/**\r
- * @author Sean Owen (srowen@google.com)\r
- */\r
-final class ZXingMainScreen extends MainScreen {\r
-\r
-  private final ZXingUIApp app;\r
-  private final ImageCapturedJournalListener captureListener;\r
-\r
-  ZXingMainScreen() {\r
-    setTitle("ZXing Barcode Reader");\r
-    add(new LabelField("UNDER CONSTRUCTION"));\r
-    add(new LabelField("1. Press 'Enter' at right to launch the Camera application"));\r
-    add(new LabelField("2. Configure Camera to capture 640x480 image"));\r
-    add(new LabelField("3. Take a picture of a barcode"));\r
-    add(new LabelField("4. If not returned to this application to see result, close Camera application"));\r
-    app = (ZXingUIApp) UiApplication.getUiApplication();\r
-    captureListener = new ImageCapturedJournalListener(this);\r
-    app.addFileSystemJournalListener(captureListener);\r
-  }\r
-\r
-  public boolean keyChar(char c, int status, int time) {\r
-    if (c == Characters.ENTER) {\r
-      Invoke.invokeApplication(Invoke.APP_TYPE_CAMERA, new CameraArguments());\r
-      return true;\r
-    } else {\r
-      return super.keyChar(c, status, time);\r
-    }\r
-  }\r
-\r
-  public void close() {\r
-    app.removeFileSystemJournalListener(captureListener);\r
-    super.close();\r
-  }\r
-\r
-  private void showMessage(String msg) {\r
-    synchronized (app.getAppEventLock()) {\r
-      Dialog.alert(msg);\r
-    }\r
-  }\r
-\r
-  void handleFile(String path) {\r
-    if (path.endsWith(".jpg") && path.indexOf("IMG") >= 0) {\r
-      // Get out of camera app\r
-      EventInjector.invokeEvent(new EventInjector.KeyEvent(EventInjector.KeyEvent.KEY_DOWN, Characters.ESCAPE, 0, 1));\r
-      EventInjector.invokeEvent(new EventInjector.KeyEvent(EventInjector.KeyEvent.KEY_UP, Characters.ESCAPE, 0, 1));\r
-      // Try to come to foreground for good measure\r
-      app.requestForeground();\r
-      try {\r
-        FileConnection file = null;\r
-        InputStream is = null;\r
-        Image capturedImage;\r
-        try {\r
-          file = (FileConnection) Connector.open("file://" + path);\r
-          is = file.openInputStream();\r
-          capturedImage = Image.createImage(is);\r
-        } finally {\r
-          if (is != null) {\r
-            try {\r
-              is.close();\r
-            } catch (IOException ioe) {\r
-              // continue\r
-            }\r
-          }\r
-          if (file != null) {\r
-            try {\r
-              file.close();\r
-            } catch (IOException ioe) {\r
-              // continue\r
-            }\r
-          }\r
-        }\r
-        MonochromeBitmapSource source = new LCDUIImageMonochromeBitmapSource(capturedImage);\r
-        Reader reader = new MultiFormatReader();\r
-        Result result = reader.decode(source);\r
-        // If decode was successful...\r
-        try {\r
-          file.delete();\r
-        } catch (IOException ioe) {\r
-          // continue\r
-        }\r
-        showMessage(result.getText());\r
-      } catch (IOException ioe) {\r
-        showMessage(ioe.getMessage());\r
-      } catch (ReaderException re) {\r
-        showMessage("Sorry, no barcode was found.");\r
-      }\r
-    }\r
-  }\r
-\r
-}\r
diff --git a/rim/src/com/google/zxing/client/rim/ZXingUIApp.java b/rim/src/com/google/zxing/client/rim/ZXingUIApp.java
deleted file mode 100644 (file)
index e635e30..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*\r
- * Copyright 2008 ZXing authors\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.google.zxing.client.rim;\r
-\r
-import net.rim.device.api.ui.UiApplication;\r
-\r
-/**\r
- * @author Sean Owen (srowen@google.com)\r
- */\r
-public final class ZXingUIApp extends UiApplication {\r
-\r
-  public static void main(String[] args) {\r
-    new ZXingUIApp().enterEventDispatcher();\r
-  }\r
-\r
-  ZXingUIApp() {\r
-    pushScreen(new ZXingMainScreen());\r
-  }\r
-\r
-}\r
diff --git a/rim/src/com/google/zxing/client/rim/ZXingUiApplication.java b/rim/src/com/google/zxing/client/rim/ZXingUiApplication.java
new file mode 100644 (file)
index 0000000..27fc542
--- /dev/null
@@ -0,0 +1,44 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim;\r
+\r
+import com.google.zxing.client.rim.persistence.AppSettings;\r
+import com.google.zxing.client.rim.persistence.history.DecodeHistory;\r
+import net.rim.device.api.ui.UiApplication;\r
+\r
+/**\r
+ * Starts the application with the MenuScreen screen on the stack.\r
+ * As well, the required permissions are requested and the history and app settings are initialized.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+public final class ZXingUiApplication extends UiApplication {\r
+\r
+  private ZXingUiApplication() {\r
+    pushScreen(new ZXingLMMainScreen());\r
+  }\r
+\r
+  public static void main(String[] args) {\r
+    AppPermissionsManager.setPermissions();\r
+    DecodeHistory.getInstance();\r
+    AppSettings.getInstance();\r
+    new ZXingUiApplication().enterEventDispatcher();\r
+  }\r
+\r
+} \r
diff --git a/rim/src/com/google/zxing/client/rim/persistence/AppSettings.java b/rim/src/com/google/zxing/client/rim/persistence/AppSettings.java
new file mode 100644 (file)
index 0000000..1148c91
--- /dev/null
@@ -0,0 +1,100 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim.persistence;\r
+\r
+import net.rim.device.api.system.PersistentObject;\r
+import net.rim.device.api.system.PersistentStore;\r
+\r
+import java.util.Hashtable;\r
+\r
+/**\r
+ * Singleton object that represents the persistent application settings data.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ * \r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+public final class AppSettings {\r
+\r
+  public static final String SETTING_CAM_RES_MSG = "setting_cam_res_msg";\r
+  private static final long ID_LONG = 0x92ac4e8ac35b8aa0L;\r
+\r
+  private static AppSettings instance;\r
+\r
+  private final PersistentObject store;\r
+  private final Hashtable settingsItems;\r
+\r
+  private AppSettings() {\r
+    store = PersistentStore.getPersistentObject(ID_LONG);\r
+    Hashtable temp = (Hashtable) store.getContents();\r
+    settingsItems = temp == null ? new Hashtable() : temp;\r
+  }\r
+\r
+  public static AppSettings getInstance() {\r
+    if (instance == null) {\r
+      instance = new AppSettings();\r
+    }\r
+    return instance;\r
+  }\r
+\r
+  /**\r
+   * Adds a setting object.\r
+   */\r
+  public void addItem(String itemName, Object itemValue) {\r
+    settingsItems.put(itemName, itemValue);\r
+  }\r
+\r
+  /**\r
+   * Returns all settings objects.\r
+   */\r
+  public Hashtable getItems() {\r
+    return settingsItems;\r
+  }\r
+\r
+  /**\r
+   * Gets a particular settings object by name.\r
+   */\r
+  Object getItem(String itemName) {\r
+    return settingsItems.get(itemName);\r
+  }\r
+\r
+  /**\r
+   * Gets a particular boolean type settings object by name.\r
+   */\r
+  public Boolean getBooleanItem(String itemName) {\r
+    Object value = getItem(itemName);\r
+    return value instanceof Boolean ? (Boolean) value : Boolean.FALSE;\r
+  }\r
+\r
+  /**\r
+   * Returns the number of settings.\r
+   */\r
+  public int getNumItems() {\r
+    return settingsItems.size();\r
+  }\r
+\r
+  /**\r
+   * Persists the settings to the device.\r
+   */\r
+  public void persist() {\r
+    synchronized (store) {\r
+      store.setContents(settingsItems);\r
+      store.commit();\r
+    }\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/persistence/history/DecodeHistory.java b/rim/src/com/google/zxing/client/rim/persistence/history/DecodeHistory.java
new file mode 100644 (file)
index 0000000..2e769cf
--- /dev/null
@@ -0,0 +1,101 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim.persistence.history;\r
+\r
+import net.rim.device.api.system.PersistentObject;\r
+import net.rim.device.api.system.PersistentStore;\r
+\r
+import java.util.Vector;\r
+\r
+/**\r
+ * Singleton used to persist the history of qrcode URLs decoded by the client.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+public class DecodeHistory {\r
+\r
+  private static final long ID_LONG = 0xb7cc76147b48ad0dL;\r
+\r
+  private static DecodeHistory instance;\r
+\r
+  private final PersistentObject store;\r
+  private final Vector historyItems;\r
+\r
+  private DecodeHistory() {\r
+    store = PersistentStore.getPersistentObject(ID_LONG);\r
+    Vector temp = (Vector) store.getContents();\r
+    historyItems = temp == null ? new Vector() : temp;\r
+  }\r
+\r
+  /**\r
+   * Returns the single instance of this class.\r
+   */\r
+  public static DecodeHistory getInstance() {\r
+    if (instance == null) {\r
+      instance = new DecodeHistory();\r
+    }\r
+    return instance;\r
+  }\r
+\r
+  /**\r
+   * Adds a history object.\r
+   */\r
+  public void addHistoryItem(DecodeHistoryItem item) {\r
+    historyItems.addElement(item);\r
+  }\r
+\r
+  /**\r
+   * Returns all history objects.\r
+   */\r
+  public Vector getItems() {\r
+    return historyItems;\r
+  }\r
+\r
+  /**\r
+   * Gets a particular history object at a given index.\r
+   */\r
+  public DecodeHistoryItem getItemAt(int index) {\r
+    return (DecodeHistoryItem) historyItems.elementAt(index);\r
+  }\r
+\r
+  /**\r
+   * Returns the number of history objects.\r
+   */\r
+  public int getNumItems() {\r
+    return historyItems.size();\r
+  }\r
+\r
+  /**\r
+   * Clears the history.\r
+   */\r
+  public void clear() {\r
+    historyItems.setSize(0);\r
+  }\r
+\r
+  /**\r
+   * Persists the history to the device.\r
+   */\r
+  public void persist() {\r
+    synchronized (store) {\r
+      store.setContents(historyItems);\r
+      store.commit();\r
+    }\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/persistence/history/DecodeHistoryItem.java b/rim/src/com/google/zxing/client/rim/persistence/history/DecodeHistoryItem.java
new file mode 100644 (file)
index 0000000..4c1de65
--- /dev/null
@@ -0,0 +1,60 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim.persistence.history;\r
+\r
+import net.rim.device.api.util.Persistable;\r
+\r
+import java.util.Date;\r
+\r
+/**\r
+ * A single decoded history item that is stored by the decode history.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ * \r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+public final class DecodeHistoryItem implements Persistable {\r
+\r
+  private String date;\r
+  private String uri;\r
+\r
+  private DecodeHistoryItem() {\r
+    date = new Date().toString();\r
+  }\r
+\r
+  public DecodeHistoryItem(String uri) {\r
+    this();\r
+    this.uri = uri;\r
+  }\r
+\r
+  public void setDate(String date) {\r
+    this.date = date;\r
+  }\r
+\r
+  public String getDate() {\r
+    return date;\r
+  }\r
+\r
+  public void setURI(String uri) {\r
+    this.uri = uri;\r
+  }\r
+\r
+  public String getURI() {\r
+    return uri;\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/util/Log.java b/rim/src/com/google/zxing/client/rim/util/Log.java
new file mode 100644 (file)
index 0000000..f561cd8
--- /dev/null
@@ -0,0 +1,83 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim.util;\r
+\r
+import net.rim.device.api.system.EventLogger;\r
+\r
+/**\r
+ * Used to write logging messages. When debugging, System.out is used to write to the simulator.\r
+ * When running on a real device, the EventLogger is used. To access the event log on a real device,\r
+ * go to the home screen, hold down ALT and type lglg.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+public final class Log {\r
+\r
+  private static final String LOG_ID_STRING = "zxing";\r
+  private static final long LOG_ID_LONG = 0x351e9b79fd52317L;\r
+\r
+  /** Used to determine if the log message should be set to System.out */\r
+  private static final boolean logToSystemOut;\r
+\r
+  static {\r
+   // Initializes the logger. Currently set to not log to System.out and log\r
+   // at the INFO level.\r
+    EventLogger.register(LOG_ID_LONG, LOG_ID_STRING, EventLogger.VIEWER_STRING);\r
+    EventLogger.setMinimumLevel(EventLogger.DEBUG_INFO); // set this to change logging level message.\r
+    logToSystemOut = false; // needs to be false for deployment to blackberry device and true for debuging on simulator.\r
+  }\r
+\r
+  private Log() {\r
+  }\r
+\r
+  /**\r
+   * Logs the given message at the debug level.\r
+   */\r
+  public static void debug(String message) {\r
+    EventLogger.logEvent(LOG_ID_LONG, message.getBytes(), EventLogger.DEBUG_INFO);\r
+    logToSystemOut(message);\r
+  }\r
+\r
+  /**\r
+   * Logs the given message at the info level.\r
+   */\r
+  public static void info(String message) {\r
+    EventLogger.logEvent(LOG_ID_LONG, message.getBytes(), EventLogger.INFORMATION);\r
+    logToSystemOut(message);\r
+  }\r
+\r
+  /**\r
+   * Logs the given message at the error level.\r
+   */\r
+  public static void error(String message) {\r
+    EventLogger.logEvent(LOG_ID_LONG, message.getBytes(), EventLogger.ERROR);\r
+    logToSystemOut(message);\r
+  }\r
+\r
+  /**\r
+   * Logs the given message to system.out.\r
+   * This is useful when debugging on the simulator.\r
+   */\r
+  private static void logToSystemOut(String message) {\r
+    if (logToSystemOut) {\r
+      System.out.println(message);\r
+    }\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/util/ReasonableTimer.java b/rim/src/com/google/zxing/client/rim/util/ReasonableTimer.java
new file mode 100644 (file)
index 0000000..3ebce71
--- /dev/null
@@ -0,0 +1,75 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim.util;\r
+\r
+/**\r
+ * Used to determine if something happend within a specified amount of time.\r
+ * For example, if a QR code was decoded in a resonable amount of time.\r
+ * If not, perhaps the user should lower their camera resolution.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+public final class ReasonableTimer {\r
+\r
+  // 2000 too low for qr decoding\r
+  private static final long DEF_RESONABLE_TIME = 2500; // in ms\r
+\r
+  private long reasonableTime;\r
+  private final long startTime;\r
+  private long finishTime;\r
+\r
+  public ReasonableTimer() {\r
+    startTime = System.currentTimeMillis();\r
+    reasonableTime = DEF_RESONABLE_TIME;\r
+  }\r
+\r
+  public ReasonableTimer(long reasonableTime) {\r
+    startTime = System.currentTimeMillis();\r
+    this.reasonableTime = reasonableTime;\r
+  }\r
+\r
+  /**\r
+   * Stops the timing.\r
+   */\r
+  public void finished() {\r
+    finishTime = System.currentTimeMillis();\r
+  }\r
+\r
+  /**\r
+   * Returns true if the timer finished in a reasonable amount of time.\r
+   */\r
+  public boolean wasResonableTime() {\r
+    return finishTime - startTime <= reasonableTime;\r
+  }\r
+\r
+  /**\r
+   * Sets the reasonable time to the given time\r
+   */\r
+  public void setResonableTime(long reasonableTime) {\r
+    this.reasonableTime = reasonableTime;\r
+  }\r
+\r
+  /**\r
+   * Returns the reasonable time.\r
+   */\r
+  public long getResonableTime() {\r
+    return reasonableTime;\r
+  }\r
+\r
+}\r
diff --git a/rim/src/com/google/zxing/client/rim/util/URLDecoder.java b/rim/src/com/google/zxing/client/rim/util/URLDecoder.java
new file mode 100644 (file)
index 0000000..a7cf0d4
--- /dev/null
@@ -0,0 +1,84 @@
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim.util;\r
+\r
+import java.util.Enumeration;\r
+import java.util.Hashtable;\r
+\r
+/**\r
+ * Used to decode URL encoded characters.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+public final class URLDecoder {\r
+\r
+  private URLDecoder() {\r
+  }\r
+\r
+  private static final Hashtable decodingMap;\r
+  static {\r
+    decodingMap = new Hashtable(37);\r
+    decodingMap.put("%21", "!");\r
+    decodingMap.put("%2A", "*");\r
+    decodingMap.put("%2a", "*");\r
+    decodingMap.put("%27", "'");\r
+    decodingMap.put("%28", "(");\r
+    decodingMap.put("%29", ")");\r
+    decodingMap.put("%3B", ";");\r
+    decodingMap.put("%3b", ";");\r
+    decodingMap.put("%3A", ":");\r
+    decodingMap.put("%3a", ":");\r
+    decodingMap.put("%40", "@");\r
+    decodingMap.put("%26", "&");\r
+    decodingMap.put("%3D", "=");\r
+    decodingMap.put("%3d", "=");\r
+    decodingMap.put("%3B", "+");\r
+    decodingMap.put("%3b", "+");\r
+    decodingMap.put("%24", "$");\r
+    decodingMap.put("%2C", "`");\r
+    decodingMap.put("%2c", "`");\r
+    decodingMap.put("%2F", "/");\r
+    decodingMap.put("%2f", "/");\r
+    decodingMap.put("%3F", "?");\r
+    decodingMap.put("%3f", "?");\r
+    decodingMap.put("%25", "%");\r
+    decodingMap.put("%23", "#");\r
+    decodingMap.put("%5B", "[");\r
+    decodingMap.put("%5b", "[");\r
+    decodingMap.put("%5D", "]");\r
+    decodingMap.put("%5d", "]");\r
+  }\r
+\r
+  public static String decode(String uri) {\r
+    Log.info("Original uri: " + uri);\r
+    if (uri.indexOf('%') >= 0) { // skip this if no encoded chars\r
+      Enumeration keys = decodingMap.keys();\r
+      while (keys.hasMoreElements()) {\r
+        String encodedChar = (String) keys.nextElement();\r
+        int encodedCharIndex = uri.indexOf(encodedChar);\r
+        while (encodedCharIndex != -1) {\r
+          uri = uri.substring(0, encodedCharIndex) + decodingMap.get(encodedChar) + uri.substring(encodedCharIndex + encodedChar.length());\r
+          encodedCharIndex = uri.indexOf(encodedChar, encodedCharIndex);\r
+        }\r
+      }\r
+    }\r
+    Log.info("Final URI: " + uri);\r
+    return uri;\r
+  }\r
+}\r