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