2 * Copyright 2008 ZXing authors
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
17 package com.google.zxing.client.rim;
\r
19 import com.google.zxing.BinaryBitmap;
\r
20 import com.google.zxing.DecodeHintType;
\r
21 import com.google.zxing.LuminanceSource;
\r
22 import com.google.zxing.MultiFormatReader;
\r
23 import com.google.zxing.Reader;
\r
24 import com.google.zxing.ReaderException;
\r
25 import com.google.zxing.Result;
\r
26 import com.google.zxing.common.GlobalHistogramBinarizer;
\r
27 import com.google.zxing.client.j2me.LCDUIImageLuminanceSource;
\r
28 import com.google.zxing.client.rim.persistence.AppSettings;
\r
29 import com.google.zxing.client.rim.persistence.history.DecodeHistory;
\r
30 import com.google.zxing.client.rim.persistence.history.DecodeHistoryItem;
\r
31 import com.google.zxing.client.rim.util.Log;
\r
32 import com.google.zxing.client.rim.util.ReasonableTimer;
\r
33 import com.google.zxing.client.rim.util.URLDecoder;
\r
34 import net.rim.blackberry.api.browser.Browser;
\r
35 import net.rim.blackberry.api.browser.BrowserSession;
\r
36 import net.rim.device.api.ui.DrawStyle;
\r
37 import net.rim.device.api.ui.Field;
\r
38 import net.rim.device.api.ui.FieldChangeListener;
\r
39 import net.rim.device.api.ui.Manager;
\r
40 import net.rim.device.api.ui.UiApplication;
\r
41 import net.rim.device.api.ui.component.ButtonField;
\r
42 import net.rim.device.api.ui.component.Dialog;
\r
43 import net.rim.device.api.ui.component.LabelField;
\r
44 import net.rim.device.api.ui.container.DialogFieldManager;
\r
45 import net.rim.device.api.ui.container.MainScreen;
\r
46 import net.rim.device.api.ui.container.PopupScreen;
\r
47 import net.rim.device.api.ui.container.VerticalFieldManager;
\r
49 import javax.microedition.io.Connector;
\r
50 import javax.microedition.io.file.FileConnection;
\r
51 import javax.microedition.lcdui.Image;
\r
52 import java.io.IOException;
\r
53 import java.io.InputStream;
\r
54 import java.util.Hashtable;
\r
57 * The main appication menu screen.
\r
59 * This code was contributed by LifeMarks.
\r
61 * @author Matt York (matt@lifemarks.mobi)
\r
63 final class ZXingLMMainScreen extends MainScreen {
\r
65 private final ZXingUiApplication app;
\r
66 private final QRCapturedJournalListener imageListener;
\r
67 private PopupScreen popup;
\r
68 private final Reader reader;
\r
69 private final Hashtable readerHints;
\r
71 ZXingLMMainScreen() {
\r
72 super(DEFAULT_MENU | DEFAULT_CLOSE);
\r
73 setTitle(new LabelField("ZXing", DrawStyle.ELLIPSIS | USE_ALL_WIDTH));
\r
74 setChangeListener(null);
\r
76 Manager vfm = new VerticalFieldManager(USE_ALL_WIDTH);
\r
77 FieldChangeListener buttonListener = new ButtonListener();
\r
80 Field snapButton = new ButtonField("Snap", FIELD_HCENTER | ButtonField.CONSUME_CLICK | USE_ALL_WIDTH);
\r
81 snapButton.setChangeListener(buttonListener);
\r
82 vfm.add(snapButton);
\r
85 Field historyButton = new ButtonField("History", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
\r
86 historyButton.setChangeListener(buttonListener);
\r
87 vfm.add(historyButton);
\r
90 Field settingsButton = new ButtonField("Settings", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
\r
91 settingsButton.setChangeListener(buttonListener);
\r
92 vfm.add(settingsButton);
\r
95 Field aboutButton = new ButtonField("About", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
\r
96 aboutButton.setChangeListener(buttonListener);
\r
97 vfm.add(aboutButton);
\r
100 Field helpButton = new ButtonField("Help", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
\r
101 helpButton.setChangeListener(buttonListener);
\r
102 vfm.add(helpButton);
\r
104 vfm.setChangeListener(null);
\r
108 app = (ZXingUiApplication) UiApplication.getUiApplication();
\r
109 imageListener = new QRCapturedJournalListener(this);
\r
111 reader = new MultiFormatReader();
\r
112 readerHints = new Hashtable(1);
\r
113 readerHints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
\r
118 * Handles the newly created file. If the file is a jpg image, from the camera, the images is assumed to be
\r
119 * a qrcode and decoding is attempted.
\r
121 void imageSaved(String imagePath) {
\r
122 Log.info("Image saved: " + imagePath);
\r
123 app.removeFileSystemJournalListener(imageListener);
\r
124 if (imagePath.endsWith(".jpg") && imagePath.indexOf("IMG") >= 0) // a blackberry camera image file
\r
126 Log.info("imageSaved - Got file: " + imagePath);
\r
127 Camera.getInstance().exit();
\r
128 Log.info("camera exit finished");
\r
129 app.requestForeground();
\r
131 DialogFieldManager manager = new DialogFieldManager();
\r
132 popup = new PopupScreen(manager);
\r
133 manager.addCustomField(new LabelField("Decoding image..."));
\r
135 app.pushScreen(popup); // original
\r
136 Log.info("started progress screen.");
\r
138 Runnable fct = new FileConnectionThread(imagePath);
\r
139 Log.info("Starting file connection thread.");
\r
140 app.invokeLater(fct);
\r
141 Log.info("Finished file connection thread.");
\r
143 Log.error("Failed to locate camera image.");
\r
148 * Closes the application and persists all required data.
\r
150 public void close() {
\r
151 app.removeFileSystemJournalListener(imageListener);
\r
152 DecodeHistory.getInstance().persist();
\r
157 * This method is overriden to remove the 'save changes' dialog when exiting.
\r
159 public boolean onSavePrompt() {
\r
165 * Listens for selected buttons and starts the required screen.
\r
167 private final class ButtonListener implements FieldChangeListener {
\r
168 public void fieldChanged(Field field, int context) {
\r
169 Log.debug("*** fieldChanged: " + field.getIndex());
\r
170 switch (field.getIndex()) {
\r
173 app.addFileSystemJournalListener(imageListener);
\r
174 Camera.getInstance().invoke(); // start camera
\r
177 catch (Exception e) {
\r
178 Log.error("!!! Problem invoking camera.!!!: " + e);
\r
182 app.pushScreen(new HistoryScreen());
\r
184 case 2: // settings
\r
185 app.pushScreen(new SettingsScreen());
\r
188 app.pushScreen(new AboutScreen());
\r
191 app.pushScreen(new HelpScreen());
\r
199 * Thread that decodes the newly created image. If the image is successfully decoded and the data is a URL,
\r
200 * the browser is invoked and pointed to the given URL.
\r
202 private final class FileConnectionThread implements Runnable {
\r
204 private final String imagePath;
\r
206 private FileConnectionThread(String imagePath) {
\r
207 this.imagePath = imagePath;
\r
210 public void run() {
\r
211 FileConnection file = null;
\r
212 InputStream is = null;
\r
213 Image capturedImage = null;
\r
215 file = (FileConnection) Connector.open("file://" + imagePath, Connector.READ_WRITE);
\r
216 is = file.openInputStream();
\r
217 capturedImage = Image.createImage(is);
\r
218 } catch (IOException e) {
\r
219 Log.error("Problem creating image: " + e);
\r
220 removeProgressBar();
\r
222 showMessage("An error occured processing the image.");
\r
229 if (file != null && file.exists()) {
\r
230 if (file.isOpen()) {
\r
234 Log.info("Deleted image file.");
\r
236 } catch (IOException ioe) {
\r
237 Log.error("Error while closing file: " + ioe);
\r
241 if (capturedImage != null) {
\r
242 Log.info("Got image...");
\r
243 LuminanceSource source = new LCDUIImageLuminanceSource(capturedImage);
\r
244 BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
\r
246 ReasonableTimer decodingTimer = null;
\r
248 decodingTimer = new ReasonableTimer();
\r
249 Log.info("Attempting to decode image...");
\r
250 result = reader.decode(bitmap, readerHints);
\r
251 decodingTimer.finished();
\r
252 } catch (ReaderException e) {
\r
253 Log.error("Could not decode image: " + e);
\r
254 decodingTimer.finished();
\r
255 removeProgressBar();
\r
257 boolean showResolutionMsg =
\r
258 !AppSettings.getInstance().getBooleanItem(AppSettings.SETTING_CAM_RES_MSG).booleanValue();
\r
259 if (showResolutionMsg) {
\r
260 showMessage("A QR Code was not found in the image. " +
\r
261 "We detected that the decoding process took quite a while. " +
\r
262 "It will be much faster if you decrease your camera's resolution (640x480).");
\r
264 showMessage("A QR Code was not found in the image.");
\r
268 if (result != null) {
\r
269 String resultText = result.getText();
\r
270 Log.info("result: " + resultText);
\r
271 if (isURI(resultText)) {
\r
272 resultText = URLDecoder.decode(resultText);
\r
273 removeProgressBar();
\r
275 if (!decodingTimer.wasResonableTime() &&
\r
276 !AppSettings.getInstance().getBooleanItem(AppSettings.SETTING_CAM_RES_MSG).booleanValue()) {
\r
277 showMessage("We detected that the decoding process took quite a while. " +
\r
278 "It will be much faster if you decrease your camera's resolution (640x480).");
\r
280 DecodeHistory.getInstance().addHistoryItem(new DecodeHistoryItem(resultText));
\r
281 invokeBrowser(resultText);
\r
285 removeProgressBar();
\r
287 showMessage("A QR Code was not found in the image.");
\r
293 removeProgressBar();
\r
298 * Quick check to see if the result of decoding the qr code was a valid uri.
\r
300 private boolean isURI(String uri) {
\r
301 return uri.startsWith("http://");
\r
305 * Invokes the web browser and browses to the given uri.
\r
307 private void invokeBrowser(String uri) {
\r
308 BrowserSession browserSession = Browser.getDefaultSession();
\r
309 browserSession.displayPage(uri);
\r
313 * Syncronized version of removing progress dialog.
\r
314 * NOTE: All methods accessing the gui that are in seperate threads should syncronize on app.getEventLock()
\r
316 private void removeProgressBar() {
\r
317 synchronized (app.getAppEventLock()) {
\r
318 if (popup != null) {
\r
319 app.popScreen(popup);
\r
325 * Syncronized version of showing a message dialog.
\r
326 * NOTE: All methods accessing the gui that are in seperate threads should syncronize on app.getEventLock()
\r
328 private void showMessage(String message) {
\r
329 synchronized (app.getAppEventLock()) {
\r
330 Dialog.alert(message);
\r