From 07349a887eae3bd2e544e71713dd4f66bf8167e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E6=96=87=E6=9D=B0?=
<71892154+18948589010@users.noreply.github.com>
Date: Wed, 13 Oct 2021 00:19:48 +0800
Subject: [PATCH] =?UTF-8?q?C/S=E8=BD=AF=E4=BB=B6=E4=BD=93=E7=B3=BB?=
=?UTF-8?q?=E7=BB=93=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
main/AndroidManifest.xml | 54 +++
.../com/dommy/qrcode/util/BitmapUtil.java | 115 +++++
main/java/com/dommy/qrcode/util/Constant.java | 13 +
main/java/com/dommy/qrcode/util/UriUtil.java | 95 +++++
.../zxing/activity/CaptureActivity.java | 385 +++++++++++++++++
.../zxing/camera/AutoFocusCallback.java | 48 +++
.../camera/CameraConfigurationManager.java | 278 ++++++++++++
.../geogle/zxing/camera/CameraManager.java | 398 ++++++++++++++++++
.../zxing/camera/FlashlightManager.java | 150 +++++++
.../camera/PlanarYUVLuminanceSource.java | 133 ++++++
.../geogle/zxing/camera/PreviewCallback.java | 59 +++
.../decoding/CaptureActivityHandler.java | 141 +++++++
.../zxing/decoding/DecodeFormatManager.java | 106 +++++
.../geogle/zxing/decoding/DecodeHandler.java | 116 +++++
.../geogle/zxing/decoding/DecodeThread.java | 87 ++++
.../geogle/zxing/decoding/FinishListener.java | 47 +++
.../zxing/decoding/InactivityTimer.java | 71 ++++
.../com/geogle/zxing/decoding/Intents.java | 190 +++++++++
.../zxing/decoding/RGBLuminanceSource.java | 104 +++++
.../zxing/encoding/EncodingHandler.java | 132 ++++++
.../view/ViewfinderResultPointCallback.java | 33 ++
.../com/geogle/zxing/view/ViewfinderView.java | 286 +++++++++++++
.../edu/hzuapps/androidlabs/MainActivity.java | 14 +
.../androidlabs/watchtv/CollectFragment.java | 88 ++++
.../androidlabs/watchtv/HomeFragment.java | 108 +++++
.../androidlabs/watchtv/MovieActivity.java | 65 +++
.../androidlabs/watchtv/MyBaseAdapter.java | 56 +++
.../watchtv/MyFragmentAdapter.java | 31 ++
.../androidlabs/watchtv/MyVideoView.java | 32 ++
.../androidlabs/watchtv/SelectActivity.java | 35 ++
.../androidlabs/watchtv/UserFragment.java | 59 +++
.../androidlabs/watchtv/WatchTVActivity.java | 300 +++++++++++++
.../androidlabs/watchtv/room/Programs.java | 93 ++++
.../androidlabs/watchtv/room/ProgramsDao.java | 16 +
.../watchtv/room/ProgramsDatabase.java | 24 ++
.../watchtv/room/manager/DBEngine.java | 49 +++
.../androidlabs/watchtv/until/ToolUtils.java | 13 +
.../drawable-v24/ic_launcher_foreground.xml | 30 ++
main/res/drawable-v24/logo.jpg | Bin 0 -> 28498 bytes
.../abc_ic_menu_moreoverflow_mtrl_alpha.png | Bin 0 -> 218 bytes
main/res/drawable/account_select.xml | 6 +
main/res/drawable/btn_back.png | Bin 0 -> 172375 bytes
main/res/drawable/edit_background.xml | 14 +
main/res/drawable/flash_off.png | Bin 0 -> 5785 bytes
main/res/drawable/flash_on.png | Bin 0 -> 4825 bytes
main/res/drawable/house_select.xml | 5 +
.../ic_baseline_account_circle_24.xml | 10 +
.../ic_baseline_account_circle_24_actvite.xml | 10 +
.../drawable/ic_baseline_attach_money_24.xml | 10 +
.../drawable/ic_baseline_chevron_left_24.xml | 10 +
.../drawable/ic_baseline_chevron_right_24.xml | 10 +
main/res/drawable/ic_baseline_email_24.xml | 10 +
.../ic_baseline_format_list_bulleted_24.xml | 10 +
main/res/drawable/ic_baseline_house_24.xml | 10 +
.../drawable/ic_baseline_house_24_active.xml | 10 +
main/res/drawable/ic_baseline_live_tv_24.xml | 10 +
.../ic_baseline_local_fire_department_24.xml | 10 +
.../drawable/ic_baseline_more_horiz_24.xml | 10 +
.../ic_baseline_qr_code_scanner_24.xml | 10 +
main/res/drawable/ic_baseline_search_24.xml | 10 +
main/res/drawable/ic_baseline_settings_24.xml | 10 +
main/res/drawable/ic_baseline_share_24.xml | 10 +
main/res/drawable/ic_baseline_star_24.xml | 10 +
.../drawable/ic_baseline_star_24_actvite.xml | 10 +
main/res/drawable/ic_baseline_update_24.xml | 10 +
main/res/drawable/ic_launcher_background.xml | 170 ++++++++
main/res/drawable/ic_switch_screen.xml | 4 +
main/res/drawable/myphoto.jpg | Bin 0 -> 49018 bytes
main/res/drawable/search_shape.xml | 15 +
main/res/drawable/star_select.xml | 5 +
main/res/drawable/thumbnail.xml | 4 +
main/res/layout/activity_bottom.xml | 83 ++++
main/res/layout/activity_main.xml | 18 +
main/res/layout/activity_mid.xml | 17 +
main/res/layout/activity_movie.xml | 17 +
main/res/layout/activity_relative_layout.xml | 33 ++
main/res/layout/activity_scanner.xml | 45 ++
main/res/layout/activity_select.xml | 32 ++
.../res/layout/activity_soft1914080902251.xml | 16 +
main/res/layout/activity_top.xml | 38 ++
main/res/layout/activity_view_by_java.xml | 16 +
main/res/layout/activity_watch_t_v.xml | 14 +
main/res/layout/fragment_collect.xml | 13 +
main/res/layout/fragment_home.xml | 54 +++
main/res/layout/fragment_user.xml | 218 ++++++++++
main/res/layout/list_item.xml | 14 +
main/res/layout/list_item_tag.xml | 13 +
main/res/layout/toolbar_scanner.xml | 45 ++
main/res/mipmap-anydpi-v26/ic_launcher.xml | 5 +
.../mipmap-anydpi-v26/ic_launcher_round.xml | 5 +
main/res/raw/beep.ogg | Bin 0 -> 12237 bytes
main/res/values-night/themes.xml | 16 +
main/res/values/array.xml | 49 +++
main/res/values/attrs.xml | 23 +
main/res/values/colors.xml | 23 +
main/res/values/ids.xml | 28 ++
main/res/values/strings.xml | 10 +
main/res/values/themes.xml | 16 +
98 files changed, 5328 insertions(+)
create mode 100644 main/AndroidManifest.xml
create mode 100644 main/java/com/dommy/qrcode/util/BitmapUtil.java
create mode 100644 main/java/com/dommy/qrcode/util/Constant.java
create mode 100644 main/java/com/dommy/qrcode/util/UriUtil.java
create mode 100644 main/java/com/geogle/zxing/activity/CaptureActivity.java
create mode 100644 main/java/com/geogle/zxing/camera/AutoFocusCallback.java
create mode 100644 main/java/com/geogle/zxing/camera/CameraConfigurationManager.java
create mode 100644 main/java/com/geogle/zxing/camera/CameraManager.java
create mode 100644 main/java/com/geogle/zxing/camera/FlashlightManager.java
create mode 100644 main/java/com/geogle/zxing/camera/PlanarYUVLuminanceSource.java
create mode 100644 main/java/com/geogle/zxing/camera/PreviewCallback.java
create mode 100644 main/java/com/geogle/zxing/decoding/CaptureActivityHandler.java
create mode 100644 main/java/com/geogle/zxing/decoding/DecodeFormatManager.java
create mode 100644 main/java/com/geogle/zxing/decoding/DecodeHandler.java
create mode 100644 main/java/com/geogle/zxing/decoding/DecodeThread.java
create mode 100644 main/java/com/geogle/zxing/decoding/FinishListener.java
create mode 100644 main/java/com/geogle/zxing/decoding/InactivityTimer.java
create mode 100644 main/java/com/geogle/zxing/decoding/Intents.java
create mode 100644 main/java/com/geogle/zxing/decoding/RGBLuminanceSource.java
create mode 100644 main/java/com/geogle/zxing/encoding/EncodingHandler.java
create mode 100644 main/java/com/geogle/zxing/view/ViewfinderResultPointCallback.java
create mode 100644 main/java/com/geogle/zxing/view/ViewfinderView.java
create mode 100644 main/java/edu/hzuapps/androidlabs/MainActivity.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/CollectFragment.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/HomeFragment.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/MovieActivity.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/MyBaseAdapter.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/MyFragmentAdapter.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/MyVideoView.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/SelectActivity.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/UserFragment.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/WatchTVActivity.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/room/Programs.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/room/ProgramsDao.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/room/ProgramsDatabase.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/room/manager/DBEngine.java
create mode 100644 main/java/edu/hzuapps/androidlabs/watchtv/until/ToolUtils.java
create mode 100644 main/res/drawable-v24/ic_launcher_foreground.xml
create mode 100644 main/res/drawable-v24/logo.jpg
create mode 100644 main/res/drawable/abc_ic_menu_moreoverflow_mtrl_alpha.png
create mode 100644 main/res/drawable/account_select.xml
create mode 100644 main/res/drawable/btn_back.png
create mode 100644 main/res/drawable/edit_background.xml
create mode 100644 main/res/drawable/flash_off.png
create mode 100644 main/res/drawable/flash_on.png
create mode 100644 main/res/drawable/house_select.xml
create mode 100644 main/res/drawable/ic_baseline_account_circle_24.xml
create mode 100644 main/res/drawable/ic_baseline_account_circle_24_actvite.xml
create mode 100644 main/res/drawable/ic_baseline_attach_money_24.xml
create mode 100644 main/res/drawable/ic_baseline_chevron_left_24.xml
create mode 100644 main/res/drawable/ic_baseline_chevron_right_24.xml
create mode 100644 main/res/drawable/ic_baseline_email_24.xml
create mode 100644 main/res/drawable/ic_baseline_format_list_bulleted_24.xml
create mode 100644 main/res/drawable/ic_baseline_house_24.xml
create mode 100644 main/res/drawable/ic_baseline_house_24_active.xml
create mode 100644 main/res/drawable/ic_baseline_live_tv_24.xml
create mode 100644 main/res/drawable/ic_baseline_local_fire_department_24.xml
create mode 100644 main/res/drawable/ic_baseline_more_horiz_24.xml
create mode 100644 main/res/drawable/ic_baseline_qr_code_scanner_24.xml
create mode 100644 main/res/drawable/ic_baseline_search_24.xml
create mode 100644 main/res/drawable/ic_baseline_settings_24.xml
create mode 100644 main/res/drawable/ic_baseline_share_24.xml
create mode 100644 main/res/drawable/ic_baseline_star_24.xml
create mode 100644 main/res/drawable/ic_baseline_star_24_actvite.xml
create mode 100644 main/res/drawable/ic_baseline_update_24.xml
create mode 100644 main/res/drawable/ic_launcher_background.xml
create mode 100644 main/res/drawable/ic_switch_screen.xml
create mode 100644 main/res/drawable/myphoto.jpg
create mode 100644 main/res/drawable/search_shape.xml
create mode 100644 main/res/drawable/star_select.xml
create mode 100644 main/res/drawable/thumbnail.xml
create mode 100644 main/res/layout/activity_bottom.xml
create mode 100644 main/res/layout/activity_main.xml
create mode 100644 main/res/layout/activity_mid.xml
create mode 100644 main/res/layout/activity_movie.xml
create mode 100644 main/res/layout/activity_relative_layout.xml
create mode 100644 main/res/layout/activity_scanner.xml
create mode 100644 main/res/layout/activity_select.xml
create mode 100644 main/res/layout/activity_soft1914080902251.xml
create mode 100644 main/res/layout/activity_top.xml
create mode 100644 main/res/layout/activity_view_by_java.xml
create mode 100644 main/res/layout/activity_watch_t_v.xml
create mode 100644 main/res/layout/fragment_collect.xml
create mode 100644 main/res/layout/fragment_home.xml
create mode 100644 main/res/layout/fragment_user.xml
create mode 100644 main/res/layout/list_item.xml
create mode 100644 main/res/layout/list_item_tag.xml
create mode 100644 main/res/layout/toolbar_scanner.xml
create mode 100644 main/res/mipmap-anydpi-v26/ic_launcher.xml
create mode 100644 main/res/mipmap-anydpi-v26/ic_launcher_round.xml
create mode 100644 main/res/raw/beep.ogg
create mode 100644 main/res/values-night/themes.xml
create mode 100644 main/res/values/array.xml
create mode 100644 main/res/values/attrs.xml
create mode 100644 main/res/values/colors.xml
create mode 100644 main/res/values/ids.xml
create mode 100644 main/res/values/strings.xml
create mode 100644 main/res/values/themes.xml
diff --git a/main/AndroidManifest.xml b/main/AndroidManifest.xml
new file mode 100644
index 0000000..3c81179
--- /dev/null
+++ b/main/AndroidManifest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main/java/com/dommy/qrcode/util/BitmapUtil.java b/main/java/com/dommy/qrcode/util/BitmapUtil.java
new file mode 100644
index 0000000..8adaa43
--- /dev/null
+++ b/main/java/com/dommy/qrcode/util/BitmapUtil.java
@@ -0,0 +1,115 @@
+package com.dommy.qrcode.util;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Bitmap util.
+ *
从Uri直接读取图片流,避免路径转换的适配问题
+ */
+public class BitmapUtil {
+ /**
+ * 读取一个缩放后的图片,限定图片大小,避免OOM
+ *
+ * @param uri 图片uri,支持“file://”、“content://”
+ * @param maxWidth 最大允许宽度
+ * @param maxHeight 最大允许高度
+ * @return 返回一个缩放后的Bitmap,失败则返回null
+ */
+ public static Bitmap decodeUri(Context context, Uri uri, int maxWidth, int maxHeight) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true; //只读取图片尺寸
+ readBitmapScale(context, uri, options);
+
+ //计算实际缩放比例
+ int scale = 1;
+ for (int i = 0; i < Integer.MAX_VALUE; i++) {
+ if ((options.outWidth / scale > maxWidth &&
+ options.outWidth / scale > maxWidth * 1.4) ||
+ (options.outHeight / scale > maxHeight &&
+ options.outHeight / scale > maxHeight * 1.4)) {
+ scale++;
+ } else {
+ break;
+ }
+ }
+
+ options.inSampleSize = scale;
+ options.inJustDecodeBounds = false;//读取图片内容
+ options.inPreferredConfig = Bitmap.Config.RGB_565; //根据情况进行修改
+ Bitmap bitmap = null;
+ try {
+ bitmap = readBitmapData(context, uri, options);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return bitmap;
+ }
+
+ private static void readBitmapScale(Context context, Uri uri, BitmapFactory.Options options) {
+ if (uri == null) {
+ return;
+ }
+ String scheme = uri.getScheme();
+ if (ContentResolver.SCHEME_CONTENT.equals(scheme) ||
+ ContentResolver.SCHEME_FILE.equals(scheme)) {
+ InputStream stream = null;
+ try {
+ stream = context.getContentResolver().openInputStream(uri);
+ BitmapFactory.decodeStream(stream, null, options);
+ } catch (Exception e) {
+ Log.w("readBitmapScale", "Unable to open content: " + uri, e);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ Log.e("readBitmapScale", "Unable to close content: " + uri, e);
+ }
+ }
+ }
+ } else if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ Log.e("readBitmapScale", "Unable to close content: " + uri);
+ } else {
+ Log.e("readBitmapScale", "Unable to close content: " + uri);
+ }
+ }
+
+ private static Bitmap readBitmapData(Context context, Uri uri, BitmapFactory.Options options) {
+ if (uri == null) {
+ return null;
+ }
+ Bitmap bitmap = null;
+ String scheme = uri.getScheme();
+ if (ContentResolver.SCHEME_CONTENT.equals(scheme) ||
+ ContentResolver.SCHEME_FILE.equals(scheme)) {
+ InputStream stream = null;
+ try {
+ stream = context.getContentResolver().openInputStream(uri);
+ bitmap = BitmapFactory.decodeStream(stream, null, options);
+ } catch (Exception e) {
+ Log.e("readBitmapData", "Unable to open content: " + uri, e);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ Log.e("readBitmapData", "Unable to close content: " + uri, e);
+ }
+ }
+ }
+ } else if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ Log.e("readBitmapData", "Unable to close content: " + uri);
+ } else {
+ Log.e("readBitmapData", "Unable to close content: " + uri);
+ }
+ return bitmap;
+ }
+}
\ No newline at end of file
diff --git a/main/java/com/dommy/qrcode/util/Constant.java b/main/java/com/dommy/qrcode/util/Constant.java
new file mode 100644
index 0000000..9a490e0
--- /dev/null
+++ b/main/java/com/dommy/qrcode/util/Constant.java
@@ -0,0 +1,13 @@
+package com.dommy.qrcode.util;
+
+/**
+ * 常量
+ */
+public class Constant {
+ // request参数
+ public static final int REQ_QR_CODE = 11002; // // 打开扫描界面请求码
+ public static final int REQ_PERM_CAMERA = 11003; // 打开摄像头
+ public static final int REQ_PERM_EXTERNAL_STORAGE = 11004; // 读写文件
+
+ public static final String INTENT_EXTRA_KEY_QR_SCAN = "qr_scan_result";
+}
diff --git a/main/java/com/dommy/qrcode/util/UriUtil.java b/main/java/com/dommy/qrcode/util/UriUtil.java
new file mode 100644
index 0000000..6f4cd2c
--- /dev/null
+++ b/main/java/com/dommy/qrcode/util/UriUtil.java
@@ -0,0 +1,95 @@
+package com.dommy.qrcode.util;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+
+/**
+ * Uri路径工具
+ *
+ * @Deprecated 该方法存在终端适配问题,较为麻烦,已经弃用,新方法为BitmapUtil
+ * @see BitmapUtil
+ */
+@Deprecated
+public class UriUtil {
+ /**
+ * 根据图片的Uri获取图片的绝对路径(适配多种API)
+ *
+ * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
+ */
+ public static String getRealPathFromUri(Context context, Uri uri) {
+ int sdkVersion = Build.VERSION.SDK_INT;
+ if (sdkVersion < 11) return getRealPathFromUri_BelowApi11(context, uri);
+ if (sdkVersion < 19) return getRealPathFromUri_Api11To18(context, uri);
+ else return getRealPathFromUri_AboveApi19(context, uri);
+ }
+
+ /**
+ * 适配api19以上,根据uri获取图片的绝对路径
+ */
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private static String getRealPathFromUri_AboveApi19(Context context, Uri uri) {
+ String filePath = null;
+ String wholeID = DocumentsContract.getDocumentId(uri);
+
+ // 使用':'分割
+ String[] ids = wholeID.split(":");
+ String id = null;
+ if (ids == null) {
+ return null;
+ }
+ if (ids.length > 1) {
+ id = ids[1];
+ } else {
+ id = ids[0];
+ }
+
+ String[] projection = {MediaStore.Images.Media.DATA};
+ String selection = MediaStore.Images.Media._ID + "=?";
+ String[] selectionArgs = {id};
+
+ Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,//
+ projection, selection, selectionArgs, null);
+ int columnIndex = cursor.getColumnIndex(projection[0]);
+ if (cursor.moveToFirst()) filePath = cursor.getString(columnIndex);
+ cursor.close();
+ return filePath;
+ }
+
+ /**
+ * 适配api11-api18,根据uri获取图片的绝对路径
+ */
+ private static String getRealPathFromUri_Api11To18(Context context, Uri uri) {
+ String filePath = null;
+ String[] projection = {MediaStore.Images.Media.DATA};
+ CursorLoader loader = new CursorLoader(context, uri, projection, null, null, null);
+ Cursor cursor = loader.loadInBackground();
+
+ if (cursor != null) {
+ cursor.moveToFirst();
+ filePath = cursor.getString(cursor.getColumnIndex(projection[0]));
+ cursor.close();
+ }
+ return filePath;
+ }
+
+ /**
+ * 适配api11以下(不包括api11),根据uri获取图片的绝对路径
+ */
+ private static String getRealPathFromUri_BelowApi11(Context context, Uri uri) {
+ String filePath = null;
+ String[] projection = {MediaStore.Images.Media.DATA};
+ Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
+ if (cursor != null) {
+ cursor.moveToFirst();
+ filePath = cursor.getString(cursor.getColumnIndex(projection[0]));
+ cursor.close();
+ }
+ return filePath;
+ }
+}
diff --git a/main/java/com/geogle/zxing/activity/CaptureActivity.java b/main/java/com/geogle/zxing/activity/CaptureActivity.java
new file mode 100644
index 0000000..e26e6ce
--- /dev/null
+++ b/main/java/com/geogle/zxing/activity/CaptureActivity.java
@@ -0,0 +1,385 @@
+package com.geogle.zxing.activity;
+
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Vibrator;
+
+import android.text.TextUtils;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.ChecksumException;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.Result;
+import com.geogle.zxing.camera.CameraManager;
+import com.google.zxing.common.HybridBinarizer;
+
+import com.geogle.zxing.decoding.InactivityTimer;
+import com.geogle.zxing.decoding.RGBLuminanceSource;
+import com.google.zxing.qrcode.QRCodeReader;
+import com.geogle.zxing.view.ViewfinderView;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import com.geogle.zxing.decoding.CaptureActivityHandler;
+import edu.hzuapps.androidlabs.R;
+
+
+/**
+ * Initial the camera
+ *
+ * @author Ryan.Tang
+ */
+public class CaptureActivity extends AppCompatActivity implements Callback {
+
+ private static final int REQUEST_CODE_SCAN_GALLERY = 100;
+
+ private CaptureActivityHandler handler;
+ private ViewfinderView viewfinderView;
+ private ImageButton back;
+ private ImageButton btnFlash;
+ private Button btnAlbum; // 相册
+ private boolean isFlashOn = false;
+ private boolean hasSurface;
+ private Vector decodeFormats;
+ private String characterSet;
+ private InactivityTimer inactivityTimer;
+ private MediaPlayer mediaPlayer;
+ private boolean playBeep;
+ private static final float BEEP_VOLUME = 0.10f;
+ private boolean vibrate;
+ private ProgressDialog mProgress;
+ private String photo_path;
+ private Bitmap scanBitmap;
+ // private Button cancelScanButton;
+ /**
+ * Called when the activity is first created.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_scanner);
+ //ViewUtil.addTopView(getApplicationContext(), this, R.string.scan_card);
+ CameraManager.init(getApplication());
+ viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_content);
+ back = (ImageButton) findViewById(R.id.btn_back);
+ back.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ btnFlash = (ImageButton) findViewById(R.id.btn_flash);
+ btnFlash.setOnClickListener(flashListener);
+
+ btnAlbum = (Button) findViewById(R.id.btn_album);
+ btnAlbum.setOnClickListener(albumOnClick);
+
+// cancelScanButton = (Button) this.findViewById(R.id.btn_cancel_scan);
+ hasSurface = false;
+ inactivityTimer = new InactivityTimer(this);
+
+ }
+
+ private View.OnClickListener albumOnClick = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ //打开手机中的相册
+ Intent innerIntent = new Intent(Intent.ACTION_GET_CONTENT); //"android.intent.action.GET_CONTENT"
+ innerIntent.setType("image/*");
+ startActivityForResult(innerIntent, REQUEST_CODE_SCAN_GALLERY);
+ }
+ };
+
+
+ @Override
+ protected void onActivityResult(final int requestCode, int resultCode, Intent data) {
+ if (resultCode==RESULT_OK) {
+ switch (requestCode) {
+ case REQUEST_CODE_SCAN_GALLERY:
+ handleAlbumPic(data);
+ break;
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ /**
+ * 处理选择的图片
+ * @param data
+ */
+ private void handleAlbumPic(Intent data) {
+ //获取选中图片的路径
+ final Uri uri = data.getData();
+
+ mProgress = new ProgressDialog(CaptureActivity.this);
+ mProgress.setMessage("正在扫描...");
+ mProgress.setCancelable(false);
+ mProgress.show();
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Result result = scanningImage(uri);
+ mProgress.dismiss();
+ if (result != null) {
+ Intent resultIntent = new Intent();
+ Bundle bundle = new Bundle();
+ bundle.putString(com.dommy.qrcode.util.Constant.INTENT_EXTRA_KEY_QR_SCAN ,result.getText());
+
+ resultIntent.putExtras(bundle);
+ CaptureActivity.this.setResult(RESULT_OK, resultIntent);
+ finish();
+ } else {
+ Toast.makeText(CaptureActivity.this, "识别失败", Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ }
+
+ /**
+ * 扫描二维码图片的方法
+ * @param uri
+ * @return
+ */
+ public Result scanningImage(Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ Hashtable hints = new Hashtable<>();
+ hints.put(DecodeHintType.CHARACTER_SET, "UTF8"); //设置二维码内容的编码
+
+ scanBitmap = com.dommy.qrcode.util.BitmapUtil.decodeUri(this, uri, 500, 500);
+ RGBLuminanceSource source = new RGBLuminanceSource(scanBitmap);
+ BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
+ QRCodeReader reader = new QRCodeReader();
+ try {
+ return reader.decode(bitmap1, hints);
+ } catch (NotFoundException e) {
+ e.printStackTrace();
+ } catch (ChecksumException e) {
+ e.printStackTrace();
+ } catch (FormatException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ SurfaceView surfaceView = (SurfaceView) findViewById(R.id.scanner_view);
+ SurfaceHolder surfaceHolder = surfaceView.getHolder();
+ if (hasSurface) {
+ initCamera(surfaceHolder);
+ } else {
+ surfaceHolder.addCallback(this);
+ surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+ }
+ decodeFormats = null;
+ characterSet = null;
+
+ playBeep = true;
+ AudioManager audioService = (AudioManager) getSystemService(AUDIO_SERVICE);
+ if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
+ playBeep = false;
+ }
+ initBeepSound();
+ vibrate = true;
+
+ //quit the scan view
+// cancelScanButton.setOnClickListener(new OnClickListener() {
+//
+// @Override
+// public void onClick(View v) {
+// CaptureActivity.this.finish();
+// }
+// });
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (handler != null) {
+ handler.quitSynchronously();
+ handler = null;
+ }
+ CameraManager.get().closeDriver();
+ }
+
+ @Override
+ protected void onDestroy() {
+ inactivityTimer.shutdown();
+ super.onDestroy();
+ }
+
+ /**
+ * Handler scan result
+ *
+ * @param result
+ * @param barcode
+ */
+ public void handleDecode(Result result, Bitmap barcode) {
+ inactivityTimer.onActivity();
+ playBeepSoundAndVibrate();
+ String resultString = result.getText();
+ //FIXME
+ if (TextUtils.isEmpty(resultString)) {
+ Toast.makeText(CaptureActivity.this, "Scan failed!", Toast.LENGTH_SHORT).show();
+ } else {
+ Intent resultIntent = new Intent();
+ Bundle bundle = new Bundle();
+ bundle.putString(com.dommy.qrcode.util.Constant.INTENT_EXTRA_KEY_QR_SCAN, resultString);
+ System.out.println("sssssssssssssssss scan 0 = " + resultString);
+ // 不能使用Intent传递大于40kb的bitmap,可以使用一个单例对象存储这个bitmap
+// bundle.putParcelable("bitmap", barcode);
+// Logger.d("saomiao",resultString);
+ resultIntent.putExtras(bundle);
+ this.setResult(RESULT_OK, resultIntent);
+ }
+ CaptureActivity.this.finish();
+ }
+
+ private void initCamera(SurfaceHolder surfaceHolder) {
+ try {
+ CameraManager.get().openDriver(surfaceHolder);
+ } catch (IOException ioe) {
+ return;
+ } catch (RuntimeException e) {
+ return;
+ }
+ if (handler == null) {
+ handler = new CaptureActivityHandler(this, decodeFormats,
+ characterSet);
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width,
+ int height) {
+
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ if (!hasSurface) {
+ hasSurface = true;
+ initCamera(holder);
+ }
+
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ hasSurface = false;
+
+ }
+
+ public ViewfinderView getViewfinderView() {
+ return viewfinderView;
+ }
+
+ public Handler getHandler() {
+ return handler;
+ }
+
+ public void drawViewfinder() {
+ viewfinderView.drawViewfinder();
+
+ }
+
+ private void initBeepSound() {
+ 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.
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ mediaPlayer = new MediaPlayer();
+ mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mediaPlayer.setOnCompletionListener(beepListener);
+
+ AssetFileDescriptor file = getResources().openRawResourceFd(
+ R.raw.beep);
+ try {
+ mediaPlayer.setDataSource(file.getFileDescriptor(),
+ file.getStartOffset(), file.getLength());
+ file.close();
+ mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
+ mediaPlayer.prepare();
+ } catch (IOException e) {
+ mediaPlayer = null;
+ }
+ }
+ }
+
+ private static final long VIBRATE_DURATION = 200L;
+
+ private void playBeepSoundAndVibrate() {
+ if (playBeep && mediaPlayer != null) {
+ mediaPlayer.start();
+ }
+ if (vibrate) {
+ Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
+ vibrator.vibrate(VIBRATE_DURATION);
+ }
+ }
+
+ /**
+ * When the beep has finished playing, rewind to queue up another one.
+ */
+ private final OnCompletionListener beepListener = new OnCompletionListener() {
+ public void onCompletion(MediaPlayer mediaPlayer) {
+ mediaPlayer.seekTo(0);
+ }
+ };
+
+ /**
+ * 闪光灯开关按钮
+ */
+ private View.OnClickListener flashListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ try {
+ boolean isSuccess = CameraManager.get().setFlashLight(!isFlashOn);
+ if(!isSuccess){
+ Toast.makeText(CaptureActivity.this, "暂时无法开启闪光灯", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ if (isFlashOn) {
+ // 关闭闪光灯
+ btnFlash.setImageResource(R.drawable.flash_off);
+ isFlashOn = false;
+ } else {
+ // 开启闪光灯
+ btnFlash.setImageResource(R.drawable.flash_on);
+ isFlashOn = true;
+ }
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+ };
+}
\ No newline at end of file
diff --git a/main/java/com/geogle/zxing/camera/AutoFocusCallback.java b/main/java/com/geogle/zxing/camera/AutoFocusCallback.java
new file mode 100644
index 0000000..7f118bd
--- /dev/null
+++ b/main/java/com/geogle/zxing/camera/AutoFocusCallback.java
@@ -0,0 +1,48 @@
+/*
+ * 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.geogle.zxing.camera;
+
+import android.hardware.Camera;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+final class AutoFocusCallback implements Camera.AutoFocusCallback {
+
+ private static final String TAG = AutoFocusCallback.class.getSimpleName();
+
+ private static final long AUTOFOCUS_INTERVAL_MS = 1500L;
+
+ private Handler autoFocusHandler;
+ private int autoFocusMessage;
+
+ void setHandler(Handler autoFocusHandler, int autoFocusMessage) {
+ this.autoFocusHandler = autoFocusHandler;
+ this.autoFocusMessage = autoFocusMessage;
+ }
+
+ public void onAutoFocus(boolean success, Camera camera) {
+ if (autoFocusHandler != null) {
+ Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);
+ autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS);
+ autoFocusHandler = null;
+ } else {
+ Log.d(TAG, "Got auto-focus callback, but no handler for it");
+ }
+ }
+
+}
diff --git a/main/java/com/geogle/zxing/camera/CameraConfigurationManager.java b/main/java/com/geogle/zxing/camera/CameraConfigurationManager.java
new file mode 100644
index 0000000..ae66cc2
--- /dev/null
+++ b/main/java/com/geogle/zxing/camera/CameraConfigurationManager.java
@@ -0,0 +1,278 @@
+/*
+ * 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.geogle.zxing.camera;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.hardware.Camera;
+import android.os.Build;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+import java.util.regex.Pattern;
+
+final class CameraConfigurationManager {
+
+ private static final String TAG = CameraConfigurationManager.class.getSimpleName();
+
+ private static final int TEN_DESIRED_ZOOM = 27;
+ private static final int DESIRED_SHARPNESS = 30;
+
+ private static final Pattern COMMA_PATTERN = Pattern.compile(",");
+
+ private final Context context;
+ private Point screenResolution;
+ private Point cameraResolution;
+ private int previewFormat;
+ private String previewFormatString;
+
+ CameraConfigurationManager(Context context) {
+ this.context = context;
+ }
+
+ /**
+ * Reads, one time, values from the camera that are needed by the app.
+ */
+ void initFromCameraParameters(Camera camera) {
+ Camera.Parameters parameters = camera.getParameters();
+ previewFormat = parameters.getPreviewFormat();
+ previewFormatString = parameters.get("preview-format");
+ Log.d(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString);
+ WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = manager.getDefaultDisplay();
+ screenResolution = new Point(display.getWidth(), display.getHeight());
+ Log.d(TAG, "Screen resolution: " + screenResolution);
+
+ //图片拉伸
+ Point screenResolutionForCamera = new Point();
+ screenResolutionForCamera.x = screenResolution.x;
+ screenResolutionForCamera.y = screenResolution.y;
+ // preview size is always something like 480*320, other 320*480
+ if (screenResolution.x < screenResolution.y) {
+ screenResolutionForCamera.x = screenResolution.y;
+ screenResolutionForCamera.y = screenResolution.x;
+ }
+
+ cameraResolution = getCameraResolution(parameters, screenResolutionForCamera);
+ Log.d(TAG, "Camera resolution: " + screenResolution);
+
+ }
+
+ /**
+ * Sets the camera up to take preview images which are used for both preview and decoding.
+ * We detect the preview format here so that buildLuminanceSource() can build an appropriate
+ * LuminanceSource subclass. In the future we may want to force YUV420SP as it's the smallest,
+ * and the planar Y can be used for barcode scanning without a copy in some cases.
+ */
+ void setDesiredCameraParameters(Camera camera) {
+ Camera.Parameters parameters = camera.getParameters();
+ Log.d(TAG, "Setting preview size: " + cameraResolution);
+ parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
+ setFlash(parameters);
+ setZoom(parameters);
+ //setSharpness(parameters);
+ //modify here
+ camera.setDisplayOrientation(90);
+ camera.setParameters(parameters);
+ }
+
+ Point getCameraResolution() {
+ return cameraResolution;
+ }
+
+ Point getScreenResolution() {
+ return screenResolution;
+ }
+
+ int getPreviewFormat() {
+ return previewFormat;
+ }
+
+ String getPreviewFormatString() {
+ return previewFormatString;
+ }
+
+ private static Point getCameraResolution(Camera.Parameters parameters, Point screenResolution) {
+
+ String previewSizeValueString = parameters.get("preview-size-values");
+ // saw this on Xperia
+ if (previewSizeValueString == null) {
+ previewSizeValueString = parameters.get("preview-size-value");
+ }
+
+ Point cameraResolution = null;
+
+ if (previewSizeValueString != null) {
+ Log.d(TAG, "preview-size-values parameter: " + previewSizeValueString);
+ cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution);
+ }
+
+ if (cameraResolution == null) {
+ // Ensure that the camera resolution is a multiple of 8, as the screen may not be.
+ cameraResolution = new Point(
+ (screenResolution.x >> 3) << 3,
+ (screenResolution.y >> 3) << 3);
+ }
+
+ return cameraResolution;
+ }
+
+ private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString, Point screenResolution) {
+ int bestX = 0;
+ int bestY = 0;
+ int diff = Integer.MAX_VALUE;
+ for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
+
+ previewSize = previewSize.trim();
+ int dimPosition = previewSize.indexOf('x');
+ if (dimPosition < 0) {
+ Log.w(TAG, "Bad preview-size: " + previewSize);
+ continue;
+ }
+
+ int newX;
+ int newY;
+ try {
+ newX = Integer.parseInt(previewSize.substring(0, dimPosition));
+ newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
+ } catch (NumberFormatException nfe) {
+ Log.w(TAG, "Bad preview-size: " + previewSize);
+ continue;
+ }
+
+ int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y);
+ if (newDiff == 0) {
+ bestX = newX;
+ bestY = newY;
+ break;
+ } else if (newDiff < diff) {
+ bestX = newX;
+ bestY = newY;
+ diff = newDiff;
+ }
+
+ }
+
+ if (bestX > 0 && bestY > 0) {
+ return new Point(bestX, bestY);
+ }
+ return null;
+ }
+
+ private static int findBestMotZoomValue(CharSequence stringValues, int tenDesiredZoom) {
+ int tenBestValue = 0;
+ for (String stringValue : COMMA_PATTERN.split(stringValues)) {
+ stringValue = stringValue.trim();
+ double value;
+ try {
+ value = Double.parseDouble(stringValue);
+ } catch (NumberFormatException nfe) {
+ return tenDesiredZoom;
+ }
+ int tenValue = (int) (10.0 * value);
+ if (Math.abs(tenDesiredZoom - value) < Math.abs(tenDesiredZoom - tenBestValue)) {
+ tenBestValue = tenValue;
+ }
+ }
+ return tenBestValue;
+ }
+
+ private void setFlash(Camera.Parameters parameters) {
+ // FIXME: This is a hack to turn the flash off on the Samsung Galaxy.
+ // And this is a hack-hack to work around a different value on the Behold II
+ // Restrict Behold II check to Cupcake, per Samsung's advice
+ //if (Build.MODEL.contains("Behold II") &&
+ // CameraManager.SDK_INT == Build.VERSION_CODES.CUPCAKE) {
+ if (Build.MODEL.contains("Behold II") && CameraManager.SDK_INT == 3) { // 3 = Cupcake
+ parameters.set("flash-value", 1);
+ } else {
+ parameters.set("flash-value", 2);
+ }
+ // This is the standard setting to turn the flash off that all devices should honor.
+ parameters.set("flash-mode", "off");
+ }
+
+ private void setZoom(Camera.Parameters parameters) {
+
+ String zoomSupportedString = parameters.get("zoom-supported");
+ if (zoomSupportedString != null && !Boolean.parseBoolean(zoomSupportedString)) {
+ return;
+ }
+
+ int tenDesiredZoom = TEN_DESIRED_ZOOM;
+
+ String maxZoomString = parameters.get("max-zoom");
+ if (maxZoomString != null) {
+ try {
+ int tenMaxZoom = (int) (10.0 * Double.parseDouble(maxZoomString));
+ if (tenDesiredZoom > tenMaxZoom) {
+ tenDesiredZoom = tenMaxZoom;
+ }
+ } catch (NumberFormatException nfe) {
+ Log.w(TAG, "Bad max-zoom: " + maxZoomString);
+ }
+ }
+
+ String takingPictureZoomMaxString = parameters.get("taking-picture-zoom-max");
+ if (takingPictureZoomMaxString != null) {
+ try {
+ int tenMaxZoom = Integer.parseInt(takingPictureZoomMaxString);
+ if (tenDesiredZoom > tenMaxZoom) {
+ tenDesiredZoom = tenMaxZoom;
+ }
+ } catch (NumberFormatException nfe) {
+ Log.w(TAG, "Bad taking-picture-zoom-max: " + takingPictureZoomMaxString);
+ }
+ }
+
+ String motZoomValuesString = parameters.get("mot-zoom-values");
+ if (motZoomValuesString != null) {
+ tenDesiredZoom = findBestMotZoomValue(motZoomValuesString, tenDesiredZoom);
+ }
+
+ String motZoomStepString = parameters.get("mot-zoom-step");
+ if (motZoomStepString != null) {
+ try {
+ double motZoomStep = Double.parseDouble(motZoomStepString.trim());
+ int tenZoomStep = (int) (10.0 * motZoomStep);
+ if (tenZoomStep > 1) {
+ tenDesiredZoom -= tenDesiredZoom % tenZoomStep;
+ }
+ } catch (NumberFormatException nfe) {
+ // continue
+ }
+ }
+
+ // Set zoom. This helps encourage the user to pull back.
+ // Some devices like the Behold have a zoom parameter
+ if (maxZoomString != null || motZoomValuesString != null) {
+ parameters.set("zoom", String.valueOf(tenDesiredZoom / 10.0));
+ }
+
+ // Most devices, like the Hero, appear to expose this zoom parameter.
+ // It takes on values like "27" which appears to mean 2.7x zoom
+ if (takingPictureZoomMaxString != null) {
+ parameters.set("taking-picture-zoom", tenDesiredZoom);
+ }
+ }
+
+ public static int getDesiredSharpness() {
+ return DESIRED_SHARPNESS;
+ }
+
+}
diff --git a/main/java/com/geogle/zxing/camera/CameraManager.java b/main/java/com/geogle/zxing/camera/CameraManager.java
new file mode 100644
index 0000000..1366466
--- /dev/null
+++ b/main/java/com/geogle/zxing/camera/CameraManager.java
@@ -0,0 +1,398 @@
+/*
+ * 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.geogle.zxing.camera;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.os.Build;
+import android.os.Handler;
+import android.view.SurfaceHolder;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This object wraps the Camera service object and expects to be the only one talking to it. The
+ * implementation encapsulates the steps needed to take preview-sized images, which are used for
+ * both preview and decoding.
+ */
+public final class CameraManager {
+
+ private static final String TAG = CameraManager.class.getSimpleName();
+
+ private static final int MIN_FRAME_WIDTH = 240;
+ private static final int MIN_FRAME_HEIGHT = 240;
+ private static final int MAX_FRAME_WIDTH = 480;
+ private static final int MAX_FRAME_HEIGHT = 360;
+
+ private static CameraManager cameraManager;
+
+ static final int SDK_INT; // Later we can use Build.VERSION.SDK_INT
+
+ static {
+ int sdkInt;
+ try {
+ sdkInt = Integer.parseInt(Build.VERSION.SDK);
+ } catch (NumberFormatException nfe) {
+ // Just to be safe
+ sdkInt = 10000;
+ }
+ SDK_INT = sdkInt;
+ }
+
+ private final Context context;
+ private final CameraConfigurationManager configManager;
+ private Camera camera;
+ private Rect framingRect;
+ private Rect framingRectInPreview;
+ private boolean initialized;
+ private boolean previewing;
+ private final boolean useOneShotPreviewCallback;
+ /**
+ * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
+ * clear the handler so it will only receive one message.
+ */
+ private final PreviewCallback previewCallback;
+ /**
+ * Autofocus callbacks arrive here, and are dispatched to the Handler which requested them.
+ */
+ private final AutoFocusCallback autoFocusCallback;
+
+ /**
+ * Initializes this static object with the Context of the calling Activity.
+ *
+ * @param context The Activity which wants to use the camera.
+ */
+ public static void init(Context context) {
+ if (cameraManager == null) {
+ cameraManager = new CameraManager(context);
+ }
+ }
+
+ /**
+ * Gets the CameraManager singleton instance.
+ *
+ * @return A reference to the CameraManager singleton.
+ */
+ public static CameraManager get() {
+ return cameraManager;
+ }
+
+ private CameraManager(Context context) {
+
+ this.context = context;
+ this.configManager = new CameraConfigurationManager(context);
+
+ // Camera.setOneShotPreviewCallback() has a race condition in Cupcake, so we use the older
+ // Camera.setPreviewCallback() on 1.5 and earlier. For Donut and later, we need to use
+ // the more efficient one shot callback, as the older one can swamp the system and cause it
+ // to run out of memory. We can't use SDK_INT because it was introduced in the Donut SDK.
+ //useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.CUPCAKE;
+ useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > 3; // 3 = Cupcake
+
+ previewCallback = new PreviewCallback(configManager, useOneShotPreviewCallback);
+ autoFocusCallback = new AutoFocusCallback();
+ }
+
+ /**
+ * Opens the camera driver and initializes the hardware parameters.
+ *
+ * @param holder The surface object which the camera will draw preview frames into.
+ * @throws IOException Indicates the camera driver failed to open.
+ */
+ public void openDriver(SurfaceHolder holder) throws IOException {
+ if (camera == null) {
+ camera = Camera.open();
+ if (camera == null) {
+ throw new IOException();
+ }
+ camera.setPreviewDisplay(holder);
+
+ if (!initialized) {
+ initialized = true;
+ configManager.initFromCameraParameters(camera);
+ }
+ configManager.setDesiredCameraParameters(camera);
+
+ //FIXME
+ // SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ //�Ƿ�ʹ��ǰ��
+// if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) {
+// FlashlightManager.enableFlashlight();
+// }
+ FlashlightManager.enableFlashlight();
+ }
+ }
+
+ /**
+ * Closes the camera driver if still in use.
+ */
+ public void closeDriver() {
+ if (camera != null) {
+ FlashlightManager.disableFlashlight();
+ camera.release();
+ camera = null;
+ }
+ }
+
+ /**
+ * Asks the camera hardware to begin drawing preview frames to the screen.
+ */
+ public void startPreview() {
+ if (camera != null && !previewing) {
+ camera.startPreview();
+ previewing = true;
+ }
+ }
+
+ /**
+ * Tells the camera to stop drawing preview frames.
+ */
+ public void stopPreview() {
+ if (camera != null && previewing) {
+ if (!useOneShotPreviewCallback) {
+ camera.setPreviewCallback(null);
+ }
+ camera.stopPreview();
+ previewCallback.setHandler(null, 0);
+ autoFocusCallback.setHandler(null, 0);
+ previewing = false;
+ }
+ }
+
+ /**
+ * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
+ * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
+ * respectively.
+ *
+ * @param handler The handler to send the message to.
+ * @param message The what field of the message to be sent.
+ */
+ public void requestPreviewFrame(Handler handler, int message) {
+ if (camera != null && previewing) {
+ previewCallback.setHandler(handler, message);
+ if (useOneShotPreviewCallback) {
+ camera.setOneShotPreviewCallback(previewCallback);
+ } else {
+ camera.setPreviewCallback(previewCallback);
+ }
+ }
+ }
+
+ /**
+ * Asks the camera hardware to perform an autofocus.
+ *
+ * @param handler The Handler to notify when the autofocus completes.
+ * @param message The message to deliver.
+ */
+ public void requestAutoFocus(Handler handler, int message) {
+ if (camera != null && previewing) {
+ autoFocusCallback.setHandler(handler, message);
+ //Log.d(TAG, "Requesting auto-focus callback");
+ camera.autoFocus(autoFocusCallback);
+ }
+ }
+
+ /**
+ * Calculates the framing rect which the UI should draw to show the user where to place the
+ * barcode. This target helps with alignment as well as forces the user to hold the device
+ * far enough away to ensure the image will be in focus.
+ *
+ * @return The rectangle to draw on screen in window coordinates.
+ */
+ public Rect getFramingRect() {
+ Point screenResolution = configManager.getScreenResolution();
+ if (screenResolution == null)
+ return null;
+ if (framingRect == null) {
+ if (camera == null) {
+ return null;
+ }
+
+ //修改之后
+ int width = screenResolution.x * 7 / 10;
+ int height = screenResolution.y * 7 / 10;
+
+ if (height >= width) { //竖屏
+ height = width;
+ } else { //黑屏
+ width = height;
+ }
+
+ int leftOffset = (screenResolution.x - width) / 2;
+ int topOffset = (screenResolution.y - height) / 3;
+ framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
+
+ }
+ return framingRect;
+ }
+// public Rect getFramingRect() {
+// Point screenResolution = configManager.getScreenResolution();
+// if (framingRect == null) {
+// if (camera == null) {
+// return null;
+// }
+// int width = screenResolution.x * 3 / 4;
+// if (width < MIN_FRAME_WIDTH) {
+// width = MIN_FRAME_WIDTH;
+// } else if (width > MAX_FRAME_WIDTH) {
+// width = MAX_FRAME_WIDTH;
+// }
+// int height = screenResolution.y * 3 / 4;
+// if (height < MIN_FRAME_HEIGHT) {
+// height = MIN_FRAME_HEIGHT;
+// } else if (height > MAX_FRAME_HEIGHT) {
+// height = MAX_FRAME_HEIGHT;
+// }
+// int leftOffset = (screenResolution.x - width) / 2;
+// int topOffset = (screenResolution.y - height) / 2;
+// framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
+// Log.d(TAG, "Calculated framing rect: " + framingRect);
+// }
+// return framingRect;
+// }
+
+ /**
+ * Like {@link #getFramingRect} but coordinates are in terms of the preview frame,
+ * not UI / screen.
+ */
+ public Rect getFramingRectInPreview() {
+ if (framingRectInPreview == null) {
+ Rect rect = new Rect(getFramingRect());
+ Point cameraResolution = configManager.getCameraResolution();
+ Point screenResolution = configManager.getScreenResolution();
+ //modify here
+// rect.left = rect.left * cameraResolution.x / screenResolution.x;
+// rect.right = rect.right * cameraResolution.x / screenResolution.x;
+// rect.top = rect.top * cameraResolution.y / screenResolution.y;
+// rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
+ rect.left = rect.left * cameraResolution.y / screenResolution.x;
+ rect.right = rect.right * cameraResolution.y / screenResolution.x;
+ rect.top = rect.top * cameraResolution.x / screenResolution.y;
+ rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
+ framingRectInPreview = rect;
+ }
+ return framingRectInPreview;
+ }
+
+ /**
+ * Converts the result points from still resolution coordinates to screen coordinates.
+ *
+ * @param points The points returned by the Reader subclass through Result.getResultPoints().
+ * @return An array of Points scaled to the size of the framing rect and offset appropriately
+ * so they can be drawn in screen coordinates.
+ */
+ /*
+ public Point[] convertResultPoints(ResultPoint[] points) {
+ Rect frame = getFramingRectInPreview();
+ int count = points.length;
+ Point[] output = new Point[count];
+ for (int x = 0; x < count; x++) {
+ output[x] = new Point();
+ output[x].x = frame.left + (int) (points[x].getX() + 0.5f);
+ output[x].y = frame.top + (int) (points[x].getY() + 0.5f);
+ }
+ return output;
+ }
+ */
+
+ /**
+ * A factory method to build the appropriate LuminanceSource object based on the format
+ * of the preview buffers, as described by Camera.Parameters.
+ *
+ * @param data A preview frame.
+ * @param width The width of the image.
+ * @param height The height of the image.
+ * @return A PlanarYUVLuminanceSource instance.
+ */
+ public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
+ Rect rect = getFramingRectInPreview();
+ int previewFormat = configManager.getPreviewFormat();
+ String previewFormatString = configManager.getPreviewFormatString();
+ switch (previewFormat) {
+ // This is the standard Android format which all devices are REQUIRED to support.
+ // In theory, it's the only one we should ever care about.
+ case PixelFormat.YCbCr_420_SP:
+ // This format has never been seen in the wild, but is compatible as we only care
+ // about the Y channel, so allow it.
+ case PixelFormat.YCbCr_422_SP:
+ return new PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height);
+ default:
+ // The Samsung Moment incorrectly uses this variant instead of the 'sp' version.
+ // Fortunately, it too has all the Y data up front, so we can read it.
+ if ("yuv420p".equals(previewFormatString)) {
+ return new PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height);
+ }
+ }
+ throw new IllegalArgumentException("Unsupported picture format: " +
+ previewFormat + '/' + previewFormatString);
+ }
+
+ public Context getContext() {
+ return context;
+ }
+
+ /**
+ * 打开或关闭闪光灯
+ * @param isOpen 是否开启闪光灯
+ * @return boolean 操作成功/失败。
+ */
+ public boolean setFlashLight(boolean isOpen) {
+ if (camera == null || !previewing) {
+ return false;
+ }
+ Camera.Parameters parameters = camera.getParameters();
+ if (parameters == null) {
+ return false;
+ }
+ List flashModes = parameters.getSupportedFlashModes();
+ // 检查手机是否有闪光灯
+ if (null == flashModes || 0 == flashModes.size()) {
+ // 没有闪光灯则返回
+ return false;
+ }
+ String flashMode = parameters.getFlashMode();
+ if (isOpen) {
+ if (Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode)) {
+ return true;
+ }
+ // 开启
+ if (flashModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) {
+ parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
+ camera.setParameters(parameters);
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ if (Camera.Parameters.FLASH_MODE_OFF.equals(flashMode)) {
+ return true;
+ }
+ // 关闭
+ if (flashModes.contains(Camera.Parameters.FLASH_MODE_OFF)) {
+ parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
+ camera.setParameters(parameters);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+}
diff --git a/main/java/com/geogle/zxing/camera/FlashlightManager.java b/main/java/com/geogle/zxing/camera/FlashlightManager.java
new file mode 100644
index 0000000..6fd1041
--- /dev/null
+++ b/main/java/com/geogle/zxing/camera/FlashlightManager.java
@@ -0,0 +1,150 @@
+/*
+ * 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.geogle.zxing.camera;
+
+import android.os.IBinder;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * This class is used to activate the weak light on some camera phones (not flash)
+ * in order to illuminate surfaces for scanning. There is no official way to do this,
+ * but, classes which allow access to this function still exist on some devices.
+ * This therefore proceeds through a great deal of reflection.
+ *
+ * See
+ * http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/ and
+ *
+ * http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java.
+ * Thanks to Ryan Alford for pointing out the availability of this class.
+ */
+final class FlashlightManager {
+
+ private static final String TAG = FlashlightManager.class.getSimpleName();
+
+ private static final Object iHardwareService;
+ private static final Method setFlashEnabledMethod;
+ static {
+ iHardwareService = getHardwareService();
+ setFlashEnabledMethod = getSetFlashEnabledMethod(iHardwareService);
+ if (iHardwareService == null) {
+ Log.v(TAG, "This device does supports control of a flashlight");
+ } else {
+ Log.v(TAG, "This device does not support control of a flashlight");
+ }
+ }
+
+ private FlashlightManager() {
+ }
+
+ /**
+ * �����������ƿ���
+ */
+ //FIXME
+ static void enableFlashlight() {
+ setFlashlight(false);
+ }
+
+ static void disableFlashlight() {
+ setFlashlight(false);
+ }
+
+ private static Object getHardwareService() {
+ Class> serviceManagerClass = maybeForName("android.os.ServiceManager");
+ if (serviceManagerClass == null) {
+ return null;
+ }
+
+ Method getServiceMethod = maybeGetMethod(serviceManagerClass, "getService", String.class);
+ if (getServiceMethod == null) {
+ return null;
+ }
+
+ Object hardwareService = invoke(getServiceMethod, null, "hardware");
+ if (hardwareService == null) {
+ return null;
+ }
+
+ Class> iHardwareServiceStubClass = maybeForName("android.os.IHardwareService$Stub");
+ if (iHardwareServiceStubClass == null) {
+ return null;
+ }
+
+ Method asInterfaceMethod = maybeGetMethod(iHardwareServiceStubClass, "asInterface", IBinder.class);
+ if (asInterfaceMethod == null) {
+ return null;
+ }
+
+ return invoke(asInterfaceMethod, null, hardwareService);
+ }
+
+ private static Method getSetFlashEnabledMethod(Object iHardwareService) {
+ if (iHardwareService == null) {
+ return null;
+ }
+ Class> proxyClass = iHardwareService.getClass();
+ return maybeGetMethod(proxyClass, "setFlashlightEnabled", boolean.class);
+ }
+
+ private static Class> maybeForName(String name) {
+ try {
+ return Class.forName(name);
+ } catch (ClassNotFoundException cnfe) {
+ // OK
+ return null;
+ } catch (RuntimeException re) {
+ Log.w(TAG, "Unexpected error while finding class " + name, re);
+ return null;
+ }
+ }
+
+ private static Method maybeGetMethod(Class> clazz, String name, Class>... argClasses) {
+ try {
+ return clazz.getMethod(name, argClasses);
+ } catch (NoSuchMethodException nsme) {
+ // OK
+ return null;
+ } catch (RuntimeException re) {
+ Log.w(TAG, "Unexpected error while finding method " + name, re);
+ return null;
+ }
+ }
+
+ private static Object invoke(Method method, Object instance, Object... args) {
+ try {
+ return method.invoke(instance, args);
+ } catch (IllegalAccessException e) {
+ Log.w(TAG, "Unexpected error while invoking " + method, e);
+ return null;
+ } catch (InvocationTargetException e) {
+ Log.w(TAG, "Unexpected error while invoking " + method, e.getCause());
+ return null;
+ } catch (RuntimeException re) {
+ Log.w(TAG, "Unexpected error while invoking " + method, re);
+ return null;
+ }
+ }
+
+ private static void setFlashlight(boolean active) {
+ if (iHardwareService != null) {
+ invoke(setFlashEnabledMethod, iHardwareService, active);
+ }
+ }
+
+}
diff --git a/main/java/com/geogle/zxing/camera/PlanarYUVLuminanceSource.java b/main/java/com/geogle/zxing/camera/PlanarYUVLuminanceSource.java
new file mode 100644
index 0000000..c6f34ea
--- /dev/null
+++ b/main/java/com/geogle/zxing/camera/PlanarYUVLuminanceSource.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2009 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.geogle.zxing.camera;
+
+import android.graphics.Bitmap;
+
+import com.google.zxing.LuminanceSource;
+
+/**
+ * This object extends LuminanceSource around an array of YUV data returned from the camera driver,
+ * with the option to crop to a rectangle within the full data. This can be used to exclude
+ * superfluous pixels around the perimeter and speed up decoding.
+ *
+ * It works for any pixel format where the Y channel is planar and appears first, including
+ * YCbCr_420_SP and YCbCr_422_SP.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class PlanarYUVLuminanceSource extends LuminanceSource {
+ private final byte[] yuvData;
+ private final int dataWidth;
+ private final int dataHeight;
+ private final int left;
+ private final int top;
+
+ public PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top,
+ int width, int height) {
+ super(width, height);
+
+ if (left + width > dataWidth || top + height > dataHeight) {
+ throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
+ }
+
+ this.yuvData = yuvData;
+ this.dataWidth = dataWidth;
+ this.dataHeight = dataHeight;
+ this.left = left;
+ this.top = top;
+ }
+
+ @Override
+ public byte[] getRow(int y, byte[] row) {
+ if (y < 0 || y >= getHeight()) {
+ throw new IllegalArgumentException("Requested row is outside the image: " + y);
+ }
+ int width = getWidth();
+ if (row == null || row.length < width) {
+ row = new byte[width];
+ }
+ int offset = (y + top) * dataWidth + left;
+ System.arraycopy(yuvData, offset, row, 0, width);
+ return row;
+ }
+
+ @Override
+ public byte[] getMatrix() {
+ int width = getWidth();
+ int height = getHeight();
+
+ // If the caller asks for the entire underlying image, save the copy and give them the
+ // original data. The docs specifically warn that result.length must be ignored.
+ if (width == dataWidth && height == dataHeight) {
+ return yuvData;
+ }
+
+ int area = width * height;
+ byte[] matrix = new byte[area];
+ int inputOffset = top * dataWidth + left;
+
+ // If the width matches the full width of the underlying data, perform a single copy.
+ if (width == dataWidth) {
+ System.arraycopy(yuvData, inputOffset, matrix, 0, area);
+ return matrix;
+ }
+
+ // Otherwise copy one cropped row at a time.
+ byte[] yuv = yuvData;
+ for (int y = 0; y < height; y++) {
+ int outputOffset = y * width;
+ System.arraycopy(yuv, inputOffset, matrix, outputOffset, width);
+ inputOffset += dataWidth;
+ }
+ return matrix;
+ }
+
+ @Override
+ public boolean isCropSupported() {
+ return true;
+ }
+
+ public int getDataWidth() {
+ return dataWidth;
+ }
+
+ public int getDataHeight() {
+ return dataHeight;
+ }
+
+ public Bitmap renderCroppedGreyscaleBitmap() {
+ int width = getWidth();
+ int height = getHeight();
+ int[] pixels = new int[width * height];
+ byte[] yuv = yuvData;
+ int inputOffset = top * dataWidth + left;
+
+ for (int y = 0; y < height; y++) {
+ int outputOffset = y * width;
+ for (int x = 0; x < width; x++) {
+ int grey = yuv[inputOffset + x] & 0xff;
+ pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);
+ }
+ inputOffset += dataWidth;
+ }
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
+ return bitmap;
+ }
+}
diff --git a/main/java/com/geogle/zxing/camera/PreviewCallback.java b/main/java/com/geogle/zxing/camera/PreviewCallback.java
new file mode 100644
index 0000000..5fbbddb
--- /dev/null
+++ b/main/java/com/geogle/zxing/camera/PreviewCallback.java
@@ -0,0 +1,59 @@
+/*
+ * 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.geogle.zxing.camera;
+
+import android.graphics.Point;
+import android.hardware.Camera;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+final class PreviewCallback implements Camera.PreviewCallback {
+
+ private static final String TAG = PreviewCallback.class.getSimpleName();
+
+ private final CameraConfigurationManager configManager;
+ private final boolean useOneShotPreviewCallback;
+ private Handler previewHandler;
+ private int previewMessage;
+
+ PreviewCallback(CameraConfigurationManager configManager, boolean useOneShotPreviewCallback) {
+ this.configManager = configManager;
+ this.useOneShotPreviewCallback = useOneShotPreviewCallback;
+ }
+
+ void setHandler(Handler previewHandler, int previewMessage) {
+ this.previewHandler = previewHandler;
+ this.previewMessage = previewMessage;
+ }
+
+ public void onPreviewFrame(byte[] data, Camera camera) {
+ Point cameraResolution = configManager.getCameraResolution();
+ if (!useOneShotPreviewCallback) {
+ camera.setPreviewCallback(null);
+ }
+ if (previewHandler != null) {
+ Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,
+ cameraResolution.y, data);
+ message.sendToTarget();
+ previewHandler = null;
+ } else {
+ Log.d(TAG, "Got preview callback, but no handler for it");
+ }
+ }
+
+}
diff --git a/main/java/com/geogle/zxing/decoding/CaptureActivityHandler.java b/main/java/com/geogle/zxing/decoding/CaptureActivityHandler.java
new file mode 100644
index 0000000..0642453
--- /dev/null
+++ b/main/java/com/geogle/zxing/decoding/CaptureActivityHandler.java
@@ -0,0 +1,141 @@
+package com.geogle.zxing.decoding;/*
+ * 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.
+ */
+
+
+
+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 com.google.zxing.BarcodeFormat;
+import com.google.zxing.Result;
+import com.geogle.zxing.camera.CameraManager;
+import com.geogle.zxing.view.ViewfinderResultPointCallback;
+
+import java.util.Vector;
+
+import com.geogle.zxing.activity.CaptureActivity;
+import edu.hzuapps.androidlabs.R;
+
+
+/**
+ * This class handles all the messaging which comprises the state machine for capture.
+ */
+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 enum State {
+ PREVIEW,
+ SUCCESS,
+ DONE
+ }
+
+ public CaptureActivityHandler(CaptureActivity activity, Vector decodeFormats,
+ String characterSet) {
+ this.activity = activity;
+ decodeThread = new DecodeThread(activity, decodeFormats, characterSet,
+ new ViewfinderResultPointCallback(activity.getViewfinderView()));
+ decodeThread.start();
+ state = State.SUCCESS;
+ // Start ourselves capturing previews and decoding.
+ CameraManager.get().startPreview();
+ restartPreviewAndDecode();
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case R.id.auto_focus:
+ //Log.d(TAG, "Got auto-focus message");
+ // When one auto focus pass finishes, start another. This is the closest thing to
+ // continuous AF. It does seem to hunt a bit, but I'm not sure what else to do.
+ if (state == State.PREVIEW) {
+ CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
+ }
+ break;
+ case R.id.restart_preview:
+ Log.d(TAG, "Got restart preview message");
+ restartPreviewAndDecode();
+ break;
+ case R.id.decode_succeeded:
+ Log.d(TAG, "Got decode succeeded message");
+ state = State.SUCCESS;
+ Bundle bundle = message.getData();
+
+ /***********************************************************************/
+ Bitmap barcode = bundle == null ? null :
+ (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);//���ñ����߳�
+
+ activity.handleDecode((Result) message.obj, barcode);//���ؽ��
+ /***********************************************************************/
+ break;
+ case R.id.decode_failed:
+ // We're decoding as fast as possible, so when one decode fails, start another.
+ state = State.PREVIEW;
+ CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
+ break;
+ case R.id.return_scan_result:
+ Log.d(TAG, "Got return scan result message");
+ activity.setResult(Activity.RESULT_OK, (Intent) message.obj);
+ activity.finish();
+ break;
+ case R.id.launch_product_query:
+ Log.d(TAG, "Got product query message");
+ String url = (String) message.obj;
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ activity.startActivity(intent);
+ break;
+ }
+ }
+
+ public void quitSynchronously() {
+ state = State.DONE;
+ CameraManager.get().stopPreview();
+ Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit);
+ quit.sendToTarget();
+ try {
+ decodeThread.join();
+ } 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.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
+ CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
+ activity.drawViewfinder();
+ }
+ }
+
+}
diff --git a/main/java/com/geogle/zxing/decoding/DecodeFormatManager.java b/main/java/com/geogle/zxing/decoding/DecodeFormatManager.java
new file mode 100644
index 0000000..4107c8b
--- /dev/null
+++ b/main/java/com/geogle/zxing/decoding/DecodeFormatManager.java
@@ -0,0 +1,106 @@
+package com.geogle.zxing.decoding;/*
+ * 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.
+ */
+
+
+
+
+
+import android.content.Intent;
+import android.net.Uri;
+
+import com.google.zxing.BarcodeFormat;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+final class DecodeFormatManager {
+
+ private static final Pattern COMMA_PATTERN = Pattern.compile(",");
+
+ static final Vector PRODUCT_FORMATS;
+ static final Vector ONE_D_FORMATS;
+ static final Vector QR_CODE_FORMATS;
+ static final Vector DATA_MATRIX_FORMATS;
+ static {
+ PRODUCT_FORMATS = new Vector(5);
+ PRODUCT_FORMATS.add(BarcodeFormat.UPC_A);
+ PRODUCT_FORMATS.add(BarcodeFormat.UPC_E);
+ PRODUCT_FORMATS.add(BarcodeFormat.EAN_13);
+ PRODUCT_FORMATS.add(BarcodeFormat.EAN_8);
+ ONE_D_FORMATS = new Vector(PRODUCT_FORMATS.size() + 4);
+ ONE_D_FORMATS.addAll(PRODUCT_FORMATS);
+ ONE_D_FORMATS.add(BarcodeFormat.CODE_39);
+ ONE_D_FORMATS.add(BarcodeFormat.CODE_93);
+ ONE_D_FORMATS.add(BarcodeFormat.CODE_128);
+ ONE_D_FORMATS.add(BarcodeFormat.ITF);
+ QR_CODE_FORMATS = new Vector(1);
+ QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE);
+ DATA_MATRIX_FORMATS = new Vector(1);
+ DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX);
+ }
+
+ private DecodeFormatManager() {}
+
+ static Vector parseDecodeFormats(Intent intent) {
+ List scanFormats = null;
+ String scanFormatsString = intent.getStringExtra(Intents.Scan.SCAN_FORMATS);
+ if (scanFormatsString != null) {
+ scanFormats = Arrays.asList(COMMA_PATTERN.split(scanFormatsString));
+ }
+ return parseDecodeFormats(scanFormats, intent.getStringExtra(Intents.Scan.MODE));
+ }
+
+ static Vector parseDecodeFormats(Uri inputUri) {
+ List formats = inputUri.getQueryParameters(Intents.Scan.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 Vector parseDecodeFormats(Iterable scanFormats,
+ String decodeMode) {
+ if (scanFormats != null) {
+ Vector formats = new Vector();
+ try {
+ for (String format : scanFormats) {
+ formats.add(BarcodeFormat.valueOf(format));
+ }
+ return formats;
+ } catch (IllegalArgumentException iae) {
+ // ignore it then
+ }
+ }
+ if (decodeMode != null) {
+ if (Intents.Scan.PRODUCT_MODE.equals(decodeMode)) {
+ return PRODUCT_FORMATS;
+ }
+ if (Intents.Scan.QR_CODE_MODE.equals(decodeMode)) {
+ return QR_CODE_FORMATS;
+ }
+ if (Intents.Scan.DATA_MATRIX_MODE.equals(decodeMode)) {
+ return DATA_MATRIX_FORMATS;
+ }
+ if (Intents.Scan.ONE_D_MODE.equals(decodeMode)) {
+ return ONE_D_FORMATS;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/main/java/com/geogle/zxing/decoding/DecodeHandler.java b/main/java/com/geogle/zxing/decoding/DecodeHandler.java
new file mode 100644
index 0000000..58ba546
--- /dev/null
+++ b/main/java/com/geogle/zxing/decoding/DecodeHandler.java
@@ -0,0 +1,116 @@
+package com.geogle.zxing.decoding;/*
+ * 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.
+ */
+
+
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+
+import com.geogle.zxing.camera.PlanarYUVLuminanceSource;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.ReaderException;
+import com.google.zxing.Result;
+
+import com.geogle.zxing.camera.CameraManager;
+
+import com.google.zxing.common.HybridBinarizer;
+
+import java.util.Hashtable;
+
+import com.geogle.zxing.activity.CaptureActivity;
+import edu.hzuapps.androidlabs.R;
+
+
+final class DecodeHandler extends Handler {
+
+ private static final String TAG = DecodeHandler.class.getSimpleName();
+
+ private final CaptureActivity activity;
+ private final MultiFormatReader multiFormatReader;
+
+ DecodeHandler(CaptureActivity activity, Hashtable hints) {
+ multiFormatReader = new MultiFormatReader();
+ multiFormatReader.setHints(hints);
+ this.activity = activity;
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case R.id.decode:
+ //Log.d(TAG, "Got decode message");
+ decode((byte[]) message.obj, message.arg1, message.arg2);
+ break;
+ case R.id.quit:
+ 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;
+
+ //modify here
+ byte[] rotatedData = new byte[data.length];
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++)
+ rotatedData[x * height + height - y - 1] = data[x + y * width];
+ }
+ int tmp = width; // Here we are swapping, that's the difference to #11
+ width = height;
+ height = tmp;
+
+ PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, width, height);
+ BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+ try {
+ rawResult = multiFormatReader.decodeWithState(bitmap);
+ } catch (ReaderException re) {
+ // continue
+ } finally {
+ multiFormatReader.reset();
+ }
+
+ if (rawResult != null) {
+ long end = System.currentTimeMillis();
+ Log.d(TAG, "Found barcode (" + (end - start) + " ms):\n" + rawResult.toString());
+ Message message = Message.obtain(activity.getHandler(), R.id.decode_succeeded, rawResult);
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());
+ message.setData(bundle);
+ //Log.d(TAG, "Sending decode succeeded message...");
+ message.sendToTarget();
+ } else {
+ Message message = Message.obtain(activity.getHandler(), R.id.decode_failed);
+ message.sendToTarget();
+ }
+ }
+
+}
diff --git a/main/java/com/geogle/zxing/decoding/DecodeThread.java b/main/java/com/geogle/zxing/decoding/DecodeThread.java
new file mode 100644
index 0000000..4ba3acf
--- /dev/null
+++ b/main/java/com/geogle/zxing/decoding/DecodeThread.java
@@ -0,0 +1,87 @@
+package com.geogle.zxing.decoding;/*
+ * 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.
+ */
+
+
+
+import android.os.Handler;
+import android.os.Looper;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.ResultPointCallback;
+
+import java.util.Hashtable;
+import java.util.Vector;
+import java.util.concurrent.CountDownLatch;
+
+import com.geogle.zxing.activity.CaptureActivity;
+
+/**
+ * This thread does all the heavy lifting of decoding the images.
+ * �����߳�
+ */
+final class DecodeThread extends Thread {
+
+ public static final String BARCODE_BITMAP = "barcode_bitmap";
+ private final CaptureActivity activity;
+ private final Hashtable hints;
+ private Handler handler;
+ private final CountDownLatch handlerInitLatch;
+
+ DecodeThread(CaptureActivity activity,
+ Vector decodeFormats,
+ String characterSet,
+ ResultPointCallback resultPointCallback) {
+
+ this.activity = activity;
+ handlerInitLatch = new CountDownLatch(1);
+
+ hints = new Hashtable(3);
+
+ if (decodeFormats == null || decodeFormats.isEmpty()) {
+ decodeFormats = new Vector();
+ decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
+ decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
+ decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
+ }
+
+ hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
+
+ if (characterSet != null) {
+ hints.put(DecodeHintType.CHARACTER_SET, characterSet);
+ }
+
+ hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
+ }
+
+ 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/main/java/com/geogle/zxing/decoding/FinishListener.java b/main/java/com/geogle/zxing/decoding/FinishListener.java
new file mode 100644
index 0000000..40dc709
--- /dev/null
+++ b/main/java/com/geogle/zxing/decoding/FinishListener.java
@@ -0,0 +1,47 @@
+/*
+ * 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.geogle.zxing.decoding;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+
+/**
+ * Simple listener used to exit the app in a few cases.
+ *
+ */
+public final class FinishListener
+ implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener, Runnable {
+
+ private final Activity activityToFinish;
+
+ public FinishListener(Activity activityToFinish) {
+ this.activityToFinish = activityToFinish;
+ }
+
+ public void onCancel(DialogInterface dialogInterface) {
+ run();
+ }
+
+ public void onClick(DialogInterface dialogInterface, int i) {
+ run();
+ }
+
+ public void run() {
+ activityToFinish.finish();
+ }
+
+}
diff --git a/main/java/com/geogle/zxing/decoding/InactivityTimer.java b/main/java/com/geogle/zxing/decoding/InactivityTimer.java
new file mode 100644
index 0000000..bfc8b20
--- /dev/null
+++ b/main/java/com/geogle/zxing/decoding/InactivityTimer.java
@@ -0,0 +1,71 @@
+/*
+ * 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.geogle.zxing.decoding;
+
+import android.app.Activity;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Finishes an activity after a period of inactivity.
+ */
+public final class InactivityTimer {
+
+ private static final int INACTIVITY_DELAY_SECONDS = 5 * 60;
+
+ private final ScheduledExecutorService inactivityTimer =
+ Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory());
+ private final Activity activity;
+ private ScheduledFuture> inactivityFuture = null;
+
+ public InactivityTimer(Activity activity) {
+ this.activity = activity;
+ onActivity();
+ }
+
+ public void onActivity() {
+ cancel();
+ inactivityFuture = inactivityTimer.schedule(new FinishListener(activity),
+ INACTIVITY_DELAY_SECONDS,
+ TimeUnit.SECONDS);
+ }
+
+ private void cancel() {
+ if (inactivityFuture != null) {
+ inactivityFuture.cancel(true);
+ inactivityFuture = null;
+ }
+ }
+
+ public void shutdown() {
+ cancel();
+ inactivityTimer.shutdown();
+ }
+
+ private static final class DaemonThreadFactory implements ThreadFactory {
+ public Thread newThread(Runnable runnable) {
+ Thread thread = new Thread(runnable);
+ thread.setDaemon(true);
+ return thread;
+ }
+ }
+
+}
diff --git a/main/java/com/geogle/zxing/decoding/Intents.java b/main/java/com/geogle/zxing/decoding/Intents.java
new file mode 100644
index 0000000..afcdd47
--- /dev/null
+++ b/main/java/com/geogle/zxing/decoding/Intents.java
@@ -0,0 +1,190 @@
+/*
+ * 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.geogle.zxing.decoding;
+
+/**
+ * This class provides the constants to use when sending an Intent to Barcode Scanner.
+ * These strings are effectively API and cannot be changed.
+ */
+public final class Intents {
+ private Intents() {
+ }
+
+ public static final class Scan {
+ /**
+ * Send this intent to open the Barcodes app in scanning mode, find a barcode, and return
+ * the results.
+ */
+ public static final String ACTION = "com.google.zxing.client.android.SCAN";
+
+ /**
+ * By default, sending Scan.ACTION will decode all barcodes that we understand. However it
+ * may be useful to limit scanning to certain formats. Use Intent.putExtra(MODE, value) with
+ * one of the values below ({@link #PRODUCT_MODE}, {@link #ONE_D_MODE}, {@link #QR_CODE_MODE}).
+ * Optional.
+ *
+ * Setting this is effectively shorthnad for setting explicit formats with {@link #SCAN_FORMATS}.
+ * It is overridden by that setting.
+ */
+ public static final String MODE = "SCAN_MODE";
+
+ /**
+ * Comma-separated list of formats to scan for. The values must match the names of
+ * {@link com.google.zxing.BarcodeFormat}s, such as {@link com.google.zxing.BarcodeFormat#EAN_13}.
+ * Example: "EAN_13,EAN_8,QR_CODE"
+ *
+ * This overrides {@link #MODE}.
+ */
+ public static final String SCAN_FORMATS = "SCAN_FORMATS";
+
+ /**
+ * @see com.google.zxing.DecodeHintType#CHARACTER_SET
+ */
+ public static final String CHARACTER_SET = "CHARACTER_SET";
+
+ /**
+ * Decode only UPC and EAN barcodes. This is the right choice for shopping apps which get
+ * prices, reviews, etc. for products.
+ */
+ public static final String PRODUCT_MODE = "PRODUCT_MODE";
+
+ /**
+ * Decode only 1D barcodes (currently UPC, EAN, Code 39, and Code 128).
+ */
+ public static final String ONE_D_MODE = "ONE_D_MODE";
+
+ /**
+ * Decode only QR codes.
+ */
+ public static final String QR_CODE_MODE = "QR_CODE_MODE";
+
+ /**
+ * Decode only Data Matrix codes.
+ */
+ public static final String DATA_MATRIX_MODE = "DATA_MATRIX_MODE";
+
+ /**
+ * If a barcode is found, Barcodes returns RESULT_OK to onActivityResult() of the app which
+ * requested the scan via startSubActivity(). The barcodes contents can be retrieved with
+ * intent.getStringExtra(RESULT). If the user presses Back, the result code will be
+ * RESULT_CANCELED.
+ */
+ public static final String RESULT = "SCAN_RESULT";
+
+ /**
+ * Call intent.getStringExtra(RESULT_FORMAT) to determine which barcode format was found.
+ * See Contents.Format for possible values.
+ */
+ public static final String RESULT_FORMAT = "SCAN_RESULT_FORMAT";
+
+ /**
+ * Setting this to false will not save scanned codes in the history.
+ */
+ public static final String SAVE_HISTORY = "SAVE_HISTORY";
+
+ private Scan() {
+ }
+ }
+
+ public static final class Encode {
+ /**
+ * Send this intent to encode a piece of data as a QR code and display it full screen, so
+ * that another person can scan the barcode from your screen.
+ */
+ public static final String ACTION = "com.google.zxing.client.android.ENCODE";
+
+ /**
+ * The data to encode. Use Intent.putExtra(DATA, data) where data is either a String or a
+ * Bundle, depending on the type and format specified. Non-QR Code formats should
+ * just use a String here. For QR Code, see Contents for details.
+ */
+ public static final String DATA = "ENCODE_DATA";
+
+ /**
+ * The type of data being supplied if the format is QR Code. Use
+ * Intent.putExtra(TYPE, type) with one of Contents.Type.
+ */
+ public static final String TYPE = "ENCODE_TYPE";
+
+ /**
+ * The barcode format to be displayed. If this isn't specified or is blank,
+ * it defaults to QR Code. Use Intent.putExtra(FORMAT, format), where
+ * format is one of Contents.Format.
+ */
+ public static final String FORMAT = "ENCODE_FORMAT";
+
+ private Encode() {
+ }
+ }
+
+ public static final class SearchBookContents {
+ /**
+ * Use Google Book Search to search the contents of the book provided.
+ */
+ public static final String ACTION = "com.google.zxing.client.android.SEARCH_BOOK_CONTENTS";
+
+ /**
+ * The book to search, identified by ISBN number.
+ */
+ public static final String ISBN = "ISBN";
+
+ /**
+ * An optional field which is the text to search for.
+ */
+ public static final String QUERY = "QUERY";
+
+ private SearchBookContents() {
+ }
+ }
+
+ public static final class WifiConnect {
+ /**
+ * Internal intent used to trigger connection to a wi-fi network.
+ */
+ public static final String ACTION = "com.google.zxing.client.android.WIFI_CONNECT";
+
+ /**
+ * The network to connect to, all the configuration provided here.
+ */
+ public static final String SSID = "SSID";
+
+ /**
+ * The network to connect to, all the configuration provided here.
+ */
+ public static final String TYPE = "TYPE";
+
+ /**
+ * The network to connect to, all the configuration provided here.
+ */
+ public static final String PASSWORD = "PASSWORD";
+
+ private WifiConnect() {
+ }
+ }
+
+
+ public static final class Share {
+ /**
+ * Give the user a choice of items to encode as a barcode, then render it as a QR Code and
+ * display onscreen for a friend to scan with their phone.
+ */
+ public static final String ACTION = "com.google.zxing.client.android.SHARE";
+
+ private Share() {
+ }
+ }
+}
diff --git a/main/java/com/geogle/zxing/decoding/RGBLuminanceSource.java b/main/java/com/geogle/zxing/decoding/RGBLuminanceSource.java
new file mode 100644
index 0000000..15dddb0
--- /dev/null
+++ b/main/java/com/geogle/zxing/decoding/RGBLuminanceSource.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2009 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.geogle.zxing.decoding;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import com.google.zxing.LuminanceSource;
+
+import java.io.FileNotFoundException;
+
+/**
+ * This class is used to help decode images from files which arrive as RGB data
+ * from Android bitmaps. It does not support cropping or rotation.
+ *
+ */
+public final class RGBLuminanceSource extends LuminanceSource {
+
+ private final byte[] luminances;
+
+ public RGBLuminanceSource(String path) throws FileNotFoundException {
+ this(loadBitmap(path));
+ }
+
+ public RGBLuminanceSource(Bitmap bitmap) {
+ super(bitmap.getWidth(), bitmap.getHeight());
+
+ int width = bitmap.getWidth();
+ int height = bitmap.getHeight();
+
+ int[] pixels = new int[width * height];
+ bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
+
+ // In order to measure pure decoding speed, we convert the entire image
+ // to a greyscale array
+ // up front, which is the same as the Y channel of the
+ // YUVLuminanceSource in the real app.
+ luminances = new byte[width * height];
+ for (int y = 0; y < height; y++) {
+ int offset = y * width;
+ for (int x = 0; x < width; x++) {
+ int pixel = pixels[offset + x];
+ int r = (pixel >> 16) & 0xff;
+ int g = (pixel >> 8) & 0xff;
+ int b = pixel & 0xff;
+ if (r == g && g == b) {
+ // Image is already greyscale, so pick any channel.
+ luminances[offset + x] = (byte) r;
+ } else {
+ // Calculate luminance cheaply, favoring green.
+ luminances[offset + x] = (byte) ((r + g + g + b) >> 2);
+ }
+ }
+ }
+ }
+
+
+
+
+ @Override
+ public byte[] getRow(int y, byte[] row) {
+ if (y < 0 || y >= getHeight()) {
+ throw new IllegalArgumentException("Requested row is outside the image: " + y);
+ }
+ int width = getWidth();
+ if (row == null || row.length < width) {
+ row = new byte[width];
+ }
+
+ System.arraycopy(luminances, y * width, row, 0, width);
+ return row;
+ }
+
+ // Since this class does not support cropping, the underlying byte array
+ // already contains
+ // exactly what the caller is asking for, so give it to them without a copy.
+ @Override
+ public byte[] getMatrix() {
+ return luminances;
+ }
+
+ private static Bitmap loadBitmap(String path) throws FileNotFoundException {
+ Bitmap bitmap = BitmapFactory.decodeFile(path);
+ if (bitmap == null) {
+ throw new FileNotFoundException("Couldn't open " + path);
+ }
+ return bitmap;
+ }
+
+}
diff --git a/main/java/com/geogle/zxing/encoding/EncodingHandler.java b/main/java/com/geogle/zxing/encoding/EncodingHandler.java
new file mode 100644
index 0000000..a0f1956
--- /dev/null
+++ b/main/java/com/geogle/zxing/encoding/EncodingHandler.java
@@ -0,0 +1,132 @@
+package com.geogle.zxing.encoding;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+/**
+ * @author Ryan Tang
+ *
+ */
+public final class EncodingHandler {
+ private static final int BLACK = 0xff000000;
+
+ public static Bitmap createQRCode(String str, int widthAndHeight) throws WriterException {
+ Hashtable hints = new Hashtable();
+ hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
+ BitMatrix matrix = new MultiFormatWriter().encode(str,
+ BarcodeFormat.QR_CODE, widthAndHeight, widthAndHeight);
+ int width = matrix.getWidth();
+ int height = matrix.getHeight();
+ int[] pixels = new int[width * height];
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ if (matrix.get(x, y)) {
+ pixels[y * width + x] = BLACK;
+ }
+ }
+ }
+ Bitmap bitmap = Bitmap.createBitmap(width, height,
+ Bitmap.Config.ARGB_8888);
+ bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
+ return bitmap;
+ }
+
+ /**
+ * 创建二维码
+ *
+ * @param content content
+ * @param widthPix widthPix
+ * @param heightPix heightPix
+ * @param logoBm logoBm
+ * @return 二维码
+ */
+ public static Bitmap createQRCode(String content, int widthPix, int heightPix, Bitmap logoBm) {
+ try {
+ if (content == null || "".equals(content)) {
+ return null;
+ }
+ // 配置参数
+ Map hints = new HashMap<>();
+ hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
+ // 容错级别
+ hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
+ // 图像数据转换,使用了矩阵转换
+ BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, widthPix,
+ heightPix, hints);
+ int[] pixels = new int[widthPix * heightPix];
+ // 下面这里按照二维码的算法,逐个生成二维码的图片,
+ // 两个for循环是图片横列扫描的结果
+ for (int y = 0; y < heightPix; y++) {
+ for (int x = 0; x < widthPix; x++) {
+ if (bitMatrix.get(x, y)) {
+ pixels[y * widthPix + x] = 0xff000000;
+ } else {
+ pixels[y * widthPix + x] = 0xffffffff;
+ }
+ }
+ }
+ // 生成二维码图片的格式,使用ARGB_8888
+ Bitmap bitmap = Bitmap.createBitmap(widthPix, heightPix, Bitmap.Config.ARGB_8888);
+ bitmap.setPixels(pixels, 0, widthPix, 0, 0, widthPix, heightPix);
+ if (logoBm != null) {
+ bitmap = addLogo(bitmap, logoBm);
+ }
+ //必须使用compress方法将bitmap保存到文件中再进行读取。直接返回的bitmap是没有任何压缩的,内存消耗巨大!
+ return bitmap;
+ } catch (WriterException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * 在二维码中间添加Logo图案
+ */
+ private static Bitmap addLogo(Bitmap src, Bitmap logo) {
+ if (src == null) {
+ return null;
+ }
+ if (logo == null) {
+ return src;
+ }
+ //获取图片的宽高
+ int srcWidth = src.getWidth();
+ int srcHeight = src.getHeight();
+ int logoWidth = logo.getWidth();
+ int logoHeight = logo.getHeight();
+ if (srcWidth == 0 || srcHeight == 0) {
+ return null;
+ }
+ if (logoWidth == 0 || logoHeight == 0) {
+ return src;
+ }
+ //logo大小为二维码整体大小的1/5
+ float scaleFactor = srcWidth * 1.0f / 5 / logoWidth;
+ Bitmap bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888);
+ try {
+ Canvas canvas = new Canvas(bitmap);
+ canvas.drawBitmap(src, 0, 0, null);
+ canvas.scale(scaleFactor, scaleFactor, srcWidth / 2, srcHeight / 2);
+ canvas.drawBitmap(logo, (srcWidth - logoWidth) / 2, (srcHeight - logoHeight) / 2, null);
+ canvas.save();
+ canvas.restore();
+ } catch (Exception e) {
+ bitmap = null;
+ e.getStackTrace();
+ }
+ return bitmap;
+ }
+}
diff --git a/main/java/com/geogle/zxing/view/ViewfinderResultPointCallback.java b/main/java/com/geogle/zxing/view/ViewfinderResultPointCallback.java
new file mode 100644
index 0000000..032ff63
--- /dev/null
+++ b/main/java/com/geogle/zxing/view/ViewfinderResultPointCallback.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2009 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.geogle.zxing.view;
+
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+
+public final class ViewfinderResultPointCallback implements ResultPointCallback {
+ private final ViewfinderView viewfinderView;
+
+ public ViewfinderResultPointCallback(ViewfinderView viewfinderView) {
+ this.viewfinderView = viewfinderView;
+ }
+
+ public void foundPossibleResultPoint(ResultPoint point) {
+ viewfinderView.addPossibleResultPoint(point);
+ }
+
+}
diff --git a/main/java/com/geogle/zxing/view/ViewfinderView.java b/main/java/com/geogle/zxing/view/ViewfinderView.java
new file mode 100644
index 0000000..173b6d4
--- /dev/null
+++ b/main/java/com/geogle/zxing/view/ViewfinderView.java
@@ -0,0 +1,286 @@
+/*
+ * 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.geogle.zxing.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ComposeShader;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.util.AttributeSet;
+import android.view.View;
+
+
+import com.google.zxing.ResultPoint;
+import com.geogle.zxing.camera.CameraManager;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import edu.hzuapps.androidlabs.R;
+
+
+/**
+ * This view is overlaid on top of the camera preview. It adds the viewfinder rectangle and partial
+ * transparency outside it, as well as the laser scanner animation and result points.
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class ViewfinderView extends View {
+
+ private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
+ private static final long ANIMATION_DELAY = 10L;
+ private static final int OPAQUE = 0xFF;
+ private static final int CORNER_RECT_WIDTH = 8; //扫描区边角的宽
+ private static final int CORNER_RECT_HEIGHT = 40; //扫描区边角的高
+ private static final int SCANNER_LINE_MOVE_DISTANCE = 5; //扫描线移动距离
+ private static final int SCANNER_LINE_HEIGHT = 10; //扫描线宽度
+
+ private final Paint paint;
+ private Bitmap resultBitmap;
+ //模糊区域颜色
+ private final int maskColor;
+ private final int resultColor;
+ //扫描区域边框颜色
+ private final int frameColor;
+ //扫描线颜色
+ private final int laserColor;
+ //四角颜色
+ private final int cornerColor;
+ //扫描点的颜色
+ private final int resultPointColor;
+ private int scannerAlpha;
+ //扫描区域提示文本
+ private final String labelText;
+ //扫描区域提示文本颜色
+ private final int labelTextColor;
+ private final float labelTextSize;
+
+ public static int scannerStart = 0;
+ public static int scannerEnd = 0;
+
+ private Collection possibleResultPoints;
+ private Collection lastPossibleResultPoints;
+
+ // This constructor is used when the class is built from an XML resource.
+ public ViewfinderView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ //初始化自定义属性信息
+ TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ViewfinderView);
+ laserColor = array.getColor(R.styleable.ViewfinderView_laser_color, 0x00FF00);
+ cornerColor = array.getColor(R.styleable.ViewfinderView_corner_color, 0x00FF00);
+ frameColor = array.getColor(R.styleable.ViewfinderView_frame_color, 0xFFFFFF);
+ resultPointColor = array.getColor(R.styleable.ViewfinderView_result_point_color, 0xC0FFFF00);
+ maskColor = array.getColor(R.styleable.ViewfinderView_mask_color, 0x60000000);
+ resultColor = array.getColor(R.styleable.ViewfinderView_result_color, 0xB0000000);
+ labelTextColor = array.getColor(R.styleable.ViewfinderView_label_text_color, 0x90FFFFFF);
+ labelText = array.getString(R.styleable.ViewfinderView_label_text);
+ labelTextSize = array.getFloat(R.styleable.ViewfinderView_label_text_size, 36f);
+
+ // Initialize these once for performance rather than calling them every time in onDraw().
+ paint = new Paint();
+ paint.setAntiAlias(true);
+ scannerAlpha = 0;
+ possibleResultPoints = new HashSet(5);
+
+
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ Rect frame = CameraManager.get().getFramingRect();
+ if (frame == null) {
+ return;
+ }
+ if(scannerStart == 0 || scannerEnd == 0) {
+ scannerStart = frame.top;
+ scannerEnd = frame.bottom;
+ }
+
+ int width = canvas.getWidth();
+ int height = canvas.getHeight();
+ // Draw the exterior (i.e. outside the framing rect) darkened
+ drawExterior(canvas, frame, width, height);
+
+
+ if (resultBitmap != null) {
+ // Draw the opaque result bitmap over the scanning rectangle
+ paint.setAlpha(OPAQUE);
+ canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint);
+ } else {
+ // Draw a two pixel solid black border inside the framing rect
+ drawFrame(canvas, frame);
+ // 绘制边角
+ drawCorner(canvas, frame);
+ //绘制提示信息
+ drawTextInfo(canvas, frame);
+ // Draw a red "laser scanner" line through the middle to show decoding is active
+ drawLaserScanner(canvas, frame);
+
+ Collection currentPossible = possibleResultPoints;
+ Collection currentLast = lastPossibleResultPoints;
+ if (currentPossible.isEmpty()) {
+ lastPossibleResultPoints = null;
+ } else {
+ possibleResultPoints = new HashSet(5);
+ lastPossibleResultPoints = currentPossible;
+ paint.setAlpha(OPAQUE);
+ paint.setColor(resultPointColor);
+ for (ResultPoint point : currentPossible) {
+ canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 6.0f, paint);
+ }
+ }
+ if (currentLast != null) {
+ paint.setAlpha(OPAQUE / 2);
+ paint.setColor(resultPointColor);
+ for (ResultPoint point : currentLast) {
+ canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 3.0f, paint);
+ }
+ }
+
+ // Request another update at the animation interval, but only repaint the laser line,
+ // not the entire viewfinder mask.
+ //指定重绘区域,该方法会在子线程中执行
+ postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom);
+ }
+ }
+
+ //绘制文本
+ private void drawTextInfo(Canvas canvas, Rect frame) {
+ paint.setColor(labelTextColor);
+ paint.setTextSize(labelTextSize);
+ paint.setTextAlign(Paint.Align.CENTER);
+ canvas.drawText(labelText, frame.left + frame.width() / 2, frame.bottom + CORNER_RECT_HEIGHT * 1.5f, paint);
+ }
+
+
+ //绘制边角
+ private void drawCorner(Canvas canvas, Rect frame) {
+ paint.setColor(cornerColor);
+ //左上
+ canvas.drawRect(frame.left, frame.top, frame.left + CORNER_RECT_WIDTH, frame.top + CORNER_RECT_HEIGHT, paint);
+ canvas.drawRect(frame.left, frame.top, frame.left + CORNER_RECT_HEIGHT, frame.top + CORNER_RECT_WIDTH, paint);
+ //右上
+ canvas.drawRect(frame.right - CORNER_RECT_WIDTH, frame.top, frame.right, frame.top + CORNER_RECT_HEIGHT, paint);
+ canvas.drawRect(frame.right - CORNER_RECT_HEIGHT, frame.top, frame.right, frame.top + CORNER_RECT_WIDTH, paint);
+ //左下
+ canvas.drawRect(frame.left, frame.bottom - CORNER_RECT_WIDTH, frame.left + CORNER_RECT_HEIGHT, frame.bottom, paint);
+ canvas.drawRect(frame.left, frame.bottom - CORNER_RECT_HEIGHT, frame.left + CORNER_RECT_WIDTH, frame.bottom, paint);
+ //右下
+ canvas.drawRect(frame.right - CORNER_RECT_WIDTH, frame.bottom - CORNER_RECT_HEIGHT, frame.right, frame.bottom, paint);
+ canvas.drawRect(frame.right - CORNER_RECT_HEIGHT, frame.bottom - CORNER_RECT_WIDTH, frame.right, frame.bottom, paint);
+ }
+
+ //绘制扫描线
+ private void drawLaserScanner(Canvas canvas, Rect frame) {
+ paint.setColor(laserColor);
+ //扫描线闪烁效果
+// paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
+// scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
+// int middle = frame.height() / 2 + frame.top;
+// canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint);
+ //线性渐变
+ LinearGradient linearGradient = new LinearGradient(
+ frame.left, scannerStart,
+ frame.left, scannerStart + SCANNER_LINE_HEIGHT,
+ shadeColor(laserColor),
+ laserColor,
+ Shader.TileMode.MIRROR);
+
+ RadialGradient radialGradient = new RadialGradient(
+ (float)(frame.left + frame.width() / 2),
+ (float)(scannerStart + SCANNER_LINE_HEIGHT / 2),
+ 360f,
+ laserColor,
+ shadeColor(laserColor),
+ Shader.TileMode.MIRROR);
+
+ SweepGradient sweepGradient = new SweepGradient(
+ (float)(frame.left + frame.width() / 2),
+ (float)(scannerStart + SCANNER_LINE_HEIGHT),
+ shadeColor(laserColor),
+ laserColor);
+
+ ComposeShader composeShader = new ComposeShader(radialGradient, linearGradient, PorterDuff.Mode.ADD);
+
+ paint.setShader(radialGradient);
+ if(scannerStart <= scannerEnd) {
+ //矩形
+// canvas.drawRect(frame.left, scannerStart, frame.right, scannerStart + SCANNER_LINE_HEIGHT, paint);
+ //椭圆
+ RectF rectF = new RectF(frame.left + 2 * SCANNER_LINE_HEIGHT, scannerStart, frame.right - 2 * SCANNER_LINE_HEIGHT, scannerStart + SCANNER_LINE_HEIGHT);
+ canvas.drawOval(rectF, paint);
+ scannerStart += SCANNER_LINE_MOVE_DISTANCE;
+ } else {
+ scannerStart = frame.top;
+ }
+ paint.setShader(null);
+ }
+
+ //处理颜色模糊
+ public int shadeColor(int color) {
+ String hax = Integer.toHexString(color);
+ String result = "20"+hax.substring(2);
+ return Integer.valueOf(result, 16);
+ }
+
+ // 绘制扫描区边框 Draw a two pixel solid black border inside the framing rect
+ private void drawFrame(Canvas canvas, Rect frame) {
+ paint.setColor(frameColor);
+ canvas.drawRect(frame.left, frame.top, frame.right + 1, frame.top + 2, paint);
+ canvas.drawRect(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1, paint);
+ canvas.drawRect(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1, paint);
+ canvas.drawRect(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1, paint);
+ }
+
+ // 绘制模糊区域 Draw the exterior (i.e. outside the framing rect) darkened
+ private void drawExterior(Canvas canvas, Rect frame, int width, int height) {
+ paint.setColor(resultBitmap != null ? resultColor : maskColor);
+ canvas.drawRect(0, 0, width, frame.top, paint);
+ canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
+ canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
+ canvas.drawRect(0, frame.bottom + 1, width, height, paint);
+ }
+
+ public void drawViewfinder() {
+ resultBitmap = null;
+ invalidate();
+ }
+
+ /**
+ * Draw a bitmap with the result points highlighted instead of the live scanning display.
+ *
+ * @param barcode An image of the decoded barcode.
+ */
+ public void drawResultBitmap(Bitmap barcode) {
+ resultBitmap = barcode;
+ invalidate();
+ }
+
+ public void addPossibleResultPoint(ResultPoint point) {
+ possibleResultPoints.add(point);
+ }
+
+}
diff --git a/main/java/edu/hzuapps/androidlabs/MainActivity.java b/main/java/edu/hzuapps/androidlabs/MainActivity.java
new file mode 100644
index 0000000..fb58aa9
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/MainActivity.java
@@ -0,0 +1,14 @@
+package edu.hzuapps.androidlabs;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.os.Bundle;
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ }
+}
\ No newline at end of file
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/CollectFragment.java b/main/java/edu/hzuapps/androidlabs/watchtv/CollectFragment.java
new file mode 100644
index 0000000..b308b4c
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/CollectFragment.java
@@ -0,0 +1,88 @@
+package edu.hzuapps.androidlabs.watchtv;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.fragment.app.Fragment;
+
+import android.os.Parcelable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import edu.hzuapps.androidlabs.R;
+import edu.hzuapps.androidlabs.watchtv.room.Programs;
+
+
+public class CollectFragment extends Fragment {
+
+ ListView listView;
+ View rootView;
+ List dataList;
+ ArrayList tagList;
+
+ public CollectFragment() {
+ // Required empty public constructor
+ }
+
+ public static CollectFragment newInstances(List list, ArrayList tag){
+ CollectFragment collectFragment = new CollectFragment();
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList("data", (ArrayList extends Parcelable>) list);
+ bundle.putStringArrayList("tag",tag);;
+ collectFragment.setArguments(bundle);
+ return collectFragment;
+ }
+
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if(getArguments()!=null){
+ dataList = getArguments().getParcelableArrayList("data");
+ tagList = getArguments().getStringArrayList("tag");
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ if(rootView == null){
+ rootView = inflater.inflate(R.layout.fragment_collect, container, false);
+ }
+ // Inflate the layout for this fragment
+ initview();
+ return rootView;
+ }
+
+ private void initview() {
+ listView = rootView.findViewById(R.id.lv_home);
+ listView.setAdapter(new MyBaseAdapter(dataList,tagList,this.getContext()));
+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ String str = dataList.get(position).getAddr();
+ ClipboardManager cm;
+ ClipData mClipData;
+ //获取剪贴板管理器:
+ cm = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
+ // 创建普通字符型ClipData
+ mClipData = ClipData.newPlainText("Label", str);
+ // 将ClipData内容放到系统剪贴板里。
+ cm.setPrimaryClip(mClipData);
+ Toast.makeText(getContext(), "视频地址已复制到剪切板",
+ Toast.LENGTH_SHORT).show();
+
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/HomeFragment.java b/main/java/edu/hzuapps/androidlabs/watchtv/HomeFragment.java
new file mode 100644
index 0000000..bde221c
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/HomeFragment.java
@@ -0,0 +1,108 @@
+package edu.hzuapps.androidlabs.watchtv;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+
+import androidx.annotation.RequiresApi;
+import androidx.fragment.app.Fragment;
+
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.MediaController;
+import android.widget.RelativeLayout;
+import android.widget.VideoView;
+
+import edu.hzuapps.androidlabs.R;
+import edu.hzuapps.androidlabs.watchtv.until.ToolUtils;
+
+import static android.content.Context.WINDOW_SERVICE;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link HomeFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class HomeFragment extends Fragment implements View.OnClickListener {
+
+ View rootView;
+ protected Button play;
+ protected VideoView videoView;
+ protected EditText edittext;
+ protected Button newplayer;
+
+
+
+ public HomeFragment() {
+ // Required empty public constructor
+ }
+
+
+ public static HomeFragment newInstance() {
+ HomeFragment fragment = new HomeFragment();
+
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ if(rootView == null){
+ rootView = inflater.inflate(R.layout.fragment_home, container, false);
+ }
+ edittext = rootView.findViewById(R.id.et_home);
+ play = rootView.findViewById(R.id.bt_play);
+ videoView = rootView.findViewById(R.id.vv_home);
+ videoView.setMediaController(new MediaController(rootView.getContext()));
+ videoView.setVisibility(View.INVISIBLE);
+ play.setOnClickListener(this);
+ newplayer = rootView.findViewById(R.id.newplayer);
+ newplayer.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(getContext(),MovieActivity.class);
+ intent.setData(Uri.parse(edittext.getText().toString()));
+ startActivity(intent);
+ }
+ });
+
+
+
+
+ return rootView;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if(videoView!=null && videoView.isPlaying()){
+ videoView.stopPlayback();
+ }
+ videoView.setVideoURI(Uri.parse(edittext.getText().toString()));
+ videoView.setVisibility(View.VISIBLE);
+ videoView.start();
+ }
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/MovieActivity.java b/main/java/edu/hzuapps/androidlabs/watchtv/MovieActivity.java
new file mode 100644
index 0000000..e4c01ae
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/MovieActivity.java
@@ -0,0 +1,65 @@
+package edu.hzuapps.androidlabs.watchtv;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.MediaController;
+import android.widget.RelativeLayout;
+import android.widget.VideoView;
+
+import edu.hzuapps.androidlabs.R;
+
+public class MovieActivity extends AppCompatActivity {
+
+ VideoView videoView ;
+ EditText editText;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_movie);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.hide(); //隐藏标题栏
+ }
+
+ videoView = findViewById(R.id.videoview);
+ editText = findViewById(R.id.et_home);
+ videoView.setMediaController(new MediaController(this));
+ videoView.setVideoURI(getIntent().getData());
+ videoView.start();
+ }
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ videoView = findViewById(R.id.videoview);
+ if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,dip2px(this,235f));
+ params.addRule(RelativeLayout.CENTER_IN_PARENT);
+ videoView.setLayoutParams(params);
+ } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+ videoView.setLayoutParams(new RelativeLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT));
+ }
+ }
+
+ /**
+ * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
+ */
+ public static int dip2px(Context context, float dpValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+
+ }
+}
\ No newline at end of file
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/MyBaseAdapter.java b/main/java/edu/hzuapps/androidlabs/watchtv/MyBaseAdapter.java
new file mode 100644
index 0000000..01cae42
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/MyBaseAdapter.java
@@ -0,0 +1,56 @@
+package edu.hzuapps.androidlabs.watchtv;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import java.util.List;
+
+import edu.hzuapps.androidlabs.R;
+import edu.hzuapps.androidlabs.watchtv.room.Programs;
+
+public class MyBaseAdapter extends BaseAdapter {
+
+ private List data;
+ private List datatag;
+ private Context context;
+
+ public MyBaseAdapter(List data,List datatag, Context context) {
+ this.data = data;
+ this.datatag = datatag;
+ this.context = context;
+ }
+
+ @Override
+ public int getCount() {
+ return data.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ if(datatag.contains(data.get(position).getName())){
+ convertView = LayoutInflater.from(context).inflate(R.layout.list_item_tag, parent, false);
+ }else{
+ convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
+ }
+
+ }
+ TextView textview = convertView.findViewById(R.id.tv_item);
+ textview.setText(data.get(position).getName());
+ return convertView;
+ }
+}
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/MyFragmentAdapter.java b/main/java/edu/hzuapps/androidlabs/watchtv/MyFragmentAdapter.java
new file mode 100644
index 0000000..d105a7a
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/MyFragmentAdapter.java
@@ -0,0 +1,31 @@
+package edu.hzuapps.androidlabs.watchtv;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.Lifecycle;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MyFragmentAdapter extends FragmentStateAdapter {
+
+ List list = new ArrayList<>();
+
+ public MyFragmentAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle,List flist) {
+ super(fragmentManager, lifecycle);
+ list = flist;
+ }
+
+ @NonNull
+ @Override
+ public Fragment createFragment(int position) {
+ return list.get(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return list.size();
+ }
+}
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/MyVideoView.java b/main/java/edu/hzuapps/androidlabs/watchtv/MyVideoView.java
new file mode 100644
index 0000000..e3259c4
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/MyVideoView.java
@@ -0,0 +1,32 @@
+package edu.hzuapps.androidlabs.watchtv;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.VideoView;
+
+public class MyVideoView extends VideoView {
+ public MyVideoView(Context context) {
+ super(context);
+ }
+
+ public MyVideoView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int width = getDefaultSize(0, widthMeasureSpec);
+ int height = getDefaultSize(0, heightMeasureSpec);
+ setMeasuredDimension(width,height);
+
+ }
+
+
+}
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/SelectActivity.java b/main/java/edu/hzuapps/androidlabs/watchtv/SelectActivity.java
new file mode 100644
index 0000000..9a1c9ee
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/SelectActivity.java
@@ -0,0 +1,35 @@
+package edu.hzuapps.androidlabs.watchtv;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+
+import edu.hzuapps.androidlabs.R;
+
+public class SelectActivity extends AppCompatActivity implements View.OnClickListener {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_select);
+
+ Button back = findViewById(R.id.btn_back);
+ back.setOnClickListener(this);
+ Button search = findViewById(R.id.btn_search);
+ search.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()){
+ case R.id.btn_search:
+ break;
+ case R.id.btn_back:
+ finish();
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/UserFragment.java b/main/java/edu/hzuapps/androidlabs/watchtv/UserFragment.java
new file mode 100644
index 0000000..b6b9e0c
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/UserFragment.java
@@ -0,0 +1,59 @@
+package edu.hzuapps.androidlabs.watchtv;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.Fragment;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.bitmap.CircleCrop;
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
+import com.bumptech.glide.request.RequestOptions;
+
+import edu.hzuapps.androidlabs.R;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link UserFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class UserFragment extends Fragment {
+
+ View rootview;
+
+
+ public UserFragment() {
+ // Required empty public constructor
+ }
+
+
+ public static UserFragment newInstance() {
+ UserFragment fragment = new UserFragment();
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ if(rootview == null){
+ rootview = inflater.inflate(R.layout.fragment_user, container, false);
+ }
+ ImageView imageView = rootview.findViewById(R.id.imageView);
+ Glide.with(this)
+ .load(R.drawable.myphoto)
+ .apply(RequestOptions.bitmapTransform(new RoundedCorners(180)))
+ .into(imageView);
+ // Inflate the layout for this fragment
+ return rootview;
+ }
+}
\ No newline at end of file
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/WatchTVActivity.java b/main/java/edu/hzuapps/androidlabs/watchtv/WatchTVActivity.java
new file mode 100644
index 0000000..7d1e95e
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/WatchTVActivity.java
@@ -0,0 +1,300 @@
+package edu.hzuapps.androidlabs.watchtv;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.widget.ViewPager2;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.MediaController;
+import android.widget.RelativeLayout;
+import android.widget.Toast;
+import android.widget.VideoView;
+
+import com.dommy.qrcode.util.Constant;
+import com.geogle.zxing.activity.CaptureActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import edu.hzuapps.androidlabs.R;
+import edu.hzuapps.androidlabs.watchtv.room.Programs;
+import edu.hzuapps.androidlabs.watchtv.room.manager.DBEngine;
+
+public class WatchTVActivity extends AppCompatActivity implements View.OnClickListener{
+
+ private static final String TAG = "zhu";
+ final WatchTVActivity thisActivity = this;
+ private List data = null;
+ private ArrayList datatag = null;
+ private Context thiscontext = this;
+
+ protected ViewPager2 viewPager;
+ protected LinearLayout lhome,lstar,lacc;
+ protected ImageView ivhome,ivstar,ivacc,ivcurr;
+ protected Button btnSearch,btnScanner,btnMore;
+
+
+
+
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_watch_t_v);
+
+ ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ 100);
+
+ DBEngine db = new DBEngine(thiscontext);
+
+ // dataInit();
+ // data = dataQuery();
+ dataInit();
+ initPage();
+ initMoudle();
+
+
+ }
+
+ private List dataQuery() {
+ List list = new ArrayList();
+// Resources resources = getResources();
+// String[] name = resources.getStringArray(R.array.name);
+// String[] url = resources.getStringArray(R.array.addr);
+// for (int i = 0; i < name.length; i++) {
+// Programs programs = new Programs();
+// programs.setName(name[i]);
+// programs.setAddr(url[i]);
+// programs.setId(i);
+// list.add(programs);
+// }
+// DBEngine db = new DBEngine(this);
+// List list = db.queryAllPrograms();
+
+
+ return list;
+ }
+
+ private void dataInit() {
+
+ data = new ArrayList<>();
+ datatag = new ArrayList();
+ Resources resources = getResources();
+ String[] url = resources.getStringArray(R.array.addr);
+ datatag.add("新闻");
+ Programs p = new Programs();
+ p.setName("新闻");
+ data.add(p);
+ int index = 0;
+ for (int i = 0; i < 5; i++) {
+ Programs programs = new Programs();
+ programs.setName("新闻"+(i+1));
+ programs.setAddr(url[index]);
+ programs.setId(index++);
+ data.add(programs);
+ }
+
+ datatag.add("娱乐");
+ p = new Programs();
+ p.setName("娱乐");
+ data.add(p);
+ for (int i = 0; i < 5; i++) {
+ Programs programs = new Programs();
+ programs.setName("娱乐"+(i+1));
+ programs.setAddr(url[index]);
+ programs.setId(index++);
+ data.add(programs);
+ }
+ datatag.add("自然");
+ p = new Programs();
+ p.setName("自然");
+ data.add(p);
+ for (int i = 0; i < 5; i++) {
+ Programs programs = new Programs();
+ programs.setName("自然"+(i+1));
+ programs.setAddr(url[index]);
+ programs.setId(index++);
+ data.add(programs);
+ }
+ }
+
+ private void initPage(){
+ viewPager = findViewById(R.id.vp_mid);
+ ArrayList fragments= new ArrayList<>();
+ fragments.add(HomeFragment.newInstance());
+ fragments.add(CollectFragment.newInstances(data,datatag));
+ fragments.add(UserFragment.newInstance());
+ viewPager.setAdapter(new MyFragmentAdapter(getSupportFragmentManager(),getLifecycle(),fragments));
+ viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ super.onPageScrolled(position, positionOffset, positionOffsetPixels);
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ super.onPageSelected(position);
+ changTab(position);
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ super.onPageScrollStateChanged(state);
+ }
+ });
+ }
+
+
+
+ private void initMoudle(){
+ lhome = findViewById(R.id.part_home);
+ lstar = findViewById(R.id.part_collect);
+ lacc = findViewById(R.id.ll_friend);
+ ivhome = findViewById(R.id.iv_home);
+ ivstar = findViewById(R.id.iv_collect);
+ ivacc = findViewById(R.id.iv_user);
+ btnSearch = findViewById(R.id.btn_search);
+ btnScanner = findViewById(R.id.btn_scanner);
+ btnMore = findViewById(R.id.btn_more);
+
+ lhome.setOnClickListener(this);
+ lstar.setOnClickListener(this);
+ lacc.setOnClickListener(this);
+ btnSearch.setOnClickListener(this);
+ btnScanner.setOnClickListener(this);
+ btnMore.setOnClickListener(this);
+
+ ivcurr = ivhome;
+ ivhome.setSelected(true);
+ }
+
+ private void changTab(int position) {
+ ivcurr.setSelected(false);
+ switch (position){
+ case R.id.part_home:
+ viewPager.setCurrentItem(0);
+ case 0:
+ ivhome.setSelected(true);
+ ivcurr = ivhome;
+ break;
+ case R.id.part_collect:
+ viewPager.setCurrentItem(1);
+ case 1:
+ ivstar.setSelected(true);
+ ivcurr = ivstar;
+ break;
+ case R.id.ll_friend:
+ case R.id.btn_more:
+ viewPager.setCurrentItem(2);
+ case 2:
+ ivacc.setSelected(true);
+ ivcurr = ivacc;
+ break;
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()){
+ case R.id.btn_more:
+ case R.id.part_home:
+ case R.id.part_collect:
+ case R.id.ll_friend:
+ changTab(v.getId());
+ break;
+ case R.id.btn_search:
+ Intent intent = new Intent(thisActivity, SelectActivity.class);
+ thisActivity.startActivity(intent);
+ break;
+ case R.id.btn_scanner:
+ startQrCode();
+ break;
+ }
+
+ }
+
+ // 开始扫码
+ private void startQrCode() {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+ if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
+ Toast.makeText(WatchTVActivity.this, "请至权限中心打开本应用的相机访问权限", Toast.LENGTH_LONG).show();
+ }
+ // 申请权限
+ ActivityCompat.requestPermissions(WatchTVActivity.this, new String[]{Manifest.permission.CAMERA}, Constant.REQ_PERM_CAMERA);
+ return;
+ }
+ // 二维码扫码
+ Intent intent = new Intent(WatchTVActivity.this, CaptureActivity.class);
+ startActivityForResult(intent, Constant.REQ_QR_CODE);
+ }
+
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ String result = "";
+ //扫描结果回调
+ if (requestCode == Constant.REQ_QR_CODE && resultCode == RESULT_OK) {
+ if (data != null) {
+ Bundle bundle = data.getExtras();
+ if (bundle == null) {
+ return;
+ }
+ result = bundle.getString(Constant.INTENT_EXTRA_KEY_QR_SCAN);
+ }
+ }
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(result)); //splitflowurl为分流地址
+ if (!hasPreferredApplication(this,intent)){
+ intent.setClassName("com.android.browser", "com.android.browser.BrowserActivity");
+ }
+ startActivity(intent);
+
+ }
+
+ public static boolean hasPreferredApplication(Context context, Intent intent) {
+ PackageManager pm = context.getPackageManager();
+ ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ return !"android".equals(info.activityInfo.packageName);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ switch (requestCode) {
+ case Constant.REQ_PERM_CAMERA:
+ // 摄像头权限申请
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // 获得授权
+ startQrCode();
+ } else {
+ // 被禁止授权
+ Toast.makeText(WatchTVActivity.this, "请至权限中心打开本应用的相机访问权限", Toast.LENGTH_LONG).show();
+ }
+ break;
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/room/Programs.java b/main/java/edu/hzuapps/androidlabs/watchtv/room/Programs.java
new file mode 100644
index 0000000..e44181c
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/room/Programs.java
@@ -0,0 +1,93 @@
+package edu.hzuapps.androidlabs.watchtv.room;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.room.Entity;
+import androidx.room.Ignore;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class Programs implements Parcelable {
+
+ @PrimaryKey(autoGenerate = true)
+ private int id;
+ private String name;
+ private String addr;
+
+ @Ignore
+ public Programs() {
+ }
+
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAddr() {
+ return addr;
+ }
+
+ public void setAddr(String addr) {
+ this.addr = addr;
+ }
+
+ public Programs(int id, String name, String addr) {
+ this.id = id;
+ this.name = name;
+ this.addr = addr;
+ }
+
+ @Override
+ public String toString() {
+ return "Programs{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ ", addr='" + addr + '\'' +
+ '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(id);
+ dest.writeString(name);
+ dest.writeString(addr);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Creator(){
+
+ @Override
+ public Programs createFromParcel(Parcel source) {
+ // TODO Auto-generated method stub
+ // 必须按成员变量声明的顺序读取数据,不然会出现获取数据出错
+ Programs p = new Programs();
+ p.setId(source.readInt());
+ p.setName(source.readString());
+ p.setAddr(source.readString());
+ return p;
+ }
+
+ @Override
+ public Programs[] newArray(int size) {
+ // TODO Auto-generated method stub
+ return new Programs[size];
+ }
+ };
+}
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/room/ProgramsDao.java b/main/java/edu/hzuapps/androidlabs/watchtv/room/ProgramsDao.java
new file mode 100644
index 0000000..296edb3
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/room/ProgramsDao.java
@@ -0,0 +1,16 @@
+package edu.hzuapps.androidlabs.watchtv.room;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
+
+import java.util.List;
+
+@Dao
+public interface ProgramsDao {
+ @Insert
+ void insertPrograms(Programs... programs);
+
+ @Query("SELECT * FROM programs")
+ List queryAllPrograms();
+}
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/room/ProgramsDatabase.java b/main/java/edu/hzuapps/androidlabs/watchtv/room/ProgramsDatabase.java
new file mode 100644
index 0000000..ad39fce
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/room/ProgramsDatabase.java
@@ -0,0 +1,24 @@
+package edu.hzuapps.androidlabs.watchtv.room;
+
+import android.content.Context;
+
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+@Database(entities = {Programs.class},version = 1,exportSchema = false)
+public abstract class ProgramsDatabase extends RoomDatabase {
+
+ public abstract ProgramsDao getProgramsDao();
+
+ private static ProgramsDatabase INSTANCE;
+ public static synchronized ProgramsDatabase getINSTANCE(Context context){
+ if(INSTANCE == null){
+ INSTANCE = Room.databaseBuilder
+ (context.getApplicationContext(),ProgramsDatabase.class,"programe_database")
+ .allowMainThreadQueries()
+ .build();
+ }
+ return INSTANCE;
+ }
+}
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/room/manager/DBEngine.java b/main/java/edu/hzuapps/androidlabs/watchtv/room/manager/DBEngine.java
new file mode 100644
index 0000000..705cf35
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/room/manager/DBEngine.java
@@ -0,0 +1,49 @@
+package edu.hzuapps.androidlabs.watchtv.room.manager;
+
+import android.content.Context;
+
+import java.util.List;
+
+import edu.hzuapps.androidlabs.watchtv.room.Programs;
+import edu.hzuapps.androidlabs.watchtv.room.ProgramsDao;
+import edu.hzuapps.androidlabs.watchtv.room.ProgramsDatabase;
+
+public class DBEngine{
+
+ private ProgramsDao programsDao;
+ private List list;
+ private String teststr;
+
+
+ public DBEngine(Context context) {
+ ProgramsDatabase programsDatabase = ProgramsDatabase.getINSTANCE(context);
+ programsDao = programsDatabase.getProgramsDao();
+ }
+
+ public void insertPrograms(Programs... programs) {
+ programsDao.insertPrograms(programs);
+ }
+
+
+ public List queryAllPrograms() {
+ List list = programsDao.queryAllPrograms();
+ return list;
+ }
+
+// private static class InsertAsyncTask extends AsyncTask{
+//
+// private ProgramsDao dao;
+//
+// public InsertAsyncTask(ProgramsDao dao) {
+// this.dao = dao;
+// }
+//
+// @Override
+// protected Void doInBackground(Programs... programs) {
+// dao.insertPrograms(programs);
+// return null;
+// }
+// }
+
+
+}
diff --git a/main/java/edu/hzuapps/androidlabs/watchtv/until/ToolUtils.java b/main/java/edu/hzuapps/androidlabs/watchtv/until/ToolUtils.java
new file mode 100644
index 0000000..95a80bf
--- /dev/null
+++ b/main/java/edu/hzuapps/androidlabs/watchtv/until/ToolUtils.java
@@ -0,0 +1,13 @@
+package edu.hzuapps.androidlabs.watchtv.until;
+
+import androidx.fragment.app.FragmentActivity;
+
+public class ToolUtils {
+
+ public static int dip2px(FragmentActivity context, float dpValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+ }
+
+
+}
diff --git a/main/res/drawable-v24/ic_launcher_foreground.xml b/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..cc14f03
--- /dev/null
+++ b/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main/res/drawable-v24/logo.jpg b/main/res/drawable-v24/logo.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..077f572baf11c7762ce2d34aee9f505f8383bd3b
GIT binary patch
literal 28498
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
z
z%|`)<0MF_Ns%H5P=NoR=PHR0NbY4A+9qfVDIY7$fqdHiTB%ZaL^)>MY*(@4>14#LS
z_UzwT7tq1CwYC7ct+d6Gl!cT}U4eH1{T^~6MFNx2V5(6S{K_Cx8(n$4^UDFClsTZi
z^%Kh5n)g}JX#ksd=^;GN%%5`;=nkj9nkYahtQB1)I?l6L>#KFQW?Tw0;V)sU^;KJ8
zn-53Do6mjNE%oBN-R;ru3ohcLmLk%Q+wm?2tm-e_5&9#YxJX=UN?6=r%6QoqVsdK|
ztc}w)&RIl4{eU(qR2fX$SdfF0>;n4~0O2JHc1zMVc`tjSnLv!ld85B<1i;tqi(7NS
z0xZ&k;%&SUZ=>Wo=vBpkTmHx0t{=bFPnCSy6HI6YK!t1>N!&WXx<uHquvu8#pl
zCd}n-;7B=qc-%Z^?yJVTtWq(0abtEc8VIzx`8!w(PmPuPBUe{9`{U$5l$jsUjh|~f
zNLfv+leGYp=Zzpx(U~3Vd&C&(anTq7d>HVgj-EsYceq)ah2$xxq(G$SZzDD|O{I_t
z_dmr09}z#+a5V42D|P$xS#(#f8aP6A=Cm`E)(lAf=Q0|N<7HY~Sbpjgx=jp$
zqA?@cKDgmVYPs&T)GXWU-a+YKzgq%Er%%hJP@e;MVsR5o((~zT)o)+=Hl;r}S`*(C
zB3j2G85rJx~ZJYgMpff`_dqonKTMw0ZDc;el_j_aGv3t*+#ciR(
z&J0_^qt;SpZ+S4xm@@X|aZ-whj2+J?s=EsiUL$MkV
zWXJH8uj=B9$4|TZzy%+aKYMU{2ncRY#Yd;m+4M;sd(DB$HTd}LMU&N)4MPS>r|oca
zFvXpNd*lDI-;i4LrDH>gcJfoeXZ&)9=p7%E`GQa{#`iyJ{SZtKL2NkjDXU4Yc0aGN
z`T0LSQj_&uBB&xF{Z!H?=iwp9H5{~yNqDodJ%!QVf}>#oS_
z{uw`)ehd5Dt{PK^4>$LKt+?%8-YMKup91j9V6U;ZD^x)dK+n}+Nh$mpS!Y;qMHYD)
zof>#$+L2=`G9vFUlaAxPn61r6HAD^xEXXve7BU#`ihp|3VKjSxEjqDLb>P>%Mz_cGe`yswYvvx%~dON~4%1a1;r>9SVVV
zQh3GjlKX3kiK0OKvA5ms*Yc0I;JpFY&)xtDfL=}Zj{X&+lT$6ksA5g@&XEnh`~
zMF055|1v7!PKXDuO^QyFJv@x>u8`kYxBtrT@I}(3+2{Yl4jh8n%vZzi%lap~)lccx
zS{%;c}6v_*b0~W>5uu23kx9s1loW3`~7Cu
HpZ)&_yd{z3
literal 0
HcmV?d00001
diff --git a/main/res/drawable/abc_ic_menu_moreoverflow_mtrl_alpha.png b/main/res/drawable/abc_ic_menu_moreoverflow_mtrl_alpha.png
new file mode 100644
index 0000000000000000000000000000000000000000..c695e316c7636dc1784994ce123134e98f2536be
GIT binary patch
literal 218
zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sx&b~Ru0Z-f(O{v+>e)aI{3Stt
z!3>(4zJA%L{^9@o^`F*?Yc;&J@#r0s$X3Ic~a{3{av7kYBf=sUD(
z?J@P_6Z<2cJ5On^J=NVLttYRg^Fv!hrzKo5ZsEbEDSO{D@Aq7lC7si$yHR!9k!c)f
YSb9=e3%kqafPBH=>FVdQ&MBb@0G^#`NdN!<
literal 0
HcmV?d00001
diff --git a/main/res/drawable/account_select.xml b/main/res/drawable/account_select.xml
new file mode 100644
index 0000000..5686596
--- /dev/null
+++ b/main/res/drawable/account_select.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main/res/drawable/btn_back.png b/main/res/drawable/btn_back.png
new file mode 100644
index 0000000000000000000000000000000000000000..8079ece0477d0246fbd0ab618aa06191eca08042
GIT binary patch
literal 172375
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