Added some simple sharpening for 1D decoding which allowed a couple more blackbox...
[zxing.git] / android-m3 / src / com / google / zxing / client / android / BarcodeReaderCaptureActivity.java
1 /*
2  * Copyright (C) 2008 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.google.zxing.client.android;
18
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.graphics.PixelFormat;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Message;
26 import android.view.KeyEvent;
27 import android.view.Menu;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.Window;
31 import android.view.WindowManager.LayoutParams;
32 import android.widget.Button;
33 import android.widget.TextView;
34 import com.google.zxing.Result;
35 import com.google.zxing.ResultPoint;
36 import com.google.zxing.client.result.ParsedReaderResult;
37 import com.google.zxing.client.result.ParsedReaderResultType;
38
39 /**
40  * The barcode reader activity itself. This is loosely based on the CameraPreview
41  * example included in the Android SDK.
42  *
43  * @author dswitkin@google.com (Daniel Switkin)
44  * @author Android Team (for CameraPreview example)
45  */
46 public final class BarcodeReaderCaptureActivity extends Activity {
47
48   private CameraManager cameraManager;
49   private CameraSurfaceView surfaceView;
50   private CameraThread cameraThread;
51   private String lastResult;
52
53   private static final int ABOUT_ID = Menu.FIRST;
54   private static final int HELP_ID = Menu.FIRST + 1;
55
56   @Override
57   public void onCreate(Bundle icicle) {
58     super.onCreate(icicle);
59     requestWindowFeature(Window.FEATURE_NO_TITLE);
60
61     // Make sure to create a TRANSLUCENT window. This is required for SurfaceView to work.
62     // Eventually this'll be done by the system automatically.
63     getWindow().setAttributes(new LayoutParams(LayoutParams.APPLICATION_TYPE,
64         LayoutParams.NO_STATUS_BAR_FLAG));
65     getWindow().setFormat(PixelFormat.TRANSLUCENT);
66
67     setContentView(R.layout.main);
68
69     cameraManager = new CameraManager(getApplication());
70     surfaceView = new CameraSurfaceView(getApplication(), cameraManager);
71     surfaceView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
72         LayoutParams.FILL_PARENT));
73
74     ViewGroup previewView = (ViewGroup) findViewById(R.id.preview_view);
75     previewView.addView(surfaceView);
76     cameraThread = null;
77
78     // TODO re-enable this when issues with Matrix.setPolyToPoly() are resolved
79     //GridSampler.setGridSampler(new AndroidGraphicsGridSampler());
80   }
81
82   @Override
83   protected boolean isFullscreenOpaque() {
84     // Our main window is set to translucent, but we know that we will
85     // fill it with opaque data. Tell the system that so it can perform
86     // some important optimizations.
87     return true;
88   }
89
90   @Override
91   protected void onResume() {
92     super.onResume();
93     resetStatusView();
94     cameraManager.openDriver();
95     if (cameraThread == null) {
96       cameraThread = new CameraThread(this, surfaceView, cameraManager, messageHandler);
97       cameraThread.start();
98     }
99   }
100
101   @Override
102   protected void onPause() {
103     super.onPause();
104     if (cameraThread != null) {
105       cameraThread.quitSynchronously();
106       cameraThread = null;
107     }
108     cameraManager.closeDriver();
109   }
110
111   @Override
112   public boolean onKeyDown(int keyCode, KeyEvent event) {
113     if (keyCode == KeyEvent.KEYCODE_A) {
114       cameraThread.setDecodeAllMode();
115     } else if (keyCode == KeyEvent.KEYCODE_C) {
116       Message save = Message.obtain(cameraThread.handler, R.id.save);
117       save.sendToTarget();
118     } else if (keyCode == KeyEvent.KEYCODE_P) {
119       cameraManager.setUsePreviewForDecode(true);
120     } else if (keyCode == KeyEvent.KEYCODE_Q) {
121       cameraThread.setDecodeQRMode();
122     } else if (keyCode == KeyEvent.KEYCODE_S) {
123       cameraManager.setUsePreviewForDecode(false);
124     } else if (keyCode == KeyEvent.KEYCODE_T) {
125       cameraThread.toggleTracing();
126     } else if (keyCode == KeyEvent.KEYCODE_U) {
127       cameraThread.setDecode1DMode();
128     } else {
129       return super.onKeyDown(keyCode, event);
130     }
131     return true;
132   }
133
134   @Override
135   public boolean onCreateOptionsMenu(Menu menu) {
136     super.onCreateOptionsMenu(menu);
137     menu.add(0, ABOUT_ID, R.string.menu_about);
138     menu.add(0, HELP_ID, R.string.menu_help);
139     return true;
140   }
141
142   @Override
143   public boolean onOptionsItemSelected(Menu.Item item) {
144     Context context = getApplication();
145     switch (item.getId()) {
146       case ABOUT_ID:
147         showAlert(context.getString(R.string.title_about),
148             context.getString(R.string.msg_about),
149             context.getString(R.string.button_ok), null, true, null);
150         break;
151       case HELP_ID:
152         showAlert(context.getString(R.string.title_help),
153             context.getString(R.string.msg_help),
154             context.getString(R.string.button_ok), null, true, null);
155         break;
156     }
157     return super.onOptionsItemSelected(item);
158   }
159
160   private final Handler messageHandler = new Handler() {
161     @Override
162     public void handleMessage(Message message) {
163       switch (message.what) {
164         case R.id.decode_succeeded:
165           int duration = message.arg1;
166           handleDecode((Result) message.obj, duration);
167           break;
168         case R.id.restart_preview:
169           restartPreview();
170           break;
171       }
172     }
173   };
174
175   public void restartPreview() {
176     Message restart = Message.obtain(cameraThread.handler, R.id.restart_preview);
177     restart.sendToTarget();
178   }
179
180   private void handleDecode(Result rawResult, int duration) {
181     if (!rawResult.toString().equals(lastResult)) {
182       lastResult = rawResult.toString();
183
184       ResultPoint[] points = rawResult.getResultPoints();
185       if (points != null && points.length > 0) {
186         surfaceView.drawResultPoints(points);
187       }
188
189       TextView textView = (TextView) findViewById(R.id.status_text_view);
190       ParsedReaderResult readerResult = parseReaderResult(rawResult);
191       textView.setText(readerResult.getDisplayResult() + " (" + duration + " ms)");
192
193       Button actionButton = (Button) findViewById(R.id.status_action_button);
194       int buttonText = getActionButtonText(readerResult.getType());
195       if (buttonText != 0) {
196         actionButton.setVisibility(View.VISIBLE);
197         actionButton.setText(buttonText);
198         ResultHandler handler = new ResultHandler(this, readerResult);
199         actionButton.setOnClickListener(handler);
200         actionButton.requestFocus();
201       } else {
202         actionButton.setVisibility(View.GONE);
203       }
204
205       // Show the green finder patterns for one second, then restart the preview
206       Message message = Message.obtain(messageHandler, R.id.restart_preview);
207       messageHandler.sendMessageDelayed(message, 1000);
208     } else {
209       restartPreview();
210     }
211   }
212
213   private void resetStatusView() {
214     TextView textView = (TextView) findViewById(R.id.status_text_view);
215     textView.setText(R.string.msg_default_status);
216     Button actionButton = (Button) findViewById(R.id.status_action_button);
217     actionButton.setVisibility(View.GONE);
218     lastResult = "";
219   }
220
221   private static ParsedReaderResult parseReaderResult(Result rawResult) {
222     ParsedReaderResult readerResult = ParsedReaderResult.parseReaderResult(rawResult);
223     if (readerResult.getType().equals(ParsedReaderResultType.TEXT)) {
224       String rawText = rawResult.getText();
225       AndroidIntentParsedResult androidResult = AndroidIntentParsedResult.parse(rawText);
226       if (androidResult != null) {
227         Intent intent = androidResult.getIntent();
228         if (!Intent.VIEW_ACTION.equals(intent.getAction())) {
229           // For now, don't take anything that just parses as a View action. A lot
230           // of things are accepted as a View action by default.
231           readerResult = androidResult;          
232         }
233       }
234     }
235     return readerResult;
236   }
237
238   private static int getActionButtonText(ParsedReaderResultType type) {
239     int buttonText;
240     if (type.equals(ParsedReaderResultType.ADDRESSBOOK)) {
241       buttonText = R.string.button_add_contact;
242     } else if (type.equals(ParsedReaderResultType.URI) ||
243                type.equals(ParsedReaderResultType.BOOKMARK) ||
244                type.equals(ParsedReaderResultType.URLTO)) {
245       buttonText = R.string.button_open_browser;
246     } else if (type.equals(ParsedReaderResultType.EMAIL) ||
247                type.equals(ParsedReaderResultType.EMAIL_ADDRESS)) {
248       buttonText = R.string.button_email;
249     } else if (type.equals(ParsedReaderResultType.UPC)) {
250       buttonText = R.string.button_lookup_product;
251     } else if (type.equals(ParsedReaderResultType.TEL)) {
252       buttonText = R.string.button_dial;
253     } else if (type.equals(ParsedReaderResultType.GEO)) {
254       buttonText = R.string.button_show_map;
255     } else {
256       buttonText = 0;
257     }
258     return buttonText;
259   }
260
261 }