Repository: zwc456baby/vmqApk Branch: master Commit: 1d8158e49f75 Files: 59 Total size: 187.2 KB Directory structure: gitextract_efbl9myg/ ├── .gitignore ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ ├── google/ │ │ │ │ └── zxing/ │ │ │ │ ├── activity/ │ │ │ │ │ └── CaptureActivity.java │ │ │ │ ├── camera/ │ │ │ │ │ ├── AutoFocusCallback.java │ │ │ │ │ ├── CameraConfigurationManager.java │ │ │ │ │ ├── CameraManager.java │ │ │ │ │ ├── FlashlightManager.java │ │ │ │ │ ├── PlanarYUVLuminanceSource.java │ │ │ │ │ └── PreviewCallback.java │ │ │ │ ├── decoding/ │ │ │ │ │ ├── CaptureActivityHandler.java │ │ │ │ │ ├── DecodeFormatManager.java │ │ │ │ │ ├── DecodeHandler.java │ │ │ │ │ ├── DecodeThread.java │ │ │ │ │ ├── FinishListener.java │ │ │ │ │ ├── InactivityTimer.java │ │ │ │ │ ├── Intents.java │ │ │ │ │ └── RGBLuminanceSource.java │ │ │ │ ├── encoding/ │ │ │ │ │ └── EncodingHandler.java │ │ │ │ └── view/ │ │ │ │ ├── ViewfinderResultPointCallback.java │ │ │ │ └── ViewfinderView.java │ │ │ └── vone/ │ │ │ └── vmq/ │ │ │ ├── App.java │ │ │ ├── Constant.java │ │ │ ├── ForegroundServer.java │ │ │ ├── LockShowActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── NeNotificationService2.java │ │ │ ├── StartReceive.java │ │ │ ├── Utils.java │ │ │ └── util/ │ │ │ ├── BitmapUtil.java │ │ │ ├── Constant.java │ │ │ ├── FileUtils.java │ │ │ └── UriUtil.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_lock_show.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_scanner.xml │ │ │ ├── toolbar_main.xml │ │ │ └── toolbar_scanner.xml │ │ ├── menu/ │ │ │ └── scanner_menu.xml │ │ ├── raw/ │ │ │ └── beep.ogg │ │ ├── values/ │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── ids.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── xml/ │ │ ├── network_security_config.xml │ │ └── update_paths.xml │ └── test/ │ └── java/ │ └── com/ │ └── vone/ │ └── qrcode/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties .idea .DS_Store /build /captures .externalNativeBuild *.jks /app/authkey.jks ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 ahuyangdong Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ V免签 —— 个人开发者收款解决方案 =============== ## Android 15 新限制 请注意,Android 15 新系统,对app权限进一步限制,需要打开usb调试模式,使用adb执行shell命令: ``` appops set com.vone.qrcode RECEIVE_SENSITIVE_NOTIFICATIONS allow ``` 手动授予 `RECEIVE_SENSITIVE_NOTIFICATIONS` 权限,才能正常监听某些 支付宝 消息,否则会导致漏单等情况。这是Android15 新加入的限制,目前没有其他办法绕过 > 部分消息会被系统拦截,部分消息能够正常监听,这是新系统的默认行为,并非软件无法监听。手动授予权限后,才能监听到完整的通知消息 > Android 14 以及低版本没有这个限制 ## 简介 此处为fork自原作者的修改版 主要修改增加几项功能: 1. 支持开机自启(国内定制系统需手动开启自启权限) 2. 支持post失败后再重试一次(这次重试是强制亮屏的) 3. ios设备可以找一台安卓设备-》绑定店员进行收款通知了 优化功能: 1. 优化了里面大量的 okhttp 的访问,做成了单例,防止某些情况下可能导致的内存溢出、错误崩溃等等 2. 增加了一次网络重试(没有做三次、四次是防止重复提交导致订单出问题),而且是唤起到前台的网络重试,(之所以唤起到前台,是测试过小米等机型,如果app长时间在后台(6小时以上),app会被限制网络,除非启动前台Service或者Activity 修改版特点:可以装在自己的日用安卓机器上,不用专门找一台机器亮屏挂机了。因为修改版支持错误重试,如果装在日用机器上推送失败,软件会强制打开一个前台Service以及一个前台Activity 前台页面的打开需要手动开启两项权限:后台弹出页面、锁屏显示 > 在使用过程中,可以将屏幕关闭,省电,然后在收到支付信息后,软件会自动的打开前台并推送支付信息。 > 软件里面的心跳本来想移除用来省电的,但基于可靠性,虽然心跳会略微增加耗电,但还是没有去除 --- **以下赞助链接采用此系统搭建,您可以随时赞助测试其稳定性:** [赞助链接](https://card.zwc365.com/p/2eyzmzlwcdc076wm8zc2) 如果 APK 出现什么问题,不兼容你的手机或新版支付宝,可以从赞助链接与我反馈 如果你不想搭建或者不会搭建 V免签,可以用我搭建好的 [V免签系统](http://pay.zwc365.com/) =============== V免签 是基于SpringBoot 2.1.1/ThinkPhP5.1 实现的一套免签支付程序,主要包含以下特色: + JAVA、PHP双服务端,总有一款适合你服务器 + 超简单Api使用,提供统一Api实现收款回调 + 免费、开源 > Java版服务端地址:【 https://github.com/szvone/Vmq 】 > PHP 版服务端地址:【 https://github.com/szvone/vmqphp 】 ## 前言 [赞助链接](https://card.zwc365.com/p/2eyzmzlwcdc076wm8zc2) ## 安装 + 下载服务端程序,可选PHP/Java版,按照说明安装 + 下载编译好的Apk,安装后扫码/手动配置 即可使用 > 升级说明:请您直接下载新版本覆盖旧版本即可! ## 说明 + 请部署完成后访问后台,有详细的Api说明 ## 注意 + 本系统原理为监控收款后手机的通知栏推送消息,所以请保持微信/支付宝/V免签监控端后台正常运行,且添加到内存清理白名单! + v免签面向用户是个人开发者,如果您不懂如何开发网站,那么v免签不适合您的使用! + v免签的原理是监控手机收到收款后的通知栏推送信息,所以不适合于商用多用户的情况,如果您想用于商用,请二次开发! + v免签是免费开源产品,所有程序均开放源代码,所以不会有收费计划,因此作者不可能教会每个人部署安装,请参考文档多百度谷歌,v免签使用具有一定的技术门槛,请见谅! + v免签的监控端并不适配所有手机,遇到手机无法正常使用的时候,请您更换手机或使用模拟器挂机! + v免签拥有双语言服务端,当您使用php版本服务端遇到问题的时候,请您尝试使用java版本服务端,php版本服务端配置略复杂,需要配置伪静态规则,请知悉! + 正常的安装步骤简略如下 + 下载服务端部署(GitHub中下载的为最新版) + 登录网站后台更改系统设置 + 打开网站后台监控端设置 + 下载监控端 + 安装监控端后使用手动配置或扫码配置 + 监控端中点击开启服务跳转到辅助功能中开启服务 + 开启服务后返回v免签点击检测监听权限 + 如果显示监听权限正常,至此安装完毕,如果只收到通知栏推送的测试通知,则系统不兼容无法正常监听 + 如果显示监听权限正常,还是无法正常运行,那么请确定微信是否关注 “微信支付”这个公众号 + v免签支持的通知有: + 支付宝个人收款的推送通知 + 支付宝商家二维码的收款推送通知 + 支付宝店员通绑定的店员账号收款的推送通知 + 微信二维码收款推送通知 + 微信店员收款推送通知 + 微信收款商业版收款推送通知 + 微信收款商业版店员到账收款通知 ## 更新记录 + v1.8.1(2019.12.20) + 修复上个版本无法正常回调问题 + v1.8(2019.12.03) + 修复付款人昵称如果是纯数字,支付完后台订单金额会记录成昵称的数字的问题 + v1.7(2019.05.17) + 删除辅助功能依赖,改为使用通知使用权进行监听,修复一大堆bug,建议更新到该版本 + v1.6.2(2019.05.17) + 增加微信收款商业版到账支持 + v1.6.1(2019.05.17) + 修复微信无法正常回调的问题 + v1.6(2019.05.16) + 启用新方式监听到账通知,理论支持更多设备! + v1.5(2019.04.24) + 修复部分手机扫码配置失败的问题! + v1.4.1(2019.04.23) + 修复部分手机无法正确检测监听权限问题,点击监听权限按钮后,如果一切正常,状态栏会收到推送信息,并且会提示监听权限正常! + v1.4(2019.04.23) + 修复部分手机无法正确检测监听权限问题,点击监听权限按钮后,如果一切正常,状态栏会收到推送信息,并且会提示监听权限正常! + v1.3(2019.04.20) + 添加后台心跳线程熄屏运行权限,更加稳定啦(推荐更新到此版本) + v1.2(2019.04.19) + 整理代码,重新优化APP兼容性 + 添加店员到账支持,添加后可以实现安卓备用机/模拟器 挂小号取收款通知,方便IOS用户, + 微信绑定店员方式=>微信->收付款->二维码收款->收款小账本->添加店员接收通知 + 支付宝绑定店员方式=>我的->商家服务->店员通->立即添加 + v1.1(2019.02.25) + 修复安卓7.0以上系统监控App闪退问题 + 修复监控端检测服务状态无法正确检测是否正常问题 + 添加商家码收款回调支持,商家码收款的也能正常回调啦 + v1.0(2019.01.31) + 初版发布 ## 版权信息 V免签遵循 MIT License 开源协议发布,并提供免费使用,请勿用于非法用途。 版权所有Copyright © 2019 by vone (http://szvone.cn) All rights reserved。 ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 28 // buildToolsVersion "26.0.0" defaultConfig { applicationId "com.vone.qrcode" minSdkVersion 19 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } signingConfigs { //加载资源 Properties properties = new Properties() InputStream inputStream = project.rootProject.file('local.properties').newDataInputStream(); properties.load(inputStream) signConfig { storeFile file(properties.getProperty("STORE_FILE_NAME"))//签名文件路径, storePassword properties.getProperty("KEY_PASSWORD") //密码 keyAlias properties.getProperty("STORE_ALIAS") keyPassword properties.getProperty("KEY_PASSWORD") //密码 } } buildTypes { debug { signingConfig signingConfigs.signConfig // 配置debug包的签名,接入微信分享必须验证签名 } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.signConfig } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:26.+' // compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.google.zxing:core:3.3.0' compile 'com.squareup.okhttp3:okhttp:3.7.0' compile 'com.squareup.okio:okio:1.12.0' testCompile 'junit:junit:4.12' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in D:\As\Sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/google/zxing/activity/CaptureActivity.java ================================================ package com.google.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.support.v7.app.AppCompatActivity; 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 com.vone.qrcode.R; import com.vone.vmq.util.BitmapUtil; import com.vone.vmq.util.Constant; 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.google.zxing.camera.CameraManager; import com.google.zxing.common.HybridBinarizer; import com.google.zxing.decoding.CaptureActivityHandler; import com.google.zxing.decoding.InactivityTimer; import com.google.zxing.decoding.RGBLuminanceSource; import com.google.zxing.qrcode.QRCodeReader; import com.google.zxing.view.ViewfinderView; import java.io.IOException; import java.util.Hashtable; import java.util.Vector; /** * 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(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 = 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(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(); } } }; } ================================================ FILE: app/src/main/java/com/google/zxing/camera/AutoFocusCallback.java ================================================ /* * Copyright (C) 2010 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.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"); } } } ================================================ FILE: app/src/main/java/com/google/zxing/camera/CameraConfigurationManager.java ================================================ /* * Copyright (C) 2010 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.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; } } ================================================ FILE: app/src/main/java/com/google/zxing/camera/CameraManager.java ================================================ /* * Copyright (C) 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.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; } } } } ================================================ FILE: app/src/main/java/com/google/zxing/camera/FlashlightManager.java ================================================ /* * Copyright (C) 2010 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.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); } } } ================================================ FILE: app/src/main/java/com/google/zxing/camera/PlanarYUVLuminanceSource.java ================================================ /* * 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.google.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; } } ================================================ FILE: app/src/main/java/com/google/zxing/camera/PreviewCallback.java ================================================ /* * Copyright (C) 2010 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.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"); } } } ================================================ FILE: app/src/main/java/com/google/zxing/decoding/CaptureActivityHandler.java ================================================ /* * Copyright (C) 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.decoding; 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.vone.qrcode.R; import com.google.zxing.BarcodeFormat; import com.google.zxing.Result; import com.google.zxing.activity.CaptureActivity; import com.google.zxing.camera.CameraManager; import com.google.zxing.view.ViewfinderResultPointCallback; import java.util.Vector; /** * 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(); } } } ================================================ FILE: app/src/main/java/com/google/zxing/decoding/DecodeFormatManager.java ================================================ /* * Copyright (C) 2010 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.decoding; 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; } } ================================================ FILE: app/src/main/java/com/google/zxing/decoding/DecodeHandler.java ================================================ /* * Copyright (C) 2010 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.decoding; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import com.vone.qrcode.R; 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.google.zxing.activity.CaptureActivity; import com.google.zxing.camera.CameraManager; import com.google.zxing.camera.PlanarYUVLuminanceSource; import com.google.zxing.common.HybridBinarizer; import java.util.Hashtable; 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(); } } } ================================================ FILE: app/src/main/java/com/google/zxing/decoding/DecodeThread.java ================================================ /* * Copyright (C) 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.decoding; import android.os.Handler; import android.os.Looper; import com.google.zxing.BarcodeFormat; import com.google.zxing.DecodeHintType; import com.google.zxing.ResultPointCallback; import com.google.zxing.activity.CaptureActivity; import java.util.Hashtable; import java.util.Vector; import java.util.concurrent.CountDownLatch; /** * 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(); } } ================================================ FILE: app/src/main/java/com/google/zxing/decoding/FinishListener.java ================================================ /* * Copyright (C) 2010 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.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(); } } ================================================ FILE: app/src/main/java/com/google/zxing/decoding/InactivityTimer.java ================================================ /* * Copyright (C) 2010 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.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; } } } ================================================ FILE: app/src/main/java/com/google/zxing/decoding/Intents.java ================================================ /* * Copyright (C) 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.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() { } } } ================================================ FILE: app/src/main/java/com/google/zxing/decoding/RGBLuminanceSource.java ================================================ /* * 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.google.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; } } ================================================ FILE: app/src/main/java/com/google/zxing/encoding/EncodingHandler.java ================================================ package com.google.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; } } ================================================ FILE: app/src/main/java/com/google/zxing/view/ViewfinderResultPointCallback.java ================================================ /* * 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.google.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); } } ================================================ FILE: app/src/main/java/com/google/zxing/view/ViewfinderView.java ================================================ /* * Copyright (C) 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.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.vone.qrcode.R; import com.google.zxing.ResultPoint; import com.google.zxing.camera.CameraManager; import java.util.Collection; import java.util.HashSet; /** * 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); } } ================================================ FILE: app/src/main/java/com/vone/vmq/App.java ================================================ package com.vone.vmq; import android.annotation.SuppressLint; import android.app.Application; import android.content.Context; import android.os.Process; import android.util.Log; public class App extends Application { @SuppressLint("StaticFieldLeak") private static Context mContext; public static Context getContext() { return mContext; } @Override public void onCreate() { super.onCreate(); Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable throwable) { Utils.putStr(App.this, "exception:" + Log.getStackTraceString(throwable)); Process.killProcess(Process.myPid()); } }); } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); mContext = base; } } ================================================ FILE: app/src/main/java/com/vone/vmq/Constant.java ================================================ package com.vone.vmq; /** * Created by user68 on 2018/7/30. */ @SuppressWarnings({"unused", "WeakerAccess"}) public class Constant { public final static String GET_MESSAGE_KEY = "get_message_key"; public final static String FINISH_LOCK_SHOW_ACTIVITY = "finish_lock_show_activity"; public final static String UPDATA_MESSAGE_DATA_ACTION = "updata_message_data_action"; public final static String FINISH_FOREGROUND_SERVICE = "finish_foreground_service"; } ================================================ FILE: app/src/main/java/com/vone/vmq/ForegroundServer.java ================================================ package com.vone.vmq; import android.annotation.TargetApi; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.SystemClock; import android.support.annotation.RequiresApi; import android.text.TextUtils; import android.util.Log; import com.vone.qrcode.R; import org.json.JSONObject; import java.io.IOException; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Request; import okhttp3.Response; @SuppressWarnings("FieldCanBeLocal") public class ForegroundServer extends Service { public static final String GET_NOTIFY_TITLE = "get_notify_title"; public static final String GET_NOTIFY_TEXT = "get_notify_text"; //这里传 json 过来 public static final String GET_NOTIFY_EXTRA = "get_notify_extra"; private final int FOREGROUND_ID = 1; private final String channel_name = "ForegoundServer"; private final String CHANNEL_ID = "service"; private final Handler handler = new Handler(Looper.getMainLooper()); private final long MIN_SHOW_TIME = 2000; private final long MAX_SHOW_TIME = 20000; private long enterTime; @Override public void onCreate() { super.onCreate(); enterTime = SystemClock.elapsedRealtime(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { setForegroundService(); } registerFinishBroad(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { handler.removeCallbacks(stopServerRunnable); handler.postDelayed(stopServerRunnable, MAX_SHOW_TIME); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { updateNotify(intent); } startForegroundActivity(intent); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { unregisterFinishBroad(); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); Log.i("ForegoundServer", "stop notification"); if (notificationManager != null) { notificationManager.cancel(FOREGROUND_ID); } handler.removeCallbacks(stopServerRunnable); super.onDestroy(); } private void startForegroundActivity(Intent intent) { if (intent == null) { return; } String extraStr = intent.getStringExtra(GET_NOTIFY_EXTRA); if (TextUtils.isEmpty(extraStr)) { return; } try { JSONObject jsonObject = new JSONObject(extraStr); final String url = jsonObject.optString("url"); if (url == null) { return; } if (jsonObject.optBoolean("show", true)) { startLockActivity(this.getString(R.string.app_is_post)); } tryPushByUrl(url, jsonObject.optInt("try_count", 1)); } catch (Exception e) { e.printStackTrace(); } } private void tryPushByUrl(final String url, final int count) { if (count <= 0) { handler.post(new Runnable() { @Override public void run() { NeNotificationService2.exitForeground(App.getContext()); } }); return; } // 进行一个短暂的延迟再通知过去 handler.postDelayed(new Runnable() { @Override public void run() { Request request = new Request.Builder().url(url).method("GET", null).build(); Call call = Utils.getOkHttpClient().newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d("ForegroundServer", "onResponse push: 请求失败"); tryPushByUrl(url, count - 1); } @Override public void onResponse(Call call, Response response) throws IOException { try { Log.d("ForegroundServer", "onResponse push: " + response.body().string()); } catch (Exception e) { e.printStackTrace(); } finally { if (!response.isSuccessful()) { tryPushByUrl(url, count - 1); } else { handler.post(new Runnable() { @Override public void run() { NeNotificationService2.exitForeground(App.getContext()); } }); } } } }); } }, MIN_SHOW_TIME); } /** * 通过通知启动服务 */ @TargetApi(Build.VERSION_CODES.O) private void setForegroundService() { Utils.createNotificationChannel(this, CHANNEL_ID, channel_name , NotificationManager.IMPORTANCE_DEFAULT); Notification notification = getNotifycation(null); startForeground(FOREGROUND_ID, notification); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void updateNotify(Intent intent) { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); if (notificationManager != null) { Notification notification = getNotifycation(intent); notificationManager.notify(FOREGROUND_ID, notification); } } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private Notification getNotifycation(Intent intent) { String title = intent == null ? getString(R.string.app_name) : TextUtils.isEmpty(intent.getStringExtra(GET_NOTIFY_TITLE)) ? getString(R.string.app_name) : intent.getStringExtra(GET_NOTIFY_TITLE); String text = intent == null ? getString(R.string.click_close_notify) : TextUtils.isEmpty(intent.getStringExtra(GET_NOTIFY_TEXT)) ? getString(R.string.click_close_notify) : intent.getStringExtra(GET_NOTIFY_TEXT); Notification.Builder notificationBuilder = new Notification.Builder(this) .setContentTitle(title)//设置通知标题 .setContentText(text)//设置通知内容 .setPriority(Notification.PRIORITY_MIN) .setCategory(Notification.CATEGORY_SERVICE) .setOngoing(true) .setWhen(System.currentTimeMillis()) .setShowWhen(true) .setSmallIcon(R.mipmap.ic_launcher) .setAutoCancel(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { notificationBuilder.setChannelId(CHANNEL_ID); } Notification notification = notificationBuilder.build(); Intent notificationIntent = new Intent(getApplicationContext(), StartReceive.class); notificationIntent.setAction(StartReceive.TRY_CLOSE_ACTIVITY_ACTION); notification.contentIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, notificationIntent, 0); return notification; } private void registerFinishBroad() { registerReceiver(finishServiceBroadcast, new IntentFilter(Constant.FINISH_FOREGROUND_SERVICE)); } private void unregisterFinishBroad() { try { this.unregisterReceiver(finishServiceBroadcast); } catch (Exception ignore) { } } private final Runnable stopServerRunnable = new Runnable() { @Override public void run() { try { finishLockActivity(); Intent intent1 = new Intent(App.getContext(), ForegroundServer.class); stopService(intent1); } catch (Exception ignore) { } } }; private final BroadcastReceiver finishServiceBroadcast = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (Constant.FINISH_FOREGROUND_SERVICE.equals(intent.getAction())) { long temp = SystemClock.elapsedRealtime() - enterTime; handler.removeCallbacks(stopServerRunnable); if (temp > MIN_SHOW_TIME) { handler.post(stopServerRunnable); } else { handler.postDelayed(stopServerRunnable, MIN_SHOW_TIME - temp); } } } }; private void startLockActivity(String msg) { Intent intent = new Intent(this, LockShowActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(Constant.GET_MESSAGE_KEY, msg); startActivity(intent); } private void finishLockActivity() { Intent sendIntent = new Intent(); sendIntent.setAction(Constant.FINISH_LOCK_SHOW_ACTIVITY); sendBroadcast(sendIntent); } private void updataMessageData(String msg) { Intent sendIntent = new Intent(); sendIntent.setAction(Constant.UPDATA_MESSAGE_DATA_ACTION); sendIntent.putExtra(Constant.GET_MESSAGE_KEY, msg); sendBroadcast(sendIntent); } @Override public IBinder onBind(Intent intent) { return null; } } ================================================ FILE: app/src/main/java/com/vone/vmq/LockShowActivity.java ================================================ package com.vone.vmq; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; import android.os.Bundle; import android.view.WindowManager; import android.widget.TextView; import com.vone.qrcode.R; public class LockShowActivity extends Activity { private TextView showTv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_lock_show); registerBroadCast(); InitView(); InitData(); } private void registerBroadCast() { IntentFilter filter = new IntentFilter(Constant.FINISH_LOCK_SHOW_ACTIVITY); registerReceiver(finishActivityBroadcast, filter); } private void unregisterBroadCast() { unregisterReceiver(finishActivityBroadcast); } private void InitView() { showTv = findViewById(R.id.showTv); } private void InitData() { resetData(getIntent()); } /** * 显示通知类型: * -1 :检测到对应的配置通知 * 0 : 远程的重要通知 * 1 : 通知栏通知 */ private void resetData(Intent intent) { String showTvText = intent.getStringExtra(Constant.GET_MESSAGE_KEY); showTv.setText(showTvText); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); resetData(intent); } @Override protected void onDestroy() { super.onDestroy(); unregisterBroadCast(); } private final BroadcastReceiver finishActivityBroadcast = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (Constant.FINISH_LOCK_SHOW_ACTIVITY.equals(intent.getAction())) { if (!isFinishing()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { finishAndRemoveTask(); } } } else if (Constant.UPDATA_MESSAGE_DATA_ACTION.equals(intent.getAction())) { resetData(intent); } } }; } ================================================ FILE: app/src/main/java/com/vone/vmq/MainActivity.java ================================================ package com.vone.vmq; import android.Manifest; import android.app.AlertDialog; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.google.zxing.activity.CaptureActivity; import com.vone.qrcode.R; import com.vone.vmq.util.Constant; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Date; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Request; import okhttp3.Response; public class MainActivity extends AppCompatActivity { private final Handler handler = new Handler(Looper.getMainLooper()); private TextView txthost; private TextView txtkey; private boolean isOk = false; private static String TAG = "MainActivity"; private static String host; private static String key; int id = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); txthost = (TextView) findViewById(R.id.txt_host); txtkey = (TextView) findViewById(R.id.txt_key); //检测通知使用权是否启用 if (!isNotificationListenersEnabled()) { //跳转到通知使用权页面 gotoNotificationAccessSetting(); } else if (!Utils.checkBatteryWhiteList(this)) { Utils.gotoBatterySetting(this); } //重启监听服务 if (!NeNotificationService2.isRunning) { toggleNotificationListenerService(this); } //读入保存的配置数据并显示 SharedPreferences read = getSharedPreferences("vone", MODE_PRIVATE); host = read.getString("host", ""); key = read.getString("key", ""); if (host != null && key != null && host != "" && key != "") { txthost.setText(" 通知地址:" + host); txtkey.setText(" 通讯密钥:" + key); isOk = true; } Toast.makeText(MainActivity.this, "v免签开源免费免签系统 v1.8.1", Toast.LENGTH_SHORT).show(); } //扫码配置 public void startQrCode(View v) { // 申请相机权限 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { // 申请权限 ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, Constant.REQ_PERM_CAMERA); return; } // 申请文件读写权限(部分朋友遇到相册选图需要读写权限的情况,这里一并写一下) if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // 申请权限 ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, Constant.REQ_PERM_EXTERNAL_STORAGE); return; } // 二维码扫码 Intent intent = new Intent(MainActivity.this, CaptureActivity.class); startActivityForResult(intent, Constant.REQ_QR_CODE); } //手动配置 public void doInput(View v) { final EditText inputServer = new EditText(this); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("请输入配置数据").setView(inputServer) .setNegativeButton("取消", null); builder.setPositiveButton("确认", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { String scanResult = inputServer.getText().toString(); String[] tmp = scanResult.split("/"); if (tmp.length != 2) { Toast.makeText(MainActivity.this, "数据错误,请您输入网站上显示的配置数据!", Toast.LENGTH_SHORT).show(); return; } String t = String.valueOf(new Date().getTime()); String sign = md5(t + tmp[1]); Request request = new Request.Builder().url("http://" + tmp[0] + "/appHeart?t=" + t + "&sign=" + sign).method("GET", null).build(); Call call = Utils.getOkHttpClient().newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { try { Log.d(TAG, "onResponse: " + response.body().string()); } catch (Exception e) { e.printStackTrace(); } isOk = true; } }); if (tmp[0].indexOf("localhost") >= 0) { Toast.makeText(MainActivity.this, "配置信息错误,本机调试请访问 本机局域网IP:8080(如192.168.1.101:8080) 获取配置信息进行配置!", Toast.LENGTH_LONG).show(); return; } //将扫描出的信息显示出来 txthost.setText(" 通知地址:" + tmp[0]); txtkey.setText(" 通讯密钥:" + tmp[1]); host = tmp[0]; key = tmp[1]; SharedPreferences.Editor editor = getSharedPreferences("vone", MODE_PRIVATE).edit(); editor.putString("host", host); editor.putString("key", key); editor.commit(); } }); builder.show(); } //检测心跳 public void doStart(View view) { if (!isOk) { Toast.makeText(MainActivity.this, "请您先配置!", Toast.LENGTH_SHORT).show(); return; } String t = String.valueOf(new Date().getTime()); String sign = md5(t + key); Request request = new Request.Builder().url("http://" + host + "/appHeart?t=" + t + "&sign=" + sign).method("GET", null).build(); Call call = Utils.getOkHttpClient().newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handler.post(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "心跳状态错误,请检查配置是否正确!", Toast.LENGTH_SHORT).show(); } }); } @Override public void onResponse(Call call, final Response response) throws IOException { handler.post(new Runnable() { @Override public void run() { try { // 为什么每一个response都 try catch了,因为response.body有可能为空 Toast.makeText(MainActivity.this, "心跳返回:" + response.body().string(), Toast.LENGTH_LONG).show(); } catch (Exception e) { e.printStackTrace(); } } }); } }); } //检测监听 public void checkPush(View v) { Notification mNotification; NotificationManager mNotificationManager; mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel("1", "Channel1", NotificationManager.IMPORTANCE_DEFAULT); channel.enableLights(true); channel.setLightColor(Color.GREEN); channel.setShowBadge(true); mNotificationManager.createNotificationChannel(channel); Notification.Builder builder = new Notification.Builder(this, "1"); mNotification = builder .setSmallIcon(R.mipmap.ic_launcher) .setTicker("这是一条测试推送信息,如果程序正常,则会提示监听权限正常") .setContentTitle("V免签测试推送") .setContentText("这是一条测试推送信息,如果程序正常,则会提示监听权限正常") .build(); } else { mNotification = new Notification.Builder(MainActivity.this) .setSmallIcon(R.mipmap.ic_launcher) .setTicker("这是一条测试推送信息,如果程序正常,则会提示监听权限正常") .setContentTitle("V免签测试推送") .setContentText("这是一条测试推送信息,如果程序正常,则会提示监听权限正常") .build(); } //Toast.makeText(MainActivity.this, "已推送信息,如果权限,那么将会有下一条提示!", Toast.LENGTH_SHORT).show(); mNotificationManager.notify(id++, mNotification); } //各种权限的判断 private void toggleNotificationListenerService(Context context) { PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting(new ComponentName(context, NeNotificationService2.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); pm.setComponentEnabledSetting(new ComponentName(context, NeNotificationService2.class), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); // 不要每次打开都显示 // Toast.makeText(MainActivity.this, "监听服务启动中...", Toast.LENGTH_SHORT).show(); } public boolean isNotificationListenersEnabled() { String pkgName = getPackageName(); final String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners"); if (!TextUtils.isEmpty(flat)) { final String[] names = flat.split(":"); for (int i = 0; i < names.length; i++) { final ComponentName cn = ComponentName.unflattenFromString(names[i]); if (cn != null) { if (TextUtils.equals(pkgName, cn.getPackageName())) { return true; } } } } return false; } protected boolean gotoNotificationAccessSetting() { try { Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); return true; } catch (ActivityNotFoundException e) {//普通情况下找不到的时候需要再特殊处理找一次 try { Intent intent = new Intent(); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ComponentName cn = new ComponentName("com.android.settings", "com.android.settings.Settings$NotificationAccessSettingsActivity"); intent.setComponent(cn); intent.putExtra(":settings:show_fragment", "NotificationAccessSettings"); startActivity(intent); return true; } catch (Exception e1) { e1.printStackTrace(); } Toast.makeText(this, "对不起,您的手机暂不支持", Toast.LENGTH_SHORT).show(); e.printStackTrace(); return false; } } public static String md5(String string) { if (TextUtils.isEmpty(string)) { return ""; } MessageDigest md5 = null; try { md5 = MessageDigest.getInstance("MD5"); byte[] bytes = md5.digest(string.getBytes()); StringBuilder result = new StringBuilder(); for (byte b : bytes) { String temp = Integer.toHexString(b & 0xff); if (temp.length() == 1) { temp = "0" + temp; } result.append(temp); } return result.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return ""; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); //扫描结果回调 if (requestCode == Constant.REQ_QR_CODE && resultCode == RESULT_OK) { Bundle bundle = data.getExtras(); String scanResult = bundle.getString(Constant.INTENT_EXTRA_KEY_QR_SCAN); String[] tmp = scanResult.split("/"); if (tmp.length != 2) { Toast.makeText(MainActivity.this, "二维码错误,请您扫描网站上显示的二维码!", Toast.LENGTH_SHORT).show(); return; } String t = String.valueOf(new Date().getTime()); String sign = md5(t + tmp[1]); Request request = new Request.Builder().url("http://" + tmp[0] + "/appHeart?t=" + t + "&sign=" + sign).method("GET", null).build(); Call call = Utils.getOkHttpClient().newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { try { Log.d(TAG, "onResponse: " + response.body().string()); } catch (Exception e) { e.printStackTrace(); } isOk = true; } }); //将扫描出的信息显示出来 txthost.setText(" 通知地址:" + tmp[0]); txtkey.setText(" 通讯密钥:" + tmp[1]); host = tmp[0]; key = tmp[1]; SharedPreferences.Editor editor = getSharedPreferences("vone", MODE_PRIVATE).edit(); editor.putString("host", host); editor.putString("key", key); editor.commit(); } } @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(null); } else { // 被禁止授权 Toast.makeText(MainActivity.this, "请至权限中心打开本应用的相机访问权限", Toast.LENGTH_LONG).show(); } break; case Constant.REQ_PERM_EXTERNAL_STORAGE: // 文件读写权限申请 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 获得授权 startQrCode(null); } else { // 被禁止授权 Toast.makeText(MainActivity.this, "请至权限中心打开本应用的文件读写权限", Toast.LENGTH_LONG).show(); } break; } } } ================================================ FILE: app/src/main/java/com/vone/vmq/NeNotificationService2.java ================================================ package com.vone.vmq; import android.annotation.SuppressLint; import android.app.Notification; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.support.v4.app.NotificationCompat; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; import com.vone.qrcode.R; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Request; import okhttp3.Response; public class NeNotificationService2 extends NotificationListenerService { private static String TAG = "NeNotificationService2"; private final Handler handler = new Handler(Looper.getMainLooper()); private String host = ""; private String key = ""; private Thread newThread = null; private PowerManager.WakeLock mWakeLock = null; public static boolean isRunning; //申请设备电源锁 @SuppressLint("InvalidWakeLockTag") public void acquireWakeLock(final Context context) { handler.post(new Runnable() { @Override public void run() { if (null == mWakeLock) { PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); if (pm != null) { mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "WakeLock"); } } if (null != mWakeLock) { mWakeLock.acquire(5000); } } }); } //释放设备电源锁 public void releaseWakeLock() { handler.post(new Runnable() { @Override public void run() { if (null != mWakeLock) { mWakeLock.release(); mWakeLock = null; } } }); } //心跳进程 public void initAppHeart() { Log.d(TAG, "开始启动心跳线程"); newThread = new Thread(new Runnable() { @Override public void run() { Log.d(TAG, "心跳线程启动!"); while (isRunning && newThread == Thread.currentThread()) { SharedPreferences read = getSharedPreferences("vone", MODE_PRIVATE); host = read.getString("host", ""); key = read.getString("key", ""); //这里写入子线程需要做的工作 String t = String.valueOf(new Date().getTime()); String sign = md5(t + key); final String url = "http://" + host + "/appHeart?t=" + t + "&sign=" + sign; Request request = new Request.Builder().url(url).method("GET", null).build(); Call call = Utils.getOkHttpClient().newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { // final String error = e.getMessage(); // Toast.makeText(getApplicationContext(), "心跳状态错误,请检查配置是否正确!" + error, Toast.LENGTH_LONG).show(); foregroundHeart(url); } //请求成功执行的方法 @Override public void onResponse(Call call, Response response) throws IOException { try { Log.d(TAG, "onResponse heard: " + response.body().string()); } catch (Exception e) { e.printStackTrace(); } if (!response.isSuccessful()) { foregroundHeart(url); } } }); try { Thread.sleep(30 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); newThread.start(); //启动线程 } //当收到一条消息的时候回调,sbn是收到的消息 @Override public void onNotificationPosted(StatusBarNotification sbn) { Log.d(TAG, "接受到通知消息"); writeNotifyToFile(sbn); // 微信支付部分通知,会调用两次,导致统计不准确 if ((sbn.getNotification().flags & Notification.FLAG_GROUP_SUMMARY) != 0) { Log.d(TAG, "群组摘要通知,忽略"); return; } SharedPreferences read = getSharedPreferences("vone", MODE_PRIVATE); host = read.getString("host", ""); key = read.getString("key", ""); Notification notification = sbn.getNotification(); String pkg = sbn.getPackageName(); if (notification != null) { Bundle extras = notification.extras; if (extras != null) { CharSequence _title = extras.getCharSequence(NotificationCompat.EXTRA_TITLE, ""); CharSequence _content = extras.getCharSequence(NotificationCompat.EXTRA_TEXT, ""); Log.d(TAG, "**********************"); Log.d(TAG, "包名:" + pkg); Log.d(TAG, "标题:" + _title); Log.d(TAG, "内容:" + _content); Log.d(TAG, "**********************"); // to string (企业微信之类的 getString 会出错,换getCharSequence) String title = _title.toString(); String content = _content.toString(); if ("com.eg.android.AlipayGphone".equals(pkg)) { if (!content.equals("")) { if (content.contains("通过扫码向你付款") || content.contains("成功收款") || title.contains("通过扫码向你付款") || title.contains("成功收款") || content.contains("店员通") || title.contains("店员通")) { String money; // 新版支付宝,会显示积分情况下。先匹配标题上的金额 if (content.contains("商家积分")) { money = getMoney(title); if (money == null) { money = getMoney(content); } } else { money = getMoney2(title); if (money == null) { // 继续使用匹配 xxx元的方式 money = getMoney2(content); } if (money == null) { // 使用数字匹配的方式 money = getMoney(content); } if (money == null) { money = getMoney(title); } } if (money != null) { Log.d(TAG, "onAccessibilityEvent: 匹配成功: 支付宝 到账 " + money); try{ appPush(2, Double.parseDouble(money)); } catch (Exception e) { Log.d(TAG, "app push 错误!!!"); } } else { handler.post(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), "监听到支付宝消息但未匹配到金额!", Toast.LENGTH_SHORT).show(); } }); } } } } else if ("com.tencent.mm".equals(pkg) || "com.tencent.wework".equals(pkg)) { if (!content.equals("")) { // 微信 最新版 8.0.50 开始,对收款通知栏格式做了修改 if (title.equals("微信") || title.equals("微信支付") || title.equals("微信收款助手") || title.equals("微信收款商业版") || content.contains("微信支付") || content.contains("微信收款助手") || content.contains("微信收款商业版") || (title.equals("对外收款") || title.equals("企业微信")) && (content.contains("成功收款") || content.contains("收款通知"))) { String money = getMoney2(content); if (money == null) { // 继续使用匹配 xxx元的方式 money = getMoney2(title); } if (money == null) { // 使用旧版的匹配方式,可能识别错误,不够精准 money = getMoney(content); } if (money != null) { Log.d(TAG, "onAccessibilityEvent: 匹配成功: 微信到账 " + money); try { appPush(1, Double.parseDouble(money)); } catch (Exception e) { Log.d(TAG, "app push 错误!!!"); } } else { handler.post(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), "监听到微信消息但未匹配到金额!", Toast.LENGTH_SHORT).show(); } }); } } } } else if ("com.vone.qrcode".equals(pkg)) { if (content.equals("这是一条测试推送信息,如果程序正常,则会提示监听权限正常")) { handler.post(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), "监听正常,如无法正常回调请联系作者反馈!", Toast.LENGTH_SHORT).show(); } }); } } } } } //当移除一条消息的时候回调,sbn是被移除的消息 @Override public void onNotificationRemoved(StatusBarNotification sbn) { } //当连接成功时调用,一般在开启监听后会回调一次该方法 @Override public void onListenerConnected() { isRunning = true; //开启心跳线程 initAppHeart(); handler.post(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), "监听服务开启成功!", Toast.LENGTH_SHORT).show(); } }); } @Override public void onListenerDisconnected() { super.onListenerDisconnected(); isRunning = false; if (newThread != null) { newThread.interrupt(); } newThread = null; } private void writeNotifyToFile(StatusBarNotification sbn) { if (!sbn.isClearable()) { return; } Log.i(TAG, "write notify message to file"); // 具有写入权限,否则不写入 CharSequence notificationTitle = null; CharSequence notificationText = null; CharSequence subText = null; Bundle extras = sbn.getNotification().extras; if (extras != null) { notificationTitle = extras.getCharSequence(Notification.EXTRA_TITLE); notificationText = extras.getCharSequence(Notification.EXTRA_TEXT); subText = extras.getCharSequence(Notification.EXTRA_SUB_TEXT); } String packageName = sbn.getPackageName(); String time = Utils.formatTime(Calendar.getInstance().getTime()); String writText = "\n" + "[" + time + "]" + "[" + packageName + "]" + "\n" + "[" + notificationTitle + "]" + "\n" + "[" + notificationText + "]" + "\n" + "[" + subText + "]" + "\n"; // 使用 post 异步的写入 Utils.putStr(this, writText); } /** * 通知服务器收款到账 */ public void appPush(int type, double price) { acquireWakeLock(this); SharedPreferences read = getSharedPreferences("vone", MODE_PRIVATE); host = read.getString("host", ""); key = read.getString("key", ""); Log.d(TAG, "onResponse push: 开始:" + type + " " + price); String t = String.valueOf(new Date().getTime()); String sign = md5(type + "" + price + t + key); final String url = "http://" + host + "/appPush?t=" + t + "&type=" + type + "&price=" + price + "&sign=" + sign; Log.d(TAG, "onResponse push: 开始:" + url); Request request = new Request.Builder().url(url).method("GET", null).build(); Call call = Utils.getOkHttpClient().newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onResponse push: 请求失败"); foregroundPost(url + "&force_push=true"); releaseWakeLock(); } @Override public void onResponse(Call call, Response response) throws IOException { try { Log.d(TAG, "onResponse push: " + response.body().string()); } catch (Exception e) { e.printStackTrace(); } // 如果返回状态不是成功的。同样要回调 if (!response.isSuccessful()) { foregroundPost(url + "&force_push=true"); } releaseWakeLock(); } }); } private void foregroundHeart(String url) { final Context context = NeNotificationService2.this; if (isRunning) { final JSONObject extraJson = new JSONObject(); try { extraJson.put("url", url); extraJson.put("show", false); } catch (JSONException jsonException) { jsonException.printStackTrace(); } handler.post(new Runnable() { @Override public void run() { enterForeground(context, context.getString(R.string.app_name), context.getString(R.string.app_is_heart), extraJson.toString()); } }); } } /** * 当通知失败的时候,前台强制通知 */ private void foregroundPost(String url) { final Context context = NeNotificationService2.this; if (isRunning) { final JSONObject extraJson = new JSONObject(); try { extraJson.put("url", url); extraJson.put("try_count", 5); } catch (JSONException jsonException) { jsonException.printStackTrace(); } handler.post(new Runnable() { @Override public void run() { enterForeground(context, context.getString(R.string.app_name), context.getString(R.string.app_is_post), extraJson.toString()); } }); } } /** * 如果出现无法通知的情况,进入前台,然后主动打开通知 */ public static void enterForeground(Context context, String title, String text, String extra) { if (context == null) return; Log.i(TAG, "enter fore ground"); Intent intent = new Intent(context, ForegroundServer.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(ForegroundServer.GET_NOTIFY_TITLE, title == null ? "" : title); intent.putExtra(ForegroundServer.GET_NOTIFY_TEXT, text == null ? "" : text); intent.putExtra(ForegroundServer.GET_NOTIFY_EXTRA, extra == null ? "" : extra); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intent); } else { context.startService(intent); } } public static void exitForeground(Context context) { if (context == null) return; Log.i(TAG, "exitForeground"); Intent intent1 = new Intent(); intent1.setAction(Constant.FINISH_FOREGROUND_SERVICE); context.sendBroadcast(intent1); } /** * 匹配支付宝的收款 元 * 为了兼容旧版,新版增加这个匹配 */ public static String getMoney2(String content) { Pattern compile = Pattern.compile("(\\d+\\.\\d+)元|(\\d+)元"); Matcher matcher = compile.matcher(content); if (matcher.find()) { String price = matcher.group(); return price.substring(0, price.lastIndexOf("元")); } else { return null; } } public static String getMoney(String content) { List ss = new ArrayList<>(); for (String sss : content.replaceAll(",", "") .replaceAll("[^0-9.]", ",").split(",")) { if (sss.length() > 0) ss.add(sss); } if (ss.size() < 1) { return null; } else { return ss.get(ss.size() - 1); } } public static String md5(String string) { if (TextUtils.isEmpty(string)) { return ""; } MessageDigest md5 = null; try { md5 = MessageDigest.getInstance("MD5"); byte[] bytes = md5.digest(string.getBytes()); StringBuilder result = new StringBuilder(); for (byte b : bytes) { String temp = Integer.toHexString(b & 0xff); if (temp.length() == 1) { temp = "0" + temp; } result.append(temp); } return result.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return ""; } } ================================================ FILE: app/src/main/java/com/vone/vmq/StartReceive.java ================================================ package com.vone.vmq; import static android.content.Context.POWER_SERVICE; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.PowerManager; import android.util.Log; import com.vone.qrcode.R; /** * Created by user68 on 2018/7/30. *

* 接收另一个app的广播启动本地服务 */ public class StartReceive extends BroadcastReceiver { public static final String START_SETTING_ACTIVITY_ACTION = "android.provider.Telephony.SECRET_CODE"; public static final String TRY_CLOSE_ACTIVITY_ACTION = "try_close_activity_action"; static boolean isBootCompleted = false; // 标志是否已经开机发送过通知 @Override public void onReceive(Context context, Intent intent) { Log.d("StartReceive", "start"); if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { if (!checkBatteryWhiteList(context)) { isBootCompleted = true; Utils.sendBatteryNotify(context); } NeNotificationService2.enterForeground(context, context.getString(R.string.app_name), context.getString(R.string.app_is_start), ""); } if (START_SETTING_ACTIVITY_ACTION.equals(intent.getAction())) { Log.d("StartReceive", "start"); Intent startActivityIntent = new Intent(context, MainActivity.class); startActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(startActivityIntent); } } static boolean checkBatteryWhiteList(Context context) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { PowerManager powerManager = (PowerManager) context.getSystemService(POWER_SERVICE); if (powerManager == null) return true; return powerManager.isIgnoringBatteryOptimizations(context.getPackageName()); } return true; } } ================================================ FILE: app/src/main/java/com/vone/vmq/Utils.java ================================================ package com.vone.vmq; import static android.content.Context.NOTIFICATION_SERVICE; import static android.content.Context.POWER_SERVICE; import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.PowerManager; import android.provider.Settings; import com.vone.qrcode.R; import com.vone.vmq.util.FileUtils; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeUnit; import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; class Utils { public final static String GET_MESSAGE_KEY = "get_message_key"; public static final String GET_SHOW_ACTIVITY_TYPE = "get_show_activity_type"; private final static String dayType = "yyyy-MM-dd HH:mm:ss"; private final static String hourType = "HH:mm:ss"; private static int notifyDay = -1; private static OkHttpClient okHttpClient; public static OkHttpClient getOkHttpClient() { if (okHttpClient == null) { synchronized (Utils.class) { if (okHttpClient == null) { okHttpClient = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .connectionPool(new ConnectionPool(0, 5, TimeUnit.SECONDS)) .build(); } } } return okHttpClient; } static void putStr(Context context, String value) { if (context == null) { return; } File notifycationFilePath = context.getExternalFilesDir("log"); if (notifycationFilePath == null || !canWrite(notifycationFilePath)) return; String notifycationFileName = "notifycation_file.txt"; File file = new File(notifycationFilePath, notifycationFileName); // 为了防止文件无限增大,只保留当天的数据 if (Calendar.getInstance().get(Calendar.DAY_OF_MONTH) != notifyDay) { if (file.exists() && file.canWrite()) { // 设置最大容量 1M 大小 if (file.length() > 1024 * 1024) { FileUtils fileUtils = new FileUtils(); if (fileUtils.deleteFileSafely(file)) { try { //noinspection ResultOfMethodCallIgnored file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } } } notifyDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH); } BufferedWriter out = null; try { out = new BufferedWriter(new FileWriter(file, true), 1024); out.write(value); } catch (IOException e) { e.printStackTrace(); } finally { if (out != null) { try { out.flush(); out.close(); } catch (IOException e) { e.printStackTrace(); } } } } private static boolean canWrite(File notifycationFilePath) { return notifycationFilePath.canWrite(); } static boolean checkBatteryWhiteList(Context context) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { PowerManager powerManager = (PowerManager) context.getSystemService(POWER_SERVICE); if (powerManager == null) return true; return powerManager.isIgnoringBatteryOptimizations(context.getPackageName()); } return true; } static void gotoBatterySetting(Context context) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { @SuppressLint("BatteryLife") Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse("package:" + context.getPackageName())); context.startActivity(intent); } } static String formatTime(Date time) { DateFormat dataFormat = new SimpleDateFormat(dayType, Locale.getDefault()); return dataFormat.format(time); } static String formatTimeSimple(Date time) { DateFormat dataFormat = new SimpleDateFormat(hourType, Locale.getDefault()); return dataFormat.format(time); } static void createNotificationChannel(Context context, String channelId, CharSequence channelName, int importance) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); channel.enableLights(true); channel.setShowBadge(true); channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); NotificationManager notificationManager = (NotificationManager) context .getSystemService(NOTIFICATION_SERVICE); if (notificationManager != null) { notificationManager.createNotificationChannel(channel); } } } static void sendNotifyMessage(Context context, String title, String text, int type) { if (type == 1) { String channelId = "MessageNotify"; int channelLevel = -1; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { channelLevel = NotificationManager.IMPORTANCE_HIGH; } int id = (channelId + System.currentTimeMillis()).hashCode(); String showTvText = String.format("%s\n%s", title, text); Intent intent = new Intent(context, MainActivity.class); intent.putExtra(GET_MESSAGE_KEY, showTvText); intent.putExtra(GET_SHOW_ACTIVITY_TYPE, type); sendNotify(context, channelId, "remote message notify", channelLevel, Notification.PRIORITY_HIGH, id, title, text, intent); } } static void sendBatteryNotify(Context context) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { String channelId = "BatteryNotify"; int channelLevel = -1; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { channelLevel = NotificationManager.IMPORTANCE_MAX; } @SuppressLint("BatteryLife") Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setData(Uri.parse("package:" + context.getPackageName())); int id = (channelId + System.currentTimeMillis()).hashCode(); sendNotify(context, channelId, "battery white list notify", channelLevel, Notification.PRIORITY_MAX, id, context.getString(R.string.app_name), context.getString(R.string.click_add_to_battery_white_list), intent); } } private static void sendNotify(Context context, String channelId, String channelName, int channelLevel, int priority, int id, String title, String text, Intent intent) { NotificationManager manager = (NotificationManager) context. getSystemService(NOTIFICATION_SERVICE); if (manager == null) return; Notification.Builder notificationBuild; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { if (channelLevel < 0) { channelLevel = NotificationManager.IMPORTANCE_HIGH; } Utils.createNotificationChannel(context, channelId , channelName , channelLevel); notificationBuild = new Notification.Builder(context, channelId); } else { notificationBuild = new Notification.Builder(context); } notificationBuild.setContentTitle(title) .setContentText(text) .setStyle(new Notification.BigTextStyle() .setBigContentTitle(title) .bigText(text)) .setWhen(System.currentTimeMillis()) .setShowWhen(true) .setSmallIcon(R.mipmap.ic_launcher) .setPriority(priority) .setAutoCancel(true); PendingIntent pendingIntent = PendingIntent.getActivity(context, id , intent, PendingIntent.FLAG_CANCEL_CURRENT); notificationBuild.setContentIntent(pendingIntent); // 正式发出通知 manager.notify(id, notificationBuild.build()); } } ================================================ FILE: app/src/main/java/com/vone/vmq/util/BitmapUtil.java ================================================ package com.vone.vmq.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; } } ================================================ FILE: app/src/main/java/com/vone/vmq/util/Constant.java ================================================ package com.vone.vmq.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"; } ================================================ FILE: app/src/main/java/com/vone/vmq/util/FileUtils.java ================================================ package com.vone.vmq.util; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; @SuppressWarnings("ALL") public class FileUtils { public String readFileToString(String filePath) { File file = new File(filePath); if (!file.exists()) return null; BufferedReader in = null; try { in = new BufferedReader(new FileReader(file)); StringBuilder readStringBuilder = new StringBuilder(); String currentLine; while ((currentLine = in.readLine()) != null) { readStringBuilder.append(currentLine); } return readStringBuilder.toString(); } catch (IOException e) { e.printStackTrace(); return null; } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } } public boolean putStringToFile(String path, String text) { File file = new File(path); if (file.exists() && (!deleteFileSafely(file))) return false; BufferedWriter out = null; try { out = new BufferedWriter(new FileWriter(file), 2048); out.write(text); return true; } catch (IOException e) { e.printStackTrace(); return false; } finally { if (out != null) { try { out.flush(); out.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * @param file 要删除的文件 */ public boolean deleteFileSafely(File file) { if (file != null && file.exists()) { File tmp = getTmpFile(file, System.currentTimeMillis(), -1); if (file.renameTo(tmp)) { // 将源文件重命名 return tmp.delete(); // 删除重命名后的文件 } else { return file.delete(); } } return false; } private File getTmpFile(File file, long time, int index) { File tmp; if (index == -1) { tmp = new File(file.getParent() + File.separator + time); } else { tmp = new File(file.getParent() + File.separator + time + "(" + index + ")"); } if (!tmp.exists()) { return tmp; } else { return getTmpFile(file, time, index >= 1000 ? index : ++index); } } } ================================================ FILE: app/src/main/java/com/vone/vmq/util/UriUtil.java ================================================ package com.vone.vmq.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; } } ================================================ FILE: app/src/main/res/layout/activity_lock_show.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================