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) 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 0000000..077f572 Binary files /dev/null and b/main/res/drawable-v24/logo.jpg differ 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 0000000..c695e31 Binary files /dev/null and b/main/res/drawable/abc_ic_menu_moreoverflow_mtrl_alpha.png differ 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 0000000..8079ece Binary files /dev/null and b/main/res/drawable/btn_back.png differ diff --git a/main/res/drawable/edit_background.xml b/main/res/drawable/edit_background.xml new file mode 100644 index 0000000..f7c141a --- /dev/null +++ b/main/res/drawable/edit_background.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/main/res/drawable/flash_off.png b/main/res/drawable/flash_off.png new file mode 100644 index 0000000..616f80a Binary files /dev/null and b/main/res/drawable/flash_off.png differ diff --git a/main/res/drawable/flash_on.png b/main/res/drawable/flash_on.png new file mode 100644 index 0000000..d47ef8a Binary files /dev/null and b/main/res/drawable/flash_on.png differ diff --git a/main/res/drawable/house_select.xml b/main/res/drawable/house_select.xml new file mode 100644 index 0000000..c96db56 --- /dev/null +++ b/main/res/drawable/house_select.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/main/res/drawable/ic_baseline_account_circle_24.xml b/main/res/drawable/ic_baseline_account_circle_24.xml new file mode 100644 index 0000000..89199eb --- /dev/null +++ b/main/res/drawable/ic_baseline_account_circle_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_account_circle_24_actvite.xml b/main/res/drawable/ic_baseline_account_circle_24_actvite.xml new file mode 100644 index 0000000..4cac4ed --- /dev/null +++ b/main/res/drawable/ic_baseline_account_circle_24_actvite.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_attach_money_24.xml b/main/res/drawable/ic_baseline_attach_money_24.xml new file mode 100644 index 0000000..fecf031 --- /dev/null +++ b/main/res/drawable/ic_baseline_attach_money_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_chevron_left_24.xml b/main/res/drawable/ic_baseline_chevron_left_24.xml new file mode 100644 index 0000000..09598f1 --- /dev/null +++ b/main/res/drawable/ic_baseline_chevron_left_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_chevron_right_24.xml b/main/res/drawable/ic_baseline_chevron_right_24.xml new file mode 100644 index 0000000..fd2878a --- /dev/null +++ b/main/res/drawable/ic_baseline_chevron_right_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_email_24.xml b/main/res/drawable/ic_baseline_email_24.xml new file mode 100644 index 0000000..6943b4c --- /dev/null +++ b/main/res/drawable/ic_baseline_email_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_format_list_bulleted_24.xml b/main/res/drawable/ic_baseline_format_list_bulleted_24.xml new file mode 100644 index 0000000..0b66281 --- /dev/null +++ b/main/res/drawable/ic_baseline_format_list_bulleted_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_house_24.xml b/main/res/drawable/ic_baseline_house_24.xml new file mode 100644 index 0000000..c199d14 --- /dev/null +++ b/main/res/drawable/ic_baseline_house_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_house_24_active.xml b/main/res/drawable/ic_baseline_house_24_active.xml new file mode 100644 index 0000000..900d6cc --- /dev/null +++ b/main/res/drawable/ic_baseline_house_24_active.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_live_tv_24.xml b/main/res/drawable/ic_baseline_live_tv_24.xml new file mode 100644 index 0000000..4c6cd09 --- /dev/null +++ b/main/res/drawable/ic_baseline_live_tv_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_local_fire_department_24.xml b/main/res/drawable/ic_baseline_local_fire_department_24.xml new file mode 100644 index 0000000..adb2a4a --- /dev/null +++ b/main/res/drawable/ic_baseline_local_fire_department_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_more_horiz_24.xml b/main/res/drawable/ic_baseline_more_horiz_24.xml new file mode 100644 index 0000000..6439bcc --- /dev/null +++ b/main/res/drawable/ic_baseline_more_horiz_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_qr_code_scanner_24.xml b/main/res/drawable/ic_baseline_qr_code_scanner_24.xml new file mode 100644 index 0000000..597e8d7 --- /dev/null +++ b/main/res/drawable/ic_baseline_qr_code_scanner_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_search_24.xml b/main/res/drawable/ic_baseline_search_24.xml new file mode 100644 index 0000000..07b76d6 --- /dev/null +++ b/main/res/drawable/ic_baseline_search_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_settings_24.xml b/main/res/drawable/ic_baseline_settings_24.xml new file mode 100644 index 0000000..41a82ed --- /dev/null +++ b/main/res/drawable/ic_baseline_settings_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_share_24.xml b/main/res/drawable/ic_baseline_share_24.xml new file mode 100644 index 0000000..2f13bb3 --- /dev/null +++ b/main/res/drawable/ic_baseline_share_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_star_24.xml b/main/res/drawable/ic_baseline_star_24.xml new file mode 100644 index 0000000..3383294 --- /dev/null +++ b/main/res/drawable/ic_baseline_star_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_star_24_actvite.xml b/main/res/drawable/ic_baseline_star_24_actvite.xml new file mode 100644 index 0000000..a474b21 --- /dev/null +++ b/main/res/drawable/ic_baseline_star_24_actvite.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_baseline_update_24.xml b/main/res/drawable/ic_baseline_update_24.xml new file mode 100644 index 0000000..3b92302 --- /dev/null +++ b/main/res/drawable/ic_baseline_update_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/main/res/drawable/ic_launcher_background.xml b/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..a4f78de --- /dev/null +++ b/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/main/res/drawable/ic_switch_screen.xml b/main/res/drawable/ic_switch_screen.xml new file mode 100644 index 0000000..ea2eee6 --- /dev/null +++ b/main/res/drawable/ic_switch_screen.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/main/res/drawable/myphoto.jpg b/main/res/drawable/myphoto.jpg new file mode 100644 index 0000000..47098b1 Binary files /dev/null and b/main/res/drawable/myphoto.jpg differ diff --git a/main/res/drawable/search_shape.xml b/main/res/drawable/search_shape.xml new file mode 100644 index 0000000..89c5bb1 --- /dev/null +++ b/main/res/drawable/search_shape.xml @@ -0,0 +1,15 @@ + + + android:shape="rectangle"> + + + + + + \ No newline at end of file diff --git a/main/res/drawable/star_select.xml b/main/res/drawable/star_select.xml new file mode 100644 index 0000000..4ce6cf2 --- /dev/null +++ b/main/res/drawable/star_select.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/main/res/drawable/thumbnail.xml b/main/res/drawable/thumbnail.xml new file mode 100644 index 0000000..ea2eee6 --- /dev/null +++ b/main/res/drawable/thumbnail.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/main/res/layout/activity_bottom.xml b/main/res/layout/activity_bottom.xml new file mode 100644 index 0000000..49319ff --- /dev/null +++ b/main/res/layout/activity_bottom.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/main/res/layout/activity_main.xml b/main/res/layout/activity_main.xml new file mode 100644 index 0000000..ca0c0de --- /dev/null +++ b/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/main/res/layout/activity_mid.xml b/main/res/layout/activity_mid.xml new file mode 100644 index 0000000..ddfaf4a --- /dev/null +++ b/main/res/layout/activity_mid.xml @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file diff --git a/main/res/layout/activity_movie.xml b/main/res/layout/activity_movie.xml new file mode 100644 index 0000000..2e8498b --- /dev/null +++ b/main/res/layout/activity_movie.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/main/res/layout/activity_relative_layout.xml b/main/res/layout/activity_relative_layout.xml new file mode 100644 index 0000000..4fe216b --- /dev/null +++ b/main/res/layout/activity_relative_layout.xml @@ -0,0 +1,33 @@ + + + + + +