diff --git a/app/build.gradle b/app/build.gradle
index 94c7b8a..c258254 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -33,8 +33,4 @@ dependencies {
compile 'com.android.support:support-v4:23.3.0'
compile 'com.android.support:recyclerview-v7:23.3.0'
compile 'com.android.support:design:23.3.0'
- compile 'com.embarkmobile:zxing-android-minimal:2.0@aar'
- compile 'com.embarkmobile:zxing-android-legacy:2.0.0@aar'
- compile 'com.embarkmobile:zxing-android-integration:2.0.0@aar'
- compile 'com.google.zxing:core:3.0.1'
}
diff --git a/app/libs/android-core-3.2.2-SNAPSHOT.jar b/app/libs/android-core-3.2.2-SNAPSHOT.jar
new file mode 100644
index 0000000..503f817
Binary files /dev/null and b/app/libs/android-core-3.2.2-SNAPSHOT.jar differ
diff --git a/app/libs/core-3.2.2-SNAPSHOT.jar b/app/libs/core-3.2.2-SNAPSHOT.jar
new file mode 100644
index 0000000..4ed7e27
Binary files /dev/null and b/app/libs/core-3.2.2-SNAPSHOT.jar differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5113f17..2580491 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,8 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/google/zxing/client/android/AmbientLightManager.java b/app/src/main/java/com/google/zxing/client/android/AmbientLightManager.java
new file mode 100644
index 0000000..2074265
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/AmbientLightManager.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 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.android;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.preference.PreferenceManager;
+import com.google.zxing.client.android.camera.CameraManager;
+import com.google.zxing.client.android.camera.FrontLightMode;
+
+/**
+ * Detects ambient light and switches on the front light when very dark, and off again when sufficiently light.
+ *
+ * @author Sean Owen
+ * @author Nikolaus Huber
+ */
+final class AmbientLightManager implements SensorEventListener {
+
+ private static final float TOO_DARK_LUX = 45.0f;
+ private static final float BRIGHT_ENOUGH_LUX = 450.0f;
+
+ private final Context context;
+ private CameraManager cameraManager;
+ private Sensor lightSensor;
+
+ AmbientLightManager(Context context) {
+ this.context = context;
+ }
+
+ void start(CameraManager cameraManager) {
+ this.cameraManager = cameraManager;
+ SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+ if (FrontLightMode.readPref(sharedPrefs) == FrontLightMode.AUTO) {
+ SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
+ if (lightSensor != null) {
+ sensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+ }
+ }
+
+ void stop() {
+ if (lightSensor != null) {
+ SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ sensorManager.unregisterListener(this);
+ cameraManager = null;
+ lightSensor = null;
+ }
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent sensorEvent) {
+ float ambientLightLux = sensorEvent.values[0];
+ if (cameraManager != null) {
+ if (ambientLightLux <= TOO_DARK_LUX) {
+ cameraManager.setTorch(true);
+ } else if (ambientLightLux >= BRIGHT_ENOUGH_LUX) {
+ cameraManager.setTorch(false);
+ }
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // do nothing
+ }
+
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/BeepManager.java b/app/src/main/java/com/google/zxing/client/android/BeepManager.java
new file mode 100644
index 0000000..f4f2e58
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/BeepManager.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2010 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.android;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.os.Vibrator;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import net.foucry.pilldroid.R;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Manages beeps and vibrations for {@link CaptureActivity}.
+ */
+final class BeepManager implements MediaPlayer.OnErrorListener, Closeable {
+
+ private static final String TAG = BeepManager.class.getSimpleName();
+
+ private static final float BEEP_VOLUME = 0.10f;
+ private static final long VIBRATE_DURATION = 200L;
+
+ private final Activity activity;
+ private MediaPlayer mediaPlayer;
+ private boolean playBeep;
+ private boolean vibrate;
+
+ BeepManager(Activity activity) {
+ this.activity = activity;
+ this.mediaPlayer = null;
+ updatePrefs();
+ }
+
+ synchronized void updatePrefs() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ playBeep = shouldBeep(prefs, activity);
+ vibrate = prefs.getBoolean(PreferencesActivity.KEY_VIBRATE, false);
+ if (playBeep && mediaPlayer == null) {
+ // The volume on STREAM_SYSTEM is not adjustable, and users found it too loud,
+ // so we now play on the music stream.
+ activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ mediaPlayer = buildMediaPlayer(activity);
+ }
+ }
+
+ synchronized void playBeepSoundAndVibrate() {
+ if (playBeep && mediaPlayer != null) {
+ mediaPlayer.start();
+ }
+ if (vibrate) {
+ Vibrator vibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
+ vibrator.vibrate(VIBRATE_DURATION);
+ }
+ }
+
+ private static boolean shouldBeep(SharedPreferences prefs, Context activity) {
+ boolean shouldPlayBeep = prefs.getBoolean(PreferencesActivity.KEY_PLAY_BEEP, true);
+ if (shouldPlayBeep) {
+ // See if sound settings overrides this
+ AudioManager audioService = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
+ if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
+ shouldPlayBeep = false;
+ }
+ }
+ return shouldPlayBeep;
+ }
+
+ private MediaPlayer buildMediaPlayer(Context activity) {
+ MediaPlayer mediaPlayer = new MediaPlayer();
+ try {
+ AssetFileDescriptor file = activity.getResources().openRawResourceFd(R.raw.beep);
+ try {
+ mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength());
+ } finally {
+ file.close();
+ }
+ mediaPlayer.setOnErrorListener(this);
+ mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mediaPlayer.setLooping(false);
+ mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
+ mediaPlayer.prepare();
+ return mediaPlayer;
+ } catch (IOException ioe) {
+ Log.w(TAG, ioe);
+ mediaPlayer.release();
+ return null;
+ }
+ }
+
+ @Override
+ public synchronized boolean onError(MediaPlayer mp, int what, int extra) {
+ if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
+ // we are finished, so put up an appropriate error toast if required and finish
+ activity.finish();
+ } else {
+ // possibly media player error, so release and recreate
+ close();
+ updatePrefs();
+ }
+ return true;
+ }
+
+ @Override
+ public synchronized void close() {
+ if (mediaPlayer != null) {
+ mediaPlayer.release();
+ mediaPlayer = null;
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/CaptureActivity.java b/app/src/main/java/com/google/zxing/client/android/CaptureActivity.java
new file mode 100644
index 0000000..2263eb2
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/CaptureActivity.java
@@ -0,0 +1,769 @@
+/*
+ * Copyright (C) 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.android;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.Result;
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.client.android.camera.CameraManager;
+import com.google.zxing.client.android.clipboard.ClipboardInterface;
+import com.google.zxing.client.android.history.HistoryActivity;
+import com.google.zxing.client.android.history.HistoryItem;
+import com.google.zxing.client.android.history.HistoryManager;
+import com.google.zxing.client.android.result.ResultButtonListener;
+import com.google.zxing.client.android.result.ResultHandler;
+import com.google.zxing.client.android.result.ResultHandlerFactory;
+import com.google.zxing.client.android.result.supplement.SupplementalInfoRetriever;
+import com.google.zxing.client.android.share.ShareActivity;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import net.foucry.pilldroid.R;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.Map;
+
+/**
+ * This activity opens the camera and does the actual scanning on a background thread. It draws a
+ * viewfinder to help the user place the barcode correctly, shows feedback as the image processing
+ * is happening, and then overlays the results when a scan is successful.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ */
+public final class CaptureActivity extends Activity implements SurfaceHolder.Callback {
+
+ private static final String TAG = CaptureActivity.class.getSimpleName();
+
+ private static final long DEFAULT_INTENT_RESULT_DURATION_MS = 1500L;
+ private static final long BULK_MODE_SCAN_DELAY_MS = 1000L;
+
+ private static final String[] ZXING_URLS = { "http://zxing.appspot.com/scan", "zxing://scan/" };
+
+ public static final int HISTORY_REQUEST_CODE = 0x0000bacc;
+
+ private static final Collection DISPLAYABLE_METADATA_TYPES =
+ EnumSet.of(ResultMetadataType.ISSUE_NUMBER,
+ ResultMetadataType.SUGGESTED_PRICE,
+ ResultMetadataType.ERROR_CORRECTION_LEVEL,
+ ResultMetadataType.POSSIBLE_COUNTRY);
+
+ private CameraManager cameraManager;
+ private CaptureActivityHandler handler;
+ private Result savedResultToShow;
+ private ViewfinderView viewfinderView;
+ private TextView statusView;
+ private View resultView;
+ private Result lastResult;
+ private boolean hasSurface;
+ private boolean copyToClipboard;
+ private IntentSource source;
+ private String sourceUrl;
+ private ScanFromWebPageManager scanFromWebPageManager;
+ private Collection decodeFormats;
+ private Map decodeHints;
+ private String characterSet;
+ private HistoryManager historyManager;
+ private InactivityTimer inactivityTimer;
+ private BeepManager beepManager;
+ private AmbientLightManager ambientLightManager;
+
+ ViewfinderView getViewfinderView() {
+ return viewfinderView;
+ }
+
+ public Handler getHandler() {
+ return handler;
+ }
+
+ CameraManager getCameraManager() {
+ return cameraManager;
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ Window window = getWindow();
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ setContentView(R.layout.capture);
+
+ hasSurface = false;
+ inactivityTimer = new InactivityTimer(this);
+ beepManager = new BeepManager(this);
+ ambientLightManager = new AmbientLightManager(this);
+
+ PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // historyManager must be initialized here to update the history preference
+ historyManager = new HistoryManager(this);
+ historyManager.trimHistory();
+
+ // CameraManager must be initialized here, not in onCreate(). This is necessary because we don't
+ // want to open the camera driver and measure the screen size if we're going to show the help on
+ // first launch. That led to bugs where the scanning rectangle was the wrong size and partially
+ // off screen.
+ cameraManager = new CameraManager(getApplication());
+
+ viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
+ viewfinderView.setCameraManager(cameraManager);
+
+ resultView = findViewById(R.id.result_view);
+ statusView = (TextView) findViewById(R.id.status_view);
+
+ handler = null;
+ lastResult = null;
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+ if (prefs.getBoolean(PreferencesActivity.KEY_DISABLE_AUTO_ORIENTATION, true)) {
+ setRequestedOrientation(getCurrentOrientation());
+ } else {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+ }
+
+ resetStatusView();
+
+
+ beepManager.updatePrefs();
+ ambientLightManager.start(cameraManager);
+
+ inactivityTimer.onResume();
+
+ Intent intent = getIntent();
+
+ copyToClipboard = prefs.getBoolean(PreferencesActivity.KEY_COPY_TO_CLIPBOARD, true)
+ && (intent == null || intent.getBooleanExtra(Intents.Scan.SAVE_HISTORY, true));
+
+ source = IntentSource.NONE;
+ sourceUrl = null;
+ scanFromWebPageManager = null;
+ decodeFormats = null;
+ characterSet = null;
+
+ if (intent != null) {
+
+ String action = intent.getAction();
+ String dataString = intent.getDataString();
+
+ if (Intents.Scan.ACTION.equals(action)) {
+
+ // Scan the formats the intent requested, and return the result to the calling activity.
+ source = IntentSource.NATIVE_APP_INTENT;
+ decodeFormats = DecodeFormatManager.parseDecodeFormats(intent);
+ decodeHints = DecodeHintManager.parseDecodeHints(intent);
+
+ if (intent.hasExtra(Intents.Scan.WIDTH) && intent.hasExtra(Intents.Scan.HEIGHT)) {
+ int width = intent.getIntExtra(Intents.Scan.WIDTH, 0);
+ int height = intent.getIntExtra(Intents.Scan.HEIGHT, 0);
+ if (width > 0 && height > 0) {
+ cameraManager.setManualFramingRect(width, height);
+ }
+ }
+
+ if (intent.hasExtra(Intents.Scan.CAMERA_ID)) {
+ int cameraId = intent.getIntExtra(Intents.Scan.CAMERA_ID, -1);
+ if (cameraId >= 0) {
+ cameraManager.setManualCameraId(cameraId);
+ }
+ }
+
+ String customPromptMessage = intent.getStringExtra(Intents.Scan.PROMPT_MESSAGE);
+ if (customPromptMessage != null) {
+ statusView.setText(customPromptMessage);
+ }
+
+ } else if (dataString != null &&
+ dataString.contains("http://www.google") &&
+ dataString.contains("/m/products/scan")) {
+
+ // Scan only products and send the result to mobile Product Search.
+ source = IntentSource.PRODUCT_SEARCH_LINK;
+ sourceUrl = dataString;
+ decodeFormats = DecodeFormatManager.PRODUCT_FORMATS;
+
+ } else if (isZXingURL(dataString)) {
+
+ // Scan formats requested in query string (all formats if none specified).
+ // If a return URL is specified, send the results there. Otherwise, handle it ourselves.
+ source = IntentSource.ZXING_LINK;
+ sourceUrl = dataString;
+ Uri inputUri = Uri.parse(dataString);
+ scanFromWebPageManager = new ScanFromWebPageManager(inputUri);
+ decodeFormats = DecodeFormatManager.parseDecodeFormats(inputUri);
+ // Allow a sub-set of the hints to be specified by the caller.
+ decodeHints = DecodeHintManager.parseDecodeHints(inputUri);
+
+ }
+
+ characterSet = intent.getStringExtra(Intents.Scan.CHARACTER_SET);
+
+ }
+
+ SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
+ SurfaceHolder surfaceHolder = surfaceView.getHolder();
+ if (hasSurface) {
+ // The activity was paused but not stopped, so the surface still exists. Therefore
+ // surfaceCreated() won't be called, so init the camera here.
+ initCamera(surfaceHolder);
+ } else {
+ // Install the callback and wait for surfaceCreated() to init the camera.
+ surfaceHolder.addCallback(this);
+ }
+ }
+
+ private int getCurrentOrientation() {
+ int rotation = getWindowManager().getDefaultDisplay().getRotation();
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_90:
+ return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ default:
+ return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ }
+ } else {
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_270:
+ return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ default:
+ return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ }
+ }
+ }
+
+ private static boolean isZXingURL(String dataString) {
+ if (dataString == null) {
+ return false;
+ }
+ for (String url : ZXING_URLS) {
+ if (dataString.startsWith(url)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void onPause() {
+ if (handler != null) {
+ handler.quitSynchronously();
+ handler = null;
+ }
+ inactivityTimer.onPause();
+ ambientLightManager.stop();
+ beepManager.close();
+ cameraManager.closeDriver();
+ //historyManager = null; // Keep for onActivityResult
+ if (!hasSurface) {
+ SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
+ SurfaceHolder surfaceHolder = surfaceView.getHolder();
+ surfaceHolder.removeCallback(this);
+ }
+ super.onPause();
+ }
+
+ @Override
+ protected void onDestroy() {
+ inactivityTimer.shutdown();
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ if (source == IntentSource.NATIVE_APP_INTENT) {
+ setResult(RESULT_CANCELED);
+ finish();
+ return true;
+ }
+ if ((source == IntentSource.NONE || source == IntentSource.ZXING_LINK) && lastResult != null) {
+ restartPreviewAfterDelay(0L);
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_FOCUS:
+ case KeyEvent.KEYCODE_CAMERA:
+ // Handle these events so they don't launch the Camera app
+ return true;
+ // Use volume up/down to turn on light
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ cameraManager.setTorch(false);
+ return true;
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ cameraManager.setTorch(true);
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater menuInflater = getMenuInflater();
+ menuInflater.inflate(R.menu.capture, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ switch (item.getItemId()) {
+ case R.id.menu_share:
+ intent.setClassName(this, ShareActivity.class.getName());
+ startActivity(intent);
+ break;
+ case R.id.menu_history:
+ intent.setClassName(this, HistoryActivity.class.getName());
+ startActivityForResult(intent, HISTORY_REQUEST_CODE);
+ break;
+ case R.id.menu_settings:
+ intent.setClassName(this, PreferencesActivity.class.getName());
+ startActivity(intent);
+ break;
+ case R.id.menu_help:
+ intent.setClassName(this, HelpActivity.class.getName());
+ startActivity(intent);
+ break;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ return true;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ if (resultCode == RESULT_OK && requestCode == HISTORY_REQUEST_CODE && historyManager != null) {
+ int itemNumber = intent.getIntExtra(Intents.History.ITEM_NUMBER, -1);
+ if (itemNumber >= 0) {
+ HistoryItem historyItem = historyManager.buildHistoryItem(itemNumber);
+ decodeOrStoreSavedBitmap(null, historyItem.getResult());
+ }
+ }
+ }
+
+ private void decodeOrStoreSavedBitmap(Bitmap bitmap, Result result) {
+ // Bitmap isn't used yet -- will be used soon
+ if (handler == null) {
+ savedResultToShow = result;
+ } else {
+ if (result != null) {
+ savedResultToShow = result;
+ }
+ if (savedResultToShow != null) {
+ Message message = Message.obtain(handler, R.id.decode_succeeded, savedResultToShow);
+ handler.sendMessage(message);
+ }
+ savedResultToShow = null;
+ }
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ if (holder == null) {
+ Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");
+ }
+ if (!hasSurface) {
+ hasSurface = true;
+ initCamera(holder);
+ }
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ hasSurface = false;
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+ }
+
+ /**
+ * A valid barcode has been found, so give an indication of success and show the results.
+ *
+ * @param rawResult The contents of the barcode.
+ * @param scaleFactor amount by which thumbnail was scaled
+ * @param barcode A greyscale bitmap of the camera data which was decoded.
+ */
+ public void handleDecode(Result rawResult, Bitmap barcode, float scaleFactor) {
+ inactivityTimer.onActivity();
+ lastResult = rawResult;
+ ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(this, rawResult);
+
+ boolean fromLiveScan = barcode != null;
+ if (fromLiveScan) {
+ historyManager.addHistoryItem(rawResult, resultHandler);
+ // Then not from history, so beep/vibrate and we have an image to draw on
+ beepManager.playBeepSoundAndVibrate();
+ drawResultPoints(barcode, scaleFactor, rawResult);
+ }
+
+ switch (source) {
+ case NATIVE_APP_INTENT:
+ case PRODUCT_SEARCH_LINK:
+ handleDecodeExternally(rawResult, resultHandler, barcode);
+ break;
+ case ZXING_LINK:
+ if (scanFromWebPageManager == null || !scanFromWebPageManager.isScanFromWebPage()) {
+ handleDecodeInternally(rawResult, resultHandler, barcode);
+ } else {
+ handleDecodeExternally(rawResult, resultHandler, barcode);
+ }
+ break;
+ case NONE:
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ if (fromLiveScan && prefs.getBoolean(PreferencesActivity.KEY_BULK_MODE, false)) {
+ Toast.makeText(getApplicationContext(),
+ getResources().getString(R.string.msg_bulk_mode_scanned) + " (" + rawResult.getText() + ')',
+ Toast.LENGTH_SHORT).show();
+ // Wait a moment or else it will scan the same barcode continuously about 3 times
+ restartPreviewAfterDelay(BULK_MODE_SCAN_DELAY_MS);
+ } else {
+ handleDecodeInternally(rawResult, resultHandler, barcode);
+ }
+ break;
+ }
+ }
+
+ /**
+ * Superimpose a line for 1D or dots for 2D to highlight the key features of the barcode.
+ *
+ * @param barcode A bitmap of the captured image.
+ * @param scaleFactor amount by which thumbnail was scaled
+ * @param rawResult The decoded results which contains the points to draw.
+ */
+ private void drawResultPoints(Bitmap barcode, float scaleFactor, Result rawResult) {
+ ResultPoint[] points = rawResult.getResultPoints();
+ if (points != null && points.length > 0) {
+ Canvas canvas = new Canvas(barcode);
+ Paint paint = new Paint();
+ paint.setColor(getResources().getColor(R.color.result_points));
+ if (points.length == 2) {
+ paint.setStrokeWidth(4.0f);
+ drawLine(canvas, paint, points[0], points[1], scaleFactor);
+ } else if (points.length == 4 &&
+ (rawResult.getBarcodeFormat() == BarcodeFormat.UPC_A ||
+ rawResult.getBarcodeFormat() == BarcodeFormat.EAN_13)) {
+ // Hacky special case -- draw two lines, for the barcode and metadata
+ drawLine(canvas, paint, points[0], points[1], scaleFactor);
+ drawLine(canvas, paint, points[2], points[3], scaleFactor);
+ } else {
+ paint.setStrokeWidth(10.0f);
+ for (ResultPoint point : points) {
+ if (point != null) {
+ canvas.drawPoint(scaleFactor * point.getX(), scaleFactor * point.getY(), paint);
+ }
+ }
+ }
+ }
+ }
+
+ private static void drawLine(Canvas canvas, Paint paint, ResultPoint a, ResultPoint b, float scaleFactor) {
+ if (a != null && b != null) {
+ canvas.drawLine(scaleFactor * a.getX(),
+ scaleFactor * a.getY(),
+ scaleFactor * b.getX(),
+ scaleFactor * b.getY(),
+ paint);
+ }
+ }
+
+ // Put up our own UI for how to handle the decoded contents.
+ private void handleDecodeInternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) {
+
+ CharSequence displayContents = resultHandler.getDisplayContents();
+
+ if (copyToClipboard && !resultHandler.areContentsSecure()) {
+ ClipboardInterface.setText(displayContents, this);
+ }
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+ if (resultHandler.getDefaultButtonID() != null && prefs.getBoolean(PreferencesActivity.KEY_AUTO_OPEN_WEB, false)) {
+ resultHandler.handleButtonPress(resultHandler.getDefaultButtonID());
+ return;
+ }
+
+ statusView.setVisibility(View.GONE);
+ viewfinderView.setVisibility(View.GONE);
+ resultView.setVisibility(View.VISIBLE);
+
+ ImageView barcodeImageView = (ImageView) findViewById(R.id.barcode_image_view);
+ if (barcode == null) {
+ barcodeImageView.setImageBitmap(BitmapFactory.decodeResource(getResources(),
+ R.mipmap.ic_launcher));
+ } else {
+ barcodeImageView.setImageBitmap(barcode);
+ }
+
+ TextView formatTextView = (TextView) findViewById(R.id.format_text_view);
+ formatTextView.setText(rawResult.getBarcodeFormat().toString());
+
+ TextView typeTextView = (TextView) findViewById(R.id.type_text_view);
+ typeTextView.setText(resultHandler.getType().toString());
+
+ DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+ TextView timeTextView = (TextView) findViewById(R.id.time_text_view);
+ timeTextView.setText(formatter.format(new Date(rawResult.getTimestamp())));
+
+
+ TextView metaTextView = (TextView) findViewById(R.id.meta_text_view);
+ View metaTextViewLabel = findViewById(R.id.meta_text_view_label);
+ metaTextView.setVisibility(View.GONE);
+ metaTextViewLabel.setVisibility(View.GONE);
+ Map metadata = rawResult.getResultMetadata();
+ if (metadata != null) {
+ StringBuilder metadataText = new StringBuilder(20);
+ for (Map.Entry entry : metadata.entrySet()) {
+ if (DISPLAYABLE_METADATA_TYPES.contains(entry.getKey())) {
+ metadataText.append(entry.getValue()).append('\n');
+ }
+ }
+ if (metadataText.length() > 0) {
+ metadataText.setLength(metadataText.length() - 1);
+ metaTextView.setText(metadataText);
+ metaTextView.setVisibility(View.VISIBLE);
+ metaTextViewLabel.setVisibility(View.VISIBLE);
+ }
+ }
+
+ TextView contentsTextView = (TextView) findViewById(R.id.contents_text_view);
+ contentsTextView.setText(displayContents);
+ int scaledSize = Math.max(22, 32 - displayContents.length() / 4);
+ contentsTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, scaledSize);
+
+ TextView supplementTextView = (TextView) findViewById(R.id.contents_supplement_text_view);
+ supplementTextView.setText("");
+ supplementTextView.setOnClickListener(null);
+ if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
+ PreferencesActivity.KEY_SUPPLEMENTAL, true)) {
+ SupplementalInfoRetriever.maybeInvokeRetrieval(supplementTextView,
+ resultHandler.getResult(),
+ historyManager,
+ this);
+ }
+
+ int buttonCount = resultHandler.getButtonCount();
+ ViewGroup buttonView = (ViewGroup) findViewById(R.id.result_button_view);
+ buttonView.requestFocus();
+ for (int x = 0; x < ResultHandler.MAX_BUTTON_COUNT; x++) {
+ TextView button = (TextView) buttonView.getChildAt(x);
+ if (x < buttonCount) {
+ button.setVisibility(View.VISIBLE);
+ button.setText(resultHandler.getButtonText(x));
+ button.setOnClickListener(new ResultButtonListener(resultHandler, x));
+ } else {
+ button.setVisibility(View.GONE);
+ }
+ }
+
+ }
+
+ // Briefly show the contents of the barcode, then handle the result outside Barcode Scanner.
+ private void handleDecodeExternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) {
+
+ if (barcode != null) {
+ viewfinderView.drawResultBitmap(barcode);
+ }
+
+ long resultDurationMS;
+ if (getIntent() == null) {
+ resultDurationMS = DEFAULT_INTENT_RESULT_DURATION_MS;
+ } else {
+ resultDurationMS = getIntent().getLongExtra(Intents.Scan.RESULT_DISPLAY_DURATION_MS,
+ DEFAULT_INTENT_RESULT_DURATION_MS);
+ }
+
+ if (resultDurationMS > 0) {
+ String rawResultString = String.valueOf(rawResult);
+ if (rawResultString.length() > 32) {
+ rawResultString = rawResultString.substring(0, 32) + " ...";
+ }
+ statusView.setText(getString(resultHandler.getDisplayTitle()) + " : " + rawResultString);
+ }
+
+ if (copyToClipboard && !resultHandler.areContentsSecure()) {
+ CharSequence text = resultHandler.getDisplayContents();
+ ClipboardInterface.setText(text, this);
+ }
+
+ if (source == IntentSource.NATIVE_APP_INTENT) {
+
+ // Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when
+ // the deprecated intent is retired.
+ Intent intent = new Intent(getIntent().getAction());
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
+ intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
+ byte[] rawBytes = rawResult.getRawBytes();
+ if (rawBytes != null && rawBytes.length > 0) {
+ intent.putExtra(Intents.Scan.RESULT_BYTES, rawBytes);
+ }
+ Map metadata = rawResult.getResultMetadata();
+ if (metadata != null) {
+ if (metadata.containsKey(ResultMetadataType.UPC_EAN_EXTENSION)) {
+ intent.putExtra(Intents.Scan.RESULT_UPC_EAN_EXTENSION,
+ metadata.get(ResultMetadataType.UPC_EAN_EXTENSION).toString());
+ }
+ Number orientation = (Number) metadata.get(ResultMetadataType.ORIENTATION);
+ if (orientation != null) {
+ intent.putExtra(Intents.Scan.RESULT_ORIENTATION, orientation.intValue());
+ }
+ String ecLevel = (String) metadata.get(ResultMetadataType.ERROR_CORRECTION_LEVEL);
+ if (ecLevel != null) {
+ intent.putExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL, ecLevel);
+ }
+ @SuppressWarnings("unchecked")
+ Iterable byteSegments = (Iterable) metadata.get(ResultMetadataType.BYTE_SEGMENTS);
+ if (byteSegments != null) {
+ int i = 0;
+ for (byte[] byteSegment : byteSegments) {
+ intent.putExtra(Intents.Scan.RESULT_BYTE_SEGMENTS_PREFIX + i, byteSegment);
+ i++;
+ }
+ }
+ }
+ sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS);
+
+ } else if (source == IntentSource.PRODUCT_SEARCH_LINK) {
+
+ // Reformulate the URL which triggered us into a query, so that the request goes to the same
+ // TLD as the scan URL.
+ int end = sourceUrl.lastIndexOf("/scan");
+ String replyURL = sourceUrl.substring(0, end) + "?q=" + resultHandler.getDisplayContents() + "&source=zxing";
+ sendReplyMessage(R.id.launch_product_query, replyURL, resultDurationMS);
+
+ } else if (source == IntentSource.ZXING_LINK) {
+
+ if (scanFromWebPageManager != null && scanFromWebPageManager.isScanFromWebPage()) {
+ String replyURL = scanFromWebPageManager.buildReplyURL(rawResult, resultHandler);
+ scanFromWebPageManager = null;
+ sendReplyMessage(R.id.launch_product_query, replyURL, resultDurationMS);
+ }
+
+ }
+ }
+
+ private void sendReplyMessage(int id, Object arg, long delayMS) {
+ if (handler != null) {
+ Message message = Message.obtain(handler, id, arg);
+ if (delayMS > 0L) {
+ handler.sendMessageDelayed(message, delayMS);
+ } else {
+ handler.sendMessage(message);
+ }
+ }
+ }
+
+ private void initCamera(SurfaceHolder surfaceHolder) {
+ if (surfaceHolder == null) {
+ throw new IllegalStateException("No SurfaceHolder provided");
+ }
+ if (cameraManager.isOpen()) {
+ Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?");
+ return;
+ }
+ try {
+ cameraManager.openDriver(surfaceHolder);
+ // Creating the handler starts the preview, which can also throw a RuntimeException.
+ if (handler == null) {
+ handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
+ }
+ decodeOrStoreSavedBitmap(null, null);
+ } catch (IOException ioe) {
+ Log.w(TAG, ioe);
+ displayFrameworkBugMessageAndExit();
+ } catch (RuntimeException e) {
+ // Barcode Scanner has seen crashes in the wild of this variety:
+ // java.?lang.?RuntimeException: Fail to connect to camera service
+ Log.w(TAG, "Unexpected error initializing camera", e);
+ displayFrameworkBugMessageAndExit();
+ }
+ }
+
+ private void displayFrameworkBugMessageAndExit() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.app_name));
+ builder.setMessage(getString(R.string.msg_camera_framework_bug));
+ builder.setPositiveButton(R.string.button_ok, new FinishListener(this));
+ builder.setOnCancelListener(new FinishListener(this));
+ builder.show();
+ }
+
+ public void restartPreviewAfterDelay(long delayMS) {
+ if (handler != null) {
+ handler.sendEmptyMessageDelayed(R.id.restart_preview, delayMS);
+ }
+ resetStatusView();
+ }
+
+ private void resetStatusView() {
+ resultView.setVisibility(View.GONE);
+ statusView.setText(R.string.msg_default_status);
+ statusView.setVisibility(View.VISIBLE);
+ viewfinderView.setVisibility(View.VISIBLE);
+ lastResult = null;
+ }
+
+ public void drawViewfinder() {
+ viewfinderView.drawViewfinder();
+ }
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/CaptureActivityHandler.java b/app/src/main/java/com/google/zxing/client/android/CaptureActivityHandler.java
new file mode 100644
index 0000000..c4698e6
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/CaptureActivityHandler.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 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.android;
+
+import android.content.ActivityNotFoundException;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.BitmapFactory;
+import android.provider.Browser;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.Result;
+import com.google.zxing.client.android.camera.CameraManager;
+
+import net.foucry.pilldroid.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * This class handles all the messaging which comprises the state machine for capture.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class CaptureActivityHandler extends Handler {
+
+ private static final String TAG = CaptureActivityHandler.class.getSimpleName();
+
+ private final CaptureActivity activity;
+ private final DecodeThread decodeThread;
+ private State state;
+ private final CameraManager cameraManager;
+
+ private enum State {
+ PREVIEW,
+ SUCCESS,
+ DONE
+ }
+
+ CaptureActivityHandler(CaptureActivity activity,
+ Collection decodeFormats,
+ Map baseHints,
+ String characterSet,
+ CameraManager cameraManager) {
+ this.activity = activity;
+ decodeThread = new DecodeThread(activity, decodeFormats, baseHints, characterSet,
+ new ViewfinderResultPointCallback(activity.getViewfinderView()));
+ decodeThread.start();
+ state = State.SUCCESS;
+
+ // Start ourselves capturing previews and decoding.
+ this.cameraManager = cameraManager;
+ cameraManager.startPreview();
+ restartPreviewAndDecode();
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case R.id.restart_preview:
+ restartPreviewAndDecode();
+ break;
+ case R.id.decode_succeeded:
+ state = State.SUCCESS;
+ Bundle bundle = message.getData();
+ Bitmap barcode = null;
+ float scaleFactor = 1.0f;
+ if (bundle != null) {
+ byte[] compressedBitmap = bundle.getByteArray(DecodeThread.BARCODE_BITMAP);
+ if (compressedBitmap != null) {
+ barcode = BitmapFactory.decodeByteArray(compressedBitmap, 0, compressedBitmap.length, null);
+ // Mutable copy:
+ barcode = barcode.copy(Bitmap.Config.ARGB_8888, true);
+ }
+ scaleFactor = bundle.getFloat(DecodeThread.BARCODE_SCALED_FACTOR);
+ }
+ activity.handleDecode((Result) message.obj, barcode, scaleFactor);
+ break;
+ case R.id.decode_failed:
+ // We're decoding as fast as possible, so when one decode fails, start another.
+ state = State.PREVIEW;
+ cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
+ break;
+ case R.id.return_scan_result:
+ activity.setResult(Activity.RESULT_OK, (Intent) message.obj);
+ activity.finish();
+ break;
+ case R.id.launch_product_query:
+ String url = (String) message.obj;
+
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ intent.setData(Uri.parse(url));
+
+ ResolveInfo resolveInfo =
+ activity.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ String browserPackageName = null;
+ if (resolveInfo != null && resolveInfo.activityInfo != null) {
+ browserPackageName = resolveInfo.activityInfo.packageName;
+ Log.d(TAG, "Using browser in package " + browserPackageName);
+ }
+
+ // Needed for default Android browser / Chrome only apparently
+ if ("com.android.browser".equals(browserPackageName) || "com.android.chrome".equals(browserPackageName)) {
+ intent.setPackage(browserPackageName);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, browserPackageName);
+ }
+
+ try {
+ activity.startActivity(intent);
+ } catch (ActivityNotFoundException ignored) {
+ Log.w(TAG, "Can't find anything to handle VIEW of URI " + url);
+ }
+ break;
+ }
+ }
+
+ public void quitSynchronously() {
+ state = State.DONE;
+ cameraManager.stopPreview();
+ Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit);
+ quit.sendToTarget();
+ try {
+ // Wait at most half a second; should be enough time, and onPause() will timeout quickly
+ decodeThread.join(500L);
+ } catch (InterruptedException e) {
+ // continue
+ }
+
+ // Be absolutely sure we don't send any queued up messages
+ removeMessages(R.id.decode_succeeded);
+ removeMessages(R.id.decode_failed);
+ }
+
+ private void restartPreviewAndDecode() {
+ if (state == State.SUCCESS) {
+ state = State.PREVIEW;
+ cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
+ activity.drawViewfinder();
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/Contents.java b/app/src/main/java/com/google/zxing/client/android/Contents.java
new file mode 100644
index 0000000..1cfff17
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/Contents.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 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.android;
+
+import android.provider.ContactsContract;
+
+/**
+ * The set of constants to use when sending Barcode Scanner an Intent which requests a barcode
+ * to be encoded.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class Contents {
+ private Contents() {
+ }
+
+ public static final class Type {
+ /**
+ * Plain text. Use Intent.putExtra(DATA, string). This can be used for URLs too, but string
+ * must include "http://" or "https://".
+ */
+ public static final String TEXT = "TEXT_TYPE";
+
+ /**
+ * An email type. Use Intent.putExtra(DATA, string) where string is the email address.
+ */
+ public static final String EMAIL = "EMAIL_TYPE";
+
+ /**
+ * Use Intent.putExtra(DATA, string) where string is the phone number to call.
+ */
+ public static final String PHONE = "PHONE_TYPE";
+
+ /**
+ * An SMS type. Use Intent.putExtra(DATA, string) where string is the number to SMS.
+ */
+ public static final String SMS = "SMS_TYPE";
+
+ /**
+ * A contact. Send a request to encode it as follows:
+ * {@code
+ * import android.provider.Contacts;
+ *
+ * Intent intent = new Intent(Intents.Encode.ACTION);
+ * intent.putExtra(Intents.Encode.TYPE, CONTACT);
+ * Bundle bundle = new Bundle();
+ * bundle.putString(ContactsContract.Intents.Insert.NAME, "Jenny");
+ * bundle.putString(ContactsContract.Intents.Insert.PHONE, "8675309");
+ * bundle.putString(ContactsContract.Intents.Insert.EMAIL, "jenny@the80s.com");
+ * bundle.putString(ContactsContract.Intents.Insert.POSTAL, "123 Fake St. San Francisco, CA 94102");
+ * intent.putExtra(Intents.Encode.DATA, bundle);
+ * }
+ */
+ public static final String CONTACT = "CONTACT_TYPE";
+
+ /**
+ * A geographic location. Use as follows:
+ * Bundle bundle = new Bundle();
+ * bundle.putFloat("LAT", latitude);
+ * bundle.putFloat("LONG", longitude);
+ * intent.putExtra(Intents.Encode.DATA, bundle);
+ */
+ public static final String LOCATION = "LOCATION_TYPE";
+
+ private Type() {
+ }
+ }
+
+ public static final String URL_KEY = "URL_KEY";
+
+ public static final String NOTE_KEY = "NOTE_KEY";
+
+ /**
+ * When using Type.CONTACT, these arrays provide the keys for adding or retrieving multiple
+ * phone numbers and addresses.
+ */
+ public static final String[] PHONE_KEYS = {
+ ContactsContract.Intents.Insert.PHONE,
+ ContactsContract.Intents.Insert.SECONDARY_PHONE,
+ ContactsContract.Intents.Insert.TERTIARY_PHONE
+ };
+
+ public static final String[] PHONE_TYPE_KEYS = {
+ ContactsContract.Intents.Insert.PHONE_TYPE,
+ ContactsContract.Intents.Insert.SECONDARY_PHONE_TYPE,
+ ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE
+ };
+
+ public static final String[] EMAIL_KEYS = {
+ ContactsContract.Intents.Insert.EMAIL,
+ ContactsContract.Intents.Insert.SECONDARY_EMAIL,
+ ContactsContract.Intents.Insert.TERTIARY_EMAIL
+ };
+
+ public static final String[] EMAIL_TYPE_KEYS = {
+ ContactsContract.Intents.Insert.EMAIL_TYPE,
+ ContactsContract.Intents.Insert.SECONDARY_EMAIL_TYPE,
+ ContactsContract.Intents.Insert.TERTIARY_EMAIL_TYPE
+ };
+
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/DecodeFormatManager.java b/app/src/main/java/com/google/zxing/client/android/DecodeFormatManager.java
new file mode 100644
index 0000000..0cef607
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/DecodeFormatManager.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 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.android;
+
+import android.content.Intent;
+import android.net.Uri;
+import com.google.zxing.BarcodeFormat;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+final class DecodeFormatManager {
+
+ private static final Pattern COMMA_PATTERN = Pattern.compile(",");
+
+ static final Set PRODUCT_FORMATS;
+ static final Set INDUSTRIAL_FORMATS;
+ private static final Set ONE_D_FORMATS;
+ static final Set QR_CODE_FORMATS = EnumSet.of(BarcodeFormat.QR_CODE);
+ static final Set DATA_MATRIX_FORMATS = EnumSet.of(BarcodeFormat.DATA_MATRIX);
+ static final Set AZTEC_FORMATS = EnumSet.of(BarcodeFormat.AZTEC);
+ static final Set PDF417_FORMATS = EnumSet.of(BarcodeFormat.PDF_417);
+ static {
+ PRODUCT_FORMATS = EnumSet.of(BarcodeFormat.UPC_A,
+ BarcodeFormat.UPC_E,
+ BarcodeFormat.EAN_13,
+ BarcodeFormat.EAN_8,
+ BarcodeFormat.RSS_14,
+ BarcodeFormat.RSS_EXPANDED);
+ INDUSTRIAL_FORMATS = EnumSet.of(BarcodeFormat.CODE_39,
+ BarcodeFormat.CODE_93,
+ BarcodeFormat.CODE_128,
+ BarcodeFormat.ITF,
+ BarcodeFormat.CODABAR);
+ ONE_D_FORMATS = EnumSet.copyOf(PRODUCT_FORMATS);
+ ONE_D_FORMATS.addAll(INDUSTRIAL_FORMATS);
+ }
+ private static final Map> FORMATS_FOR_MODE;
+ static {
+ FORMATS_FOR_MODE = new HashMap<>();
+ FORMATS_FOR_MODE.put(Intents.Scan.ONE_D_MODE, ONE_D_FORMATS);
+ FORMATS_FOR_MODE.put(Intents.Scan.PRODUCT_MODE, PRODUCT_FORMATS);
+ FORMATS_FOR_MODE.put(Intents.Scan.QR_CODE_MODE, QR_CODE_FORMATS);
+ FORMATS_FOR_MODE.put(Intents.Scan.DATA_MATRIX_MODE, DATA_MATRIX_FORMATS);
+ FORMATS_FOR_MODE.put(Intents.Scan.AZTEC_MODE, AZTEC_FORMATS);
+ FORMATS_FOR_MODE.put(Intents.Scan.PDF417_MODE, PDF417_FORMATS);
+ }
+
+ private DecodeFormatManager() {}
+
+ static Set parseDecodeFormats(Intent intent) {
+ Iterable scanFormats = null;
+ CharSequence scanFormatsString = intent.getStringExtra(Intents.Scan.FORMATS);
+ if (scanFormatsString != null) {
+ scanFormats = Arrays.asList(COMMA_PATTERN.split(scanFormatsString));
+ }
+ return parseDecodeFormats(scanFormats, intent.getStringExtra(Intents.Scan.MODE));
+ }
+
+ static Set parseDecodeFormats(Uri inputUri) {
+ List formats = inputUri.getQueryParameters(Intents.Scan.FORMATS);
+ if (formats != null && formats.size() == 1 && formats.get(0) != null){
+ formats = Arrays.asList(COMMA_PATTERN.split(formats.get(0)));
+ }
+ return parseDecodeFormats(formats, inputUri.getQueryParameter(Intents.Scan.MODE));
+ }
+
+ private static Set parseDecodeFormats(Iterable scanFormats, String decodeMode) {
+ if (scanFormats != null) {
+ Set formats = EnumSet.noneOf(BarcodeFormat.class);
+ try {
+ for (String format : scanFormats) {
+ formats.add(BarcodeFormat.valueOf(format));
+ }
+ return formats;
+ } catch (IllegalArgumentException iae) {
+ // ignore it then
+ }
+ }
+ if (decodeMode != null) {
+ return FORMATS_FOR_MODE.get(decodeMode);
+ }
+ return null;
+ }
+
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/DecodeHandler.java b/app/src/main/java/com/google/zxing/client/android/DecodeHandler.java
new file mode 100644
index 0000000..1a8dc9c
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/DecodeHandler.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2010 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.android;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.PlanarYUVLuminanceSource;
+import com.google.zxing.ReaderException;
+import com.google.zxing.Result;
+import com.google.zxing.common.HybridBinarizer;
+
+import net.foucry.pilldroid.R;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Map;
+
+final class DecodeHandler extends Handler {
+
+ private static final String TAG = DecodeHandler.class.getSimpleName();
+
+ private final CaptureActivity activity;
+ private final MultiFormatReader multiFormatReader;
+ private boolean running = true;
+
+ DecodeHandler(CaptureActivity activity, Map hints) {
+ multiFormatReader = new MultiFormatReader();
+ multiFormatReader.setHints(hints);
+ this.activity = activity;
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ if (!running) {
+ return;
+ }
+ switch (message.what) {
+ case R.id.decode:
+ decode((byte[]) message.obj, message.arg1, message.arg2);
+ break;
+ case R.id.quit:
+ running = false;
+ Looper.myLooper().quit();
+ break;
+ }
+ }
+
+ /**
+ * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
+ * reuse the same reader objects from one decode to the next.
+ *
+ * @param data The YUV preview frame.
+ * @param width The width of the preview frame.
+ * @param height The height of the preview frame.
+ */
+ private void decode(byte[] data, int width, int height) {
+ long start = System.currentTimeMillis();
+ Result rawResult = null;
+ PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
+ if (source != null) {
+ BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+ try {
+ rawResult = multiFormatReader.decodeWithState(bitmap);
+ } catch (ReaderException re) {
+ // continue
+ } finally {
+ multiFormatReader.reset();
+ }
+ }
+
+ Handler handler = activity.getHandler();
+ if (rawResult != null) {
+ // Don't log the barcode contents for security.
+ long end = System.currentTimeMillis();
+ Log.d(TAG, "Found barcode in " + (end - start) + " ms");
+ if (handler != null) {
+ Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);
+ Bundle bundle = new Bundle();
+ bundleThumbnail(source, bundle);
+ message.setData(bundle);
+ message.sendToTarget();
+ }
+ } else {
+ if (handler != null) {
+ Message message = Message.obtain(handler, R.id.decode_failed);
+ message.sendToTarget();
+ }
+ }
+ }
+
+ private static void bundleThumbnail(PlanarYUVLuminanceSource source, Bundle bundle) {
+ int[] pixels = source.renderThumbnail();
+ int width = source.getThumbnailWidth();
+ int height = source.getThumbnailHeight();
+ Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 50, out);
+ bundle.putByteArray(DecodeThread.BARCODE_BITMAP, out.toByteArray());
+ bundle.putFloat(DecodeThread.BARCODE_SCALED_FACTOR, (float) width / source.getWidth());
+ }
+
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/DecodeHintManager.java b/app/src/main/java/com/google/zxing/client/android/DecodeHintManager.java
new file mode 100644
index 0000000..39305fe
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/DecodeHintManager.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2013 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.android;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.zxing.DecodeHintType;
+
+/**
+ * @author Lachezar Dobrev
+ */
+final class DecodeHintManager {
+
+ private static final String TAG = DecodeHintManager.class.getSimpleName();
+
+ // This pattern is used in decoding integer arrays.
+ private static final Pattern COMMA = Pattern.compile(",");
+
+ private DecodeHintManager() {}
+
+ /**
+ *
Split a query string into a list of name-value pairs.
+ *
+ *
This is an alternative to the {@link Uri#getQueryParameterNames()} and
+ * {@link Uri#getQueryParameters(String)}, which are quirky and not suitable
+ * for exist-only Uri parameters.
+ *
+ *
This method ignores multiple parameters with the same name and returns the
+ * first one only. This is technically incorrect, but should be acceptable due
+ * to the method of processing Hints: no multiple values for a hint.
+ *
+ * @param query query to split
+ * @return name-value pairs
+ */
+ private static Map splitQuery(String query) {
+ Map map = new HashMap<>();
+ int pos = 0;
+ while (pos < query.length()) {
+ if (query.charAt(pos) == '&') {
+ // Skip consecutive ampersand separators.
+ pos ++;
+ continue;
+ }
+ int amp = query.indexOf('&', pos);
+ int equ = query.indexOf('=', pos);
+ if (amp < 0) {
+ // This is the last element in the query, no more ampersand elements.
+ String name;
+ String text;
+ if (equ < 0) {
+ // No equal sign
+ name = query.substring(pos);
+ name = name.replace('+', ' '); // Preemptively decode +
+ name = Uri.decode(name);
+ text = "";
+ } else {
+ // Split name and text.
+ name = query.substring(pos, equ);
+ name = name.replace('+', ' '); // Preemptively decode +
+ name = Uri.decode(name);
+ text = query.substring(equ + 1);
+ text = text.replace('+', ' '); // Preemptively decode +
+ text = Uri.decode(text);
+ }
+ if (!map.containsKey(name)) {
+ map.put(name, text);
+ }
+ break;
+ }
+ if (equ < 0 || equ > amp) {
+ // No equal sign until the &: this is a simple parameter with no value.
+ String name = query.substring(pos, amp);
+ name = name.replace('+', ' '); // Preemptively decode +
+ name = Uri.decode(name);
+ if (!map.containsKey(name)) {
+ map.put(name, "");
+ }
+ pos = amp + 1;
+ continue;
+ }
+ String name = query.substring(pos, equ);
+ name = name.replace('+', ' '); // Preemptively decode +
+ name = Uri.decode(name);
+ String text = query.substring(equ+1, amp);
+ text = text.replace('+', ' '); // Preemptively decode +
+ text = Uri.decode(text);
+ if (!map.containsKey(name)) {
+ map.put(name, text);
+ }
+ pos = amp + 1;
+ }
+ return map;
+ }
+
+ static Map parseDecodeHints(Uri inputUri) {
+ String query = inputUri.getEncodedQuery();
+ if (query == null || query.isEmpty()) {
+ return null;
+ }
+
+ // Extract parameters
+ Map parameters = splitQuery(query);
+
+ Map hints = new EnumMap<>(DecodeHintType.class);
+
+ for (DecodeHintType hintType: DecodeHintType.values()) {
+
+ if (hintType == DecodeHintType.CHARACTER_SET ||
+ hintType == DecodeHintType.NEED_RESULT_POINT_CALLBACK ||
+ hintType == DecodeHintType.POSSIBLE_FORMATS) {
+ continue; // This hint is specified in another way
+ }
+
+ String parameterName = hintType.name();
+ String parameterText = parameters.get(parameterName);
+ if (parameterText == null) {
+ continue;
+ }
+ if (hintType.getValueType().equals(Object.class)) {
+ // This is an unspecified type of hint content. Use the value as is.
+ // TODO: Can we make a different assumption on this?
+ hints.put(hintType, parameterText);
+ continue;
+ }
+ if (hintType.getValueType().equals(Void.class)) {
+ // Void hints are just flags: use the constant specified by DecodeHintType
+ hints.put(hintType, Boolean.TRUE);
+ continue;
+ }
+ if (hintType.getValueType().equals(String.class)) {
+ // A string hint: use the decoded value.
+ hints.put(hintType, parameterText);
+ continue;
+ }
+ if (hintType.getValueType().equals(Boolean.class)) {
+ // A boolean hint: a few values for false, everything else is true.
+ // An empty parameter is simply a flag-style parameter, assuming true
+ if (parameterText.isEmpty()) {
+ hints.put(hintType, Boolean.TRUE);
+ } else if ("0".equals(parameterText) ||
+ "false".equalsIgnoreCase(parameterText) ||
+ "no".equalsIgnoreCase(parameterText)) {
+ hints.put(hintType, Boolean.FALSE);
+ } else {
+ hints.put(hintType, Boolean.TRUE);
+ }
+
+ continue;
+ }
+ if (hintType.getValueType().equals(int[].class)) {
+ // An integer array. Used to specify valid lengths.
+ // Strip a trailing comma as in Java style array initialisers.
+ if (!parameterText.isEmpty() && parameterText.charAt(parameterText.length() - 1) == ',') {
+ parameterText = parameterText.substring(0, parameterText.length() - 1);
+ }
+ String[] values = COMMA.split(parameterText);
+ int[] array = new int[values.length];
+ for (int i = 0; i < values.length; i++) {
+ try {
+ array[i] = Integer.parseInt(values[i]);
+ } catch (NumberFormatException ignored) {
+ Log.w(TAG, "Skipping array of integers hint " + hintType + " due to invalid numeric value: '" + values[i] + '\'');
+ array = null;
+ break;
+ }
+ }
+ if (array != null) {
+ hints.put(hintType, array);
+ }
+ continue;
+ }
+ Log.w(TAG, "Unsupported hint type '" + hintType + "' of type " + hintType.getValueType());
+ }
+
+ Log.i(TAG, "Hints from the URI: " + hints);
+ return hints;
+ }
+
+ static Map parseDecodeHints(Intent intent) {
+ Bundle extras = intent.getExtras();
+ if (extras == null || extras.isEmpty()) {
+ return null;
+ }
+ Map hints = new EnumMap<>(DecodeHintType.class);
+
+ for (DecodeHintType hintType: DecodeHintType.values()) {
+
+ if (hintType == DecodeHintType.CHARACTER_SET ||
+ hintType == DecodeHintType.NEED_RESULT_POINT_CALLBACK ||
+ hintType == DecodeHintType.POSSIBLE_FORMATS) {
+ continue; // This hint is specified in another way
+ }
+
+ String hintName = hintType.name();
+ if (extras.containsKey(hintName)) {
+ if (hintType.getValueType().equals(Void.class)) {
+ // Void hints are just flags: use the constant specified by the DecodeHintType
+ hints.put(hintType, Boolean.TRUE);
+ } else {
+ Object hintData = extras.get(hintName);
+ if (hintType.getValueType().isInstance(hintData)) {
+ hints.put(hintType, hintData);
+ } else {
+ Log.w(TAG, "Ignoring hint " + hintType + " because it is not assignable from " + hintData);
+ }
+ }
+ }
+ }
+
+ Log.i(TAG, "Hints from the Intent: " + hints);
+ return hints;
+ }
+
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/DecodeThread.java b/app/src/main/java/com/google/zxing/client/android/DecodeThread.java
new file mode 100644
index 0000000..fab4039
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/DecodeThread.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 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.android;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.ResultPointCallback;
+
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.Looper;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * This thread does all the heavy lifting of decoding the images.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+final class DecodeThread extends Thread {
+
+ public static final String BARCODE_BITMAP = "barcode_bitmap";
+ public static final String BARCODE_SCALED_FACTOR = "barcode_scaled_factor";
+
+ private final CaptureActivity activity;
+ private final Map hints;
+ private Handler handler;
+ private final CountDownLatch handlerInitLatch;
+
+ DecodeThread(CaptureActivity activity,
+ Collection decodeFormats,
+ Map baseHints,
+ String characterSet,
+ ResultPointCallback resultPointCallback) {
+
+ this.activity = activity;
+ handlerInitLatch = new CountDownLatch(1);
+
+ hints = new EnumMap<>(DecodeHintType.class);
+ if (baseHints != null) {
+ hints.putAll(baseHints);
+ }
+
+ // The prefs can't change while the thread is running, so pick them up once here.
+ if (decodeFormats == null || decodeFormats.isEmpty()) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
+ if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D_PRODUCT, true)) {
+ decodeFormats.addAll(DecodeFormatManager.PRODUCT_FORMATS);
+ }
+ if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D_INDUSTRIAL, true)) {
+ decodeFormats.addAll(DecodeFormatManager.INDUSTRIAL_FORMATS);
+ }
+ if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_QR, true)) {
+ decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
+ }
+ if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_DATA_MATRIX, true)) {
+ decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
+ }
+ if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_AZTEC, false)) {
+ decodeFormats.addAll(DecodeFormatManager.AZTEC_FORMATS);
+ }
+ if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_PDF417, false)) {
+ decodeFormats.addAll(DecodeFormatManager.PDF417_FORMATS);
+ }
+ }
+ hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
+
+ if (characterSet != null) {
+ hints.put(DecodeHintType.CHARACTER_SET, characterSet);
+ }
+ hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
+ Log.i("DecodeThread", "Hints: " + hints);
+ }
+
+ Handler getHandler() {
+ try {
+ handlerInitLatch.await();
+ } catch (InterruptedException ie) {
+ // continue?
+ }
+ return handler;
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ handler = new DecodeHandler(activity, hints);
+ handlerInitLatch.countDown();
+ Looper.loop();
+ }
+
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/FinishListener.java b/app/src/main/java/com/google/zxing/client/android/FinishListener.java
new file mode 100644
index 0000000..5d59886
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/FinishListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 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.android;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+
+/**
+ * Simple listener used to exit the app in a few cases.
+ *
+ * @author Sean Owen
+ */
+public final class FinishListener implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+
+ private final Activity activityToFinish;
+
+ public FinishListener(Activity activityToFinish) {
+ this.activityToFinish = activityToFinish;
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialogInterface) {
+ run();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ run();
+ }
+
+ private void run() {
+ activityToFinish.finish();
+ }
+
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/HelpActivity.java b/app/src/main/java/com/google/zxing/client/android/HelpActivity.java
new file mode 100644
index 0000000..1f964bd
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/HelpActivity.java
@@ -0,0 +1,66 @@
+/*
+ * 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.android;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.webkit.WebView;
+
+import net.foucry.pilldroid.R;
+
+/**
+ * An HTML-based help screen.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class HelpActivity extends Activity {
+
+ private static final String BASE_URL =
+ "file:///android_asset/html-" + LocaleManager.getTranslatedAssetLanguage() + '/';
+
+ private WebView webView;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.help);
+
+ webView = (WebView) findViewById(R.id.help_contents);
+
+ if (icicle == null) {
+ webView.loadUrl(BASE_URL + "index.html");
+ } else {
+ webView.restoreState(icicle);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
+ webView.goBack();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle icicle) {
+ super.onSaveInstanceState(icicle);
+ webView.saveState(icicle);
+ }
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/HttpHelper.java b/app/src/main/java/com/google/zxing/client/android/HttpHelper.java
new file mode 100644
index 0000000..5a1543d
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/HttpHelper.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2011 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.android;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * Utility methods for retrieving content over HTTP using the more-supported {@code java.net} classes
+ * in Android.
+ */
+public final class HttpHelper {
+
+ private static final String TAG = HttpHelper.class.getSimpleName();
+
+ private static final Collection REDIRECTOR_DOMAINS = new HashSet<>(Arrays.asList(
+ "amzn.to", "bit.ly", "bitly.com", "fb.me", "goo.gl", "is.gd", "j.mp", "lnkd.in", "ow.ly",
+ "R.BEETAGG.COM", "r.beetagg.com", "SCN.BY", "su.pr", "t.co", "tinyurl.com", "tr.im"
+ ));
+
+ private HttpHelper() {
+ }
+
+ public enum ContentType {
+ /** HTML-like content type, including HTML, XHTML, etc. */
+ HTML,
+ /** JSON content */
+ JSON,
+ /** XML */
+ XML,
+ /** Plain text content */
+ TEXT,
+ }
+
+ /**
+ * Downloads the entire resource instead of part.
+ *
+ * @param uri URI to retrieve
+ * @param type expected text-like MIME type of that content
+ * @return content as a {@code String}
+ * @throws IOException if the content can't be retrieved because of a bad URI, network problem, etc.
+ * @see #downloadViaHttp(String, HttpHelper.ContentType, int)
+ */
+ public static CharSequence downloadViaHttp(String uri, ContentType type) throws IOException {
+ return downloadViaHttp(uri, type, Integer.MAX_VALUE);
+ }
+
+ /**
+ * @param uri URI to retrieve
+ * @param type expected text-like MIME type of that content
+ * @param maxChars approximate maximum characters to read from the source
+ * @return content as a {@code String}
+ * @throws IOException if the content can't be retrieved because of a bad URI, network problem, etc.
+ */
+ public static CharSequence downloadViaHttp(String uri, ContentType type, int maxChars) throws IOException {
+ String contentTypes;
+ switch (type) {
+ case HTML:
+ contentTypes = "application/xhtml+xml,text/html,text/*,*/*";
+ break;
+ case JSON:
+ contentTypes = "application/json,text/*,*/*";
+ break;
+ case XML:
+ contentTypes = "application/xml,text/*,*/*";
+ break;
+ case TEXT:
+ default:
+ contentTypes = "text/*,*/*";
+ }
+ return downloadViaHttp(uri, contentTypes, maxChars);
+ }
+
+ private static CharSequence downloadViaHttp(String uri, String contentTypes, int maxChars) throws IOException {
+ int redirects = 0;
+ while (redirects < 5) {
+ URL url = new URL(uri);
+ HttpURLConnection connection = safelyOpenConnection(url);
+ connection.setInstanceFollowRedirects(true); // Won't work HTTP -> HTTPS or vice versa
+ connection.setRequestProperty("Accept", contentTypes);
+ connection.setRequestProperty("Accept-Charset", "utf-8,*");
+ connection.setRequestProperty("User-Agent", "ZXing (Android)");
+ try {
+ int responseCode = safelyConnect(connection);
+ switch (responseCode) {
+ case HttpURLConnection.HTTP_OK:
+ return consume(connection, maxChars);
+ case HttpURLConnection.HTTP_MOVED_TEMP:
+ String location = connection.getHeaderField("Location");
+ if (location != null) {
+ uri = location;
+ redirects++;
+ continue;
+ }
+ throw new IOException("No Location");
+ default:
+ throw new IOException("Bad HTTP response: " + responseCode);
+ }
+ } finally {
+ connection.disconnect();
+ }
+ }
+ throw new IOException("Too many redirects");
+ }
+
+ private static String getEncoding(URLConnection connection) {
+ String contentTypeHeader = connection.getHeaderField("Content-Type");
+ if (contentTypeHeader != null) {
+ int charsetStart = contentTypeHeader.indexOf("charset=");
+ if (charsetStart >= 0) {
+ return contentTypeHeader.substring(charsetStart + "charset=".length());
+ }
+ }
+ return "UTF-8";
+ }
+
+ private static CharSequence consume(URLConnection connection, int maxChars) throws IOException {
+ String encoding = getEncoding(connection);
+ StringBuilder out = new StringBuilder();
+ Reader in = null;
+ try {
+ in = new InputStreamReader(connection.getInputStream(), encoding);
+ char[] buffer = new char[1024];
+ int charsRead;
+ while (out.length() < maxChars && (charsRead = in.read(buffer)) > 0) {
+ out.append(buffer, 0, charsRead);
+ }
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException | NullPointerException ioe) {
+ // continue
+ }
+ }
+ }
+ return out;
+ }
+
+ public static URI unredirect(URI uri) throws IOException {
+ if (!REDIRECTOR_DOMAINS.contains(uri.getHost())) {
+ return uri;
+ }
+ URL url = uri.toURL();
+ HttpURLConnection connection = safelyOpenConnection(url);
+ connection.setInstanceFollowRedirects(false);
+ connection.setDoInput(false);
+ connection.setRequestMethod("HEAD");
+ connection.setRequestProperty("User-Agent", "ZXing (Android)");
+ try {
+ int responseCode = safelyConnect(connection);
+ switch (responseCode) {
+ case HttpURLConnection.HTTP_MULT_CHOICE:
+ case HttpURLConnection.HTTP_MOVED_PERM:
+ case HttpURLConnection.HTTP_MOVED_TEMP:
+ case HttpURLConnection.HTTP_SEE_OTHER:
+ case 307: // No constant for 307 Temporary Redirect ?
+ String location = connection.getHeaderField("Location");
+ if (location != null) {
+ try {
+ return new URI(location);
+ } catch (URISyntaxException e) {
+ // nevermind
+ }
+ }
+ }
+ return uri;
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ private static HttpURLConnection safelyOpenConnection(URL url) throws IOException {
+ URLConnection conn;
+ try {
+ conn = url.openConnection();
+ } catch (NullPointerException npe) {
+ // Another strange bug in Android?
+ Log.w(TAG, "Bad URI? " + url);
+ throw new IOException(npe);
+ }
+ if (!(conn instanceof HttpURLConnection)) {
+ throw new IOException();
+ }
+ return (HttpURLConnection) conn;
+ }
+
+ private static int safelyConnect(HttpURLConnection connection) throws IOException {
+ try {
+ connection.connect();
+ } catch (NullPointerException | IllegalArgumentException | IndexOutOfBoundsException | SecurityException e) {
+ // this is an Android bug: http://code.google.com/p/android/issues/detail?id=16895
+ throw new IOException(e);
+ }
+ try {
+ return connection.getResponseCode();
+ } catch (NullPointerException | StringIndexOutOfBoundsException | IllegalArgumentException e) {
+ // this is maybe this Android bug: http://code.google.com/p/android/issues/detail?id=15554
+ throw new IOException(e);
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/google/zxing/client/android/InactivityTimer.java b/app/src/main/java/com/google/zxing/client/android/InactivityTimer.java
new file mode 100644
index 0000000..5e33d90
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/client/android/InactivityTimer.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 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.android;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncTask;
+import android.os.BatteryManager;
+import android.util.Log;
+
+/**
+ * Finishes an activity after a period of inactivity if the device is on battery power.
+ */
+final class InactivityTimer {
+
+ private static final String TAG = InactivityTimer.class.getSimpleName();
+
+ private static final long INACTIVITY_DELAY_MS = 5 * 60 * 1000L;
+
+ private final Activity activity;
+ private final BroadcastReceiver powerStatusReceiver;
+ private boolean registered;
+ private AsyncTask