From e8a2dbeed1a338df62ce4644a5fb9055e966e8d7 Mon Sep 17 00:00:00 2001 From: gallonyin Date: Mon, 20 Feb 2023 17:47:27 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E5=9B=BE=E5=83=8F=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E6=88=AA=E5=9B=BE=E5=92=8C=E5=89=8D=E5=8F=B0=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 9 + .../java/org/yameida/worktool/Constant.kt | 1 + .../org/yameida/worktool/MyApplication.kt | 3 + .../activity/GetScreenShotActivity.kt | 159 ++++++ .../worktool/activity/ListenActivity.kt | 23 + .../notification/PlayNotifyManager.kt | 36 ++ .../yameida/worktool/service/GlobalMethod.kt | 15 + .../worktool/service/PlayNotifyService.kt | 107 ++++ .../yameida/worktool/service/WeworkGetImpl.kt | 2 +- .../yameida/worktool/utils/ExtensionMethod.kt | 53 ++ .../worktool/utils/capture/AndroidUtils.kt | 128 +++++ .../worktool/utils/capture/BitmapUtil.kt | 92 ++++ .../yameida/worktool/utils/capture/Color.java | 103 ++++ .../yameida/worktool/utils/capture/Image.java | 463 ++++++++++++++++++ .../utils/capture/MediaProjectionHolder.kt | 28 ++ .../yameida/worktool/utils/capture/Point.java | 42 ++ .../utils/capture/ScreenCaptureUtil.java | 119 +++++ .../capture/ScreenCaptureUtilByMediaPro.java | 142 ++++++ app/src/main/res/values/styles.xml | 26 + 19 files changed, 1550 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/yameida/worktool/activity/GetScreenShotActivity.kt create mode 100644 app/src/main/java/org/yameida/worktool/notification/PlayNotifyManager.kt create mode 100644 app/src/main/java/org/yameida/worktool/service/PlayNotifyService.kt create mode 100644 app/src/main/java/org/yameida/worktool/utils/ExtensionMethod.kt create mode 100644 app/src/main/java/org/yameida/worktool/utils/capture/AndroidUtils.kt create mode 100644 app/src/main/java/org/yameida/worktool/utils/capture/BitmapUtil.kt create mode 100644 app/src/main/java/org/yameida/worktool/utils/capture/Color.java create mode 100644 app/src/main/java/org/yameida/worktool/utils/capture/Image.java create mode 100644 app/src/main/java/org/yameida/worktool/utils/capture/MediaProjectionHolder.kt create mode 100644 app/src/main/java/org/yameida/worktool/utils/capture/Point.java create mode 100644 app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtil.java create mode 100644 app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtilByMediaPro.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c7a5d5c..e109f16 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -70,6 +70,12 @@ android:windowSoftInputMode="adjustUnspecified|stateHidden" android:theme="@style/AppTheme"> + + , flags: Int = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP, i: Intent? = null) { + val intent = i ?: Intent(context, clazz) + intent.flags = flags + val pendingIntent = PendingIntent.getActivity(context, requestCode++, intent, 0) + try { + pendingIntent.send() + } catch (e: PendingIntent.CanceledException) { + e.printStackTrace() + context.startActivity(intent) + } +} + /** * 进入首页-消息页 */ diff --git a/app/src/main/java/org/yameida/worktool/service/PlayNotifyService.kt b/app/src/main/java/org/yameida/worktool/service/PlayNotifyService.kt new file mode 100644 index 0000000..5e89168 --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/service/PlayNotifyService.kt @@ -0,0 +1,107 @@ +package org.yameida.worktool.service + +import android.app.* +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.graphics.Color +import android.media.projection.MediaProjectionManager +import android.os.Binder +import android.os.Build +import android.os.IBinder +import android.os.SystemClock +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.blankj.utilcode.util.LogUtils +import com.blankj.utilcode.util.Utils +import org.yameida.worktool.R +import org.yameida.worktool.activity.GetScreenShotActivity +import org.yameida.worktool.utils.capture.MediaProjectionHolder + + +/** + * Created by Gallon on 2019/8/7. + */ +class PlayNotifyService : Service() { + private val TAG = PlayNotifyService::class.java.simpleName + + companion object { + private const val PLAY_NOTIFY_ID = 0x1216 + private const val CHANNEL_ONE_ID = "RECORD_CHANNEL_ONE_ID" + private const val CHANNEL_ONE_NAME = "RECORD_CHANNEL_ONE_NAME" + const val PLAY_NOTIFY = "record_notify" + const val PLAY_NOTIFY_CODE = "record_notify_code" + private var playNotifyReceiver: PlayNotifyReceiver = PlayNotifyReceiver() + } + private var context = Utils.getApp() + private var manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + private var notification: Notification? = null + + inner class PlayNotifyServiceBinder : Binder() { + fun getService() = this@PlayNotifyService + } + + override fun onBind(intent: Intent?): IBinder? = PlayNotifyServiceBinder() + + override fun onDestroy() { + super.onDestroy() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + LogUtils.d(TAG, "onStartCommand: $intent") + show(null) + initBroadcastReceivers() + if (intent?.getBooleanExtra("setMediaProject", false) == true) { + val resultCode = intent.getIntExtra("code", -1) + val data = intent.getParcelableExtra("data") + val mediaProjectionManager = Utils.getApp().getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager + MediaProjectionHolder.setMediaProjection(mediaProjectionManager.getMediaProjection(resultCode, data!!)) + } + return super.onStartCommand(intent, flags, startId) + } + + fun show(view: RemoteViews?) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel(CHANNEL_ONE_ID, CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_DEFAULT) + channel.enableLights(true) //是否在桌面icon右上角展示小红点 + channel.lightColor = Color.GREEN //小红点颜色 + channel.setShowBadge(true) //是否在久按桌面图标时显示此渠道的通知 + channel.setSound(null, null) //静音 NOTE:首次安装生效 否则需要APP清除数据生效 + manager.createNotificationChannel(channel) + } + val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ONE_ID) + .setCustomContentView(view) + .setWhen(System.currentTimeMillis()) + .setAutoCancel(false) + .setOngoing(true) + .setSmallIcon(R.mipmap.ic_launcher) + .setOngoing(true) + notification = notificationBuilder.build() + startForeground(PLAY_NOTIFY_ID, notification) + } + + private fun initBroadcastReceivers() { + val filter = IntentFilter(PLAY_NOTIFY) + context.registerReceiver(playNotifyReceiver, filter) + } + + private class PlayNotifyReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + val code = intent.getIntExtra(PLAY_NOTIFY_CODE, -1) + if (code == -1) { + return + } + + LogUtils.d("notification click: $code") + if (MediaProjectionHolder.mMediaProjection != null) { + SystemClock.sleep(300) + GetScreenShotActivity.startCapture() + } else { + fastStartActivity(context, GetScreenShotActivity::class.java) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt b/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt index 179b6fb..cd3ba01 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt @@ -256,7 +256,7 @@ object WeworkGetImpl { for (i in 0 until list.childCount) { val item = list.getChild(i) val tvList = AccessibilityUtil.findAllOnceByClazz(item, Views.TextView) - val textList = tvList.filter { it.text != null }.map { it.text.toString() } + val textList = tvList.mapNotNull { it.text?.toString() } if (textList.isNotEmpty()) { corpList.add(textList[0]) } diff --git a/app/src/main/java/org/yameida/worktool/utils/ExtensionMethod.kt b/app/src/main/java/org/yameida/worktool/utils/ExtensionMethod.kt new file mode 100644 index 0000000..ac6049f --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/ExtensionMethod.kt @@ -0,0 +1,53 @@ +package org.yameida.worktool.utils + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Build +import android.view.View +import com.blankj.utilcode.util.EncryptUtils + + + +fun getFileMD5(file: java.io.File): String = file.getMD5() + +fun java.io.File.getMD5(): String { + val digest = EncryptUtils.encryptMD5File(this) + val strHex = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f") + val sb = StringBuilder() + for (byte in digest) { + var d = byte.toInt() + if (d < 0) d += 256 + val d1 = d / 16 + val d2 = d % 16 + sb.append(strHex[d1] + strHex[d2]) + } + return sb.toString() +} + +fun Long.toTimeString(): String { + val i = this.toInt() / 1000 + return String.format("%02d:%02d", Integer.valueOf(i / 60), Integer.valueOf(i % 60)) +} + +fun Context.startServiceSafe(intent: Intent) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(intent) + } else { + startService(intent) + } +} + +fun Activity.hideBottomNav() { + val decorView = this.window.decorView + decorView.systemUiVisibility = 0 + val uiOptions = (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) + decorView.setSystemUiVisibility(uiOptions) +} + +fun Activity.showBottomNav() { + val decorView = this.window.decorView + decorView.systemUiVisibility = 0 +} diff --git a/app/src/main/java/org/yameida/worktool/utils/capture/AndroidUtils.kt b/app/src/main/java/org/yameida/worktool/utils/capture/AndroidUtils.kt new file mode 100644 index 0000000..74c9c48 --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/capture/AndroidUtils.kt @@ -0,0 +1,128 @@ +package org.yameida.worktool.utils.capture + +import android.app.ActivityManager +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.graphics.drawable.StateListDrawable +import android.media.MediaMetadataRetriever +import android.os.Process +import android.text.TextUtils +import android.view.View +import android.view.inputmethod.InputMethodManager +import com.blankj.utilcode.util.LogUtils +import com.blankj.utilcode.util.Utils +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream +import java.text.SimpleDateFormat +import java.util.* + +/** + * Created by gallon on 2019/7/9. + */ +class AndroidUtils { + + companion object { + private val TAG = AndroidUtils::class.java.simpleName + + @JvmStatic + fun hideKeyboard(view: View?) { + if (view != null) { + val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + if (imm.isActive) { + imm.hideSoftInputFromWindow(view.windowToken, 0) + } + } + } + + @JvmStatic + fun getMediaDurationTime(filePath: String): Long { + var durationTime: Long = 0 + val retriever = MediaMetadataRetriever() + try { + retriever.setDataSource(filePath) + val v1 = retriever.extractMetadata(9) + if (TextUtils.isEmpty(v1)) { + LogUtils.d(TAG, "Extract metadata failed.") + } + durationTime = java.lang.Long.parseLong(v1) + } catch (e: Exception) { + e.printStackTrace() + LogUtils.d(TAG, "exception = " + e.message) + } + + try { + retriever.release() + } catch (e: Exception) { + LogUtils.d(TAG, "retriever.release() Exception = " + e.message) + } + + return durationTime + } + + @JvmStatic + fun hasPermission(context: Context, permission: String): Boolean { + return try { + context.checkPermission(permission, Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED + } catch (e: SecurityException) { + false + } + + } + + @JvmStatic + fun getTimeFileName(time: Long, type: String): String { + return try { + SimpleDateFormat(type).format(Date(time)) + } catch (e: Exception) { + "" + } + + } + + @JvmStatic + fun getRunningAppProcesses(context: Context, packageName: String): Boolean { + val appProcesses = (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses ?: return false + for (appProcess in appProcesses) { + if (appProcess.importance == 100 && appProcess.processName == packageName) { + return true + } + } + return false + } + + @JvmStatic + fun setPressedBg(view: View, normal: Drawable?, focused: Drawable?, pressed: Drawable?) { + val bg = StateListDrawable() + val states = arrayOfNulls(6) + states[0] = intArrayOf(16842919, 16842910) + states[1] = intArrayOf(16842910, 16842908) + states[2] = intArrayOf(16842910) + states[3] = intArrayOf(16842908, 16842909) + states[4] = intArrayOf(16842909) + bg.addState(states[0], pressed) + bg.addState(states[3], focused) + bg.addState(states[2], normal) + view.background = bg + } + + fun bitmapToFile(bitmap: Bitmap, fileName: String, path: String = "screenShot"): String { + val dir = File(Utils.getApp().getExternalFilesDir("capture"), path) + if (!dir.exists()) { + dir.mkdirs() + } + val myCaptureFile = File(dir, fileName) + if (!myCaptureFile.exists()) { + myCaptureFile.createNewFile() + } + val bos = BufferedOutputStream(FileOutputStream(myCaptureFile)) + bitmap.compress(Bitmap.CompressFormat.JPEG, 75, bos) + bos.flush() + bos.close() + return myCaptureFile.absolutePath + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/yameida/worktool/utils/capture/BitmapUtil.kt b/app/src/main/java/org/yameida/worktool/utils/capture/BitmapUtil.kt new file mode 100644 index 0000000..fd0ca16 --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/capture/BitmapUtil.kt @@ -0,0 +1,92 @@ +package org.yameida.worktool.utils.capture + +import android.graphics.BitmapFactory +import android.graphics.Bitmap +import android.provider.MediaStore +import android.app.Activity +import android.graphics.Matrix +import android.media.ExifInterface +import android.net.Uri +import java.io.File +import java.io.IOException + + +/** + * Created by Gallon on 2019/9/1. + */ +object BitmapUtil { + + /** + * 获取图片的旋转角度 + * + * @param path 图片绝对路径 + * @return 图片的旋转角度 + */ + fun getBitmapDegree(path: String): Int { + var degree = 0 + try { + // 从指定路径下读取图片,并获取其EXIF信息 + val exifInterface = ExifInterface(path) + // 获取图片的旋转信息 + val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) + when (orientation) { + ExifInterface.ORIENTATION_ROTATE_90 -> degree = 90 + ExifInterface.ORIENTATION_ROTATE_180 -> degree = 180 + ExifInterface.ORIENTATION_ROTATE_270 -> degree = 270 + } + } catch (e: IOException) { + e.printStackTrace() + } + + return degree + } + + /** + * 将图片按照指定的角度进行旋转 + * + * @param bitmap 需要旋转的图片 + * @param degree 指定的旋转角度 + * @return 旋转后的图片 + */ + fun rotateBitmapByDegree(bitmap: Bitmap, degree: Float): Bitmap { + // 根据旋转角度,生成旋转矩阵 + val matrix = Matrix() + matrix.postRotate(degree) + // 将原始图片按照旋转矩阵进行旋转,并得到新的图片 + val newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) + if (!bitmap.isRecycled) { + bitmap.recycle() + } + return newBitmap + } + + /** + * 获取我们需要的整理过旋转角度的Uri + * @param activity 上下文环境 + * @param path 路径 + * @return 正常的Uri + */ + fun getRotatedUri(activity: Activity, path: String): Uri { + val degree = getBitmapDegree(path) + if (degree != 0) { + val bitmap = BitmapFactory.decodeFile(path) + val newBitmap = rotateBitmapByDegree(bitmap, degree.toFloat()) + return Uri.parse(MediaStore.Images.Media.insertImage(activity.contentResolver, newBitmap, null, null)) + } else { + return Uri.fromFile(File(path)) + } + } + + /** + * 将图片按照指定的角度进行旋转 + * + * @param path 需要旋转的图片的路径 + * @param degree 指定的旋转角度 + * @return 旋转后的图片 + */ + fun rotateBitmapByDegree(path: String, degree: Float): Bitmap { + val bitmap = BitmapFactory.decodeFile(path) + return rotateBitmapByDegree(bitmap, degree) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/yameida/worktool/utils/capture/Color.java b/app/src/main/java/org/yameida/worktool/utils/capture/Color.java new file mode 100644 index 0000000..d4f18d9 --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/capture/Color.java @@ -0,0 +1,103 @@ +package org.yameida.worktool.utils.capture; + +public class Color { + private int R = 0; + private int G = 0; + private int B = 0; + + + public Color(int r, int g, int b) { + R = r; + G = g; + B = b; + } + + public Color(int b, int g, int r, Boolean bgr) { + if (bgr) { + R = r; + G = g; + B = b; + } else { + R = b; + G = g; + B = r; + } + } + + public Color(int color) { + R = android.graphics.Color.red(color); + G = android.graphics.Color.green(color); + B = android.graphics.Color.blue(color); + } + + public Color() { + } + + public int getR() { + return R; + } + + public void setR(int r) { + R = r; + } + + public int getG() { + return G; + } + + public void setG(int g) { + G = g; + } + + public int getB() { + return B; + } + + public void setB(int b) { + B = b; + } + + + public int[] getDexRGB() { + return new int[]{R, G, B}; + } + + public int[] getDexBGR() { + return new int[]{B, G, R}; + } + + + /** + * 判断2个颜色是否相同,由于图像渲染叠加,同一个icon可能每次渲染的色值不完全相同,所以判断的时候加入了误差 + * + * @param color1 + * @param color2 + * @return + */ + public static boolean isSame(Color color1, Color color2) { + return isSame(color1, color2, 30); + } + + /** + * 判断颜色是否相同,自定义误差 + * + * @param color1 + * @param color2 + * @param offset + * @return + */ + public static boolean isSame(Color color1, Color color2, int offset) { + + // 算法: R G B这3个通道的色值差的绝对值之和小于offset + return (Math.abs(color1.getR() - color2.getR()) + Math.abs(color1.getG() - color2.getG()) + Math.abs(color1.getB() - color2.getB())) <= offset; + } + + public boolean equals(Color obj) { + return obj.getR() == R && obj.getG() == G && obj.getB() == B; + } + + @Override + public String toString() { + return "" + R + "," + G + "," + B; + } +} diff --git a/app/src/main/java/org/yameida/worktool/utils/capture/Image.java b/app/src/main/java/org/yameida/worktool/utils/capture/Image.java new file mode 100644 index 0000000..728472d --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/capture/Image.java @@ -0,0 +1,463 @@ +package org.yameida.worktool.utils.capture; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.util.Base64; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; + +import com.blankj.utilcode.util.LogUtils; + +//import org.opencv.android.Utils; +//import org.opencv.core.Core; +//import org.opencv.core.CvType; +//import org.opencv.core.Mat; +//import org.opencv.imgproc.Imgproc; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.LinkedList; + + +public class Image { + + /** + * 保存图片到图库 + * Image.saveImageToGallery(bt, getExternalFilesDir("").getAbsolutePath() + "/asdf.png"); + * + * @param bmp + */ + public static void saveImageToGallery(Bitmap bmp, String bitName) { + File file = new File(bitName); + try { + FileOutputStream fos = new FileOutputStream(file); + bmp.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.flush(); + fos.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + /** + * 打开本地图片 + * + * @param path + * @return + */ + public static Bitmap openImg(String path) { + + Bitmap ret = BitmapFactory.decodeFile(path); + + if (ret == null) { + LogUtils.e("打开" + path + "失败!"); + } + + return ret; + } + + + /** + * 单点找色 + * + * @param img + * @param color + * @return + */ + public static LinkedList findPoint(Bitmap img, Color color) { + LinkedList pl = new LinkedList(); + int width = img.getWidth(); + int height = img.getHeight(); + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + if (Color.isSame(getPoint(img, i, j), color)) { + pl.add(new Point(i, j)); + } + } + } + return pl; + } + + + /** + * 多色找点 + * 在屏幕某个范围内 + * + * @param img + * @param colorRules + * @param leftX + * @param leftY + * @param rightX + * @param rightY + * @return + */ + public static Point findPointByMulColor(Bitmap img, String colorRules, int leftX, int leftY, int rightX, int rightY) { + img = cropBitmap(img, leftX, leftY, rightX, rightY); + Point p = findPointByMulColor(img, colorRules); + if (p.isEmpty()) { + return p; + } + return new Point(p.getX() + leftX, p.getY() + leftY); + } + + /** + * 多色找点函数 + * + * @param img + * @param colorRules + * @return + */ + public static Point findPointByMulColor(Bitmap img, String colorRules) { + long now = System.currentTimeMillis(); + int[] colors = new int[img.getWidth() * img.getHeight()]; + String[] res = colorRules.split(","); + Color firstPointColor = HexColor2DecColor(res[0], true); + img.getPixels(colors, 0, img.getWidth(), 0, 0, img.getWidth(), img.getHeight()); + for (int i = 0; i < colors.length; i++) { + if (Color.isSame(new Color(colors[i]), firstPointColor)) { + int y = (int) (i / img.getWidth()); + int x = i % img.getWidth(); + for (int k = 1; k < res.length; k++) { + res[k] = res[k].replace("\"", ""); + String[] info = res[k].split("\\|"); + int testX = x + Integer.parseInt(info[0]); + int testY = y + Integer.parseInt(info[1]); + if (testX < 0 || testY < 0 || testX > img.getWidth() || testY > img.getHeight()) { + break; + } + Color nextColor = getPoint(img, testX, testY); + if (!Color.isSame(nextColor, HexColor2DecColor(info[2], true))) { + break; + } else { + if (k == (res.length - 1)) { + LogUtils.i("找点用时:", String.valueOf(System.currentTimeMillis() - now)); + return new Point(x, y); + } + } + } + } + } + return new Point(-1, -1); + } + + /** + * 多色找点,自定义颜色误差 + * + * @param img + * @param colorRules + * @param offset + * @return + */ + public static Point findPointByMulColor(Bitmap img, String colorRules, int offset) { + long now = System.currentTimeMillis(); + // 将图像转换成颜色数组 + int[] colors = new int[img.getWidth() * img.getHeight()]; + String[] res = colorRules.split(","); + Color firstPointColor = HexColor2DecColor(res[0], true); + img.getPixels(colors, 0, img.getWidth(), 0, 0, img.getWidth(), img.getHeight()); + //遍历颜色数组 + for (int i = 0; i < colors.length; i++) { + // 寻找规则中第一个点 + if (Color.isSame(new Color(colors[i]), firstPointColor, offset)) { + // 第一个点的y坐标 + int y = (int) (i / img.getWidth()); + // 第一个点的x坐标 + int x = i % img.getWidth(); + // 检查规则中后续每个点 + for (int k = 1; k < res.length; k++) { + //处理规则中多余的引号 + res[k] = res[k].replace("\"", ""); + String[] info = res[k].split("\\|"); + int testX = x + Integer.parseInt(info[0]); + int testY = y + Integer.parseInt(info[1]); + //超出图片范围 + if (testX < 0 || testY < 0 || testX > img.getWidth() || testY > img.getHeight()) { + break; + } + Color nextColor = getPoint(img, testX, testY); + if (!Color.isSame(nextColor, HexColor2DecColor(info[2], true), offset)) { + break; + } else { + if (k == (res.length - 1)) { + return new Point(x, y); + } + } + } + } + } + LogUtils.i("找点用时:", String.valueOf(System.currentTimeMillis() - now)); + return new Point(-1, -1); + } + + /** + * 已废弃 + * + * @param img + * @param colorRules + * @return + * @deprecated + */ + public static Point findPointByMulColorBack(Bitmap img, String colorRules) { + long now = System.currentTimeMillis(); + String[] res = colorRules.split(","); + Color firstPointColor = HexColor2DecColor(res[0], true); + int imgWidth = img.getWidth(); + int imgHeight = img.getHeight(); + for (int i = 0; i < imgWidth; i++) { + for (int j = 0; j < imgHeight; j++) { + if (Color.isSame(getPoint(img, i, j), firstPointColor)) { + for (int k = 1; k < res.length; k++) { + res[k] = res[k].replace("\"", ""); + String[] info = res[k].split("\\|"); + int testX = i + Integer.parseInt(info[0]); + int testY = j + Integer.parseInt(info[1]); + if (testX < 0 || testY < 0 || testX > imgWidth || testY > imgHeight) { + break; + } + Color nextColor = getPoint(img, testX, testY); + if (!Color.isSame(nextColor, HexColor2DecColor(info[2], true))) { + break; + } else { + if (k == (res.length - 1)) { + LogUtils.i("找点用时:", String.valueOf(System.currentTimeMillis() - now)); + return new Point(i, j); + } + } + } + } + } + } + LogUtils.i("找点用时:", String.valueOf(System.currentTimeMillis() - now)); + return new Point(-1, -1); + } + + + /** + * 多色找点,返回屏幕内全部满足规则的点 + * + * @param img + * @param colorRules + * @return + */ + public static LinkedList findPointsByMulColor(Bitmap img, String colorRules) { + LinkedList ret = new LinkedList(); + String[] res = colorRules.split(","); + Color firstPointColor = HexColor2DecColor(res[0], true); + int imgWidth = img.getWidth(); + int imgHeight = img.getHeight(); + + for (int i = 0; i < imgWidth; i++) { + for (int j = 0; j < imgHeight; j++) { + if (Color.isSame(getPoint(img, i, j), firstPointColor)) { + for (int k = 1; k < res.length; k++) { + res[k] = res[k].replace("\"", ""); + String[] info = res[k].split("\\|"); + int testX = i + Integer.parseInt(info[0]); + int testY = j + Integer.parseInt(info[1]); + if (testX < 0 || testY < 0 || testX > imgWidth || testY > imgHeight) { + break; + } + Color nextColor = getPoint(img, testX, testY); + if (!Color.isSame(nextColor, HexColor2DecColor(info[2], true))) { + break; + } else { + if (k == (res.length - 1)) { + ret.add(new Point(i, j)); + } + } + } + } + } + } + return ret; + } + + + /** + * @param color + * @return + */ + public static Color HexColor2DecColor(String color) { + color = color.replace("#", ""); + color = color.replace("\"", ""); + try { + int r = Integer.parseInt(color.substring(0, 2), 16); + int g = Integer.parseInt(color.substring(2, 4), 16); + int b = Integer.parseInt(color.substring(4, 6), 16); + return new Color(r, g, b); + + } catch (Exception e) { + return new Color(); + } + } + + /** + * @param color + * @param bgr + * @return + */ + public static Color HexColor2DecColor(String color, boolean bgr) { + color = color.replace("#", ""); + color = color.replace("\"", ""); + try { + int b = Integer.parseInt(color.substring(0, 2), 16); + int g = Integer.parseInt(color.substring(2, 4), 16); + int r = Integer.parseInt(color.substring(4, 6), 16); + return new Color(r, g, b); + + } catch (Exception e) { + return new Color(); + } + } + + + /** + * 获取一个点的颜色 + * + * @param img + * @param x + * @param y + * @return + */ + public static Color getPoint(Bitmap img, int x, int y) { + try { + return new Color(img.getPixel(x, y)); + } catch (IllegalArgumentException e) { + return new Color(0, 0, 0); + } + + + } + + + /** + * 预览图片 + * + * @param img + * @param context + */ +// public static void show(Bitmap img, Context context) { +// Dialog dia = new Dialog(context, R.style.edit_AlertDialog_style2); +// dia.setContentView(R.layout.activity_start_dialog); +// ImageView imageView = (ImageView) dia.findViewById(R.id.start_img); +// imageView.setImageBitmap(img); +// dia.show(); +// +// dia.setCanceledOnTouchOutside(true); // Sets whether this dialog is +// Window w = dia.getWindow(); +// WindowManager.LayoutParams lp = w.getAttributes(); +// lp.x = 0; +// lp.y = 40; +// dia.onWindowAttributesChanged(lp); +// } + + + /** + * 裁剪 + * + * @param bitmap + * @param leftTopX + * @param leftTopY + * @param rightBottomX + * @param rightBottomY + * @return + */ + public static Bitmap cropBitmap(Bitmap bitmap, int leftTopX, int leftTopY, int rightBottomX, int rightBottomY) { + return Bitmap.createBitmap(bitmap, leftTopX, leftTopY, rightBottomX - leftTopX, rightBottomY - leftTopY, null, false); + } + + + /** + * base64 图片 + * + * @param bitmap + * @return + */ + public static String encodeImage(Bitmap bitmap) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + //读取图片到ByteArrayOutputStream + bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); //参数如果为100那么就不压缩 + byte[] bytes = baos.toByteArray(); + + return Base64.encodeToString(bytes, Base64.DEFAULT); + + + } + + + /** + * 模板匹配 + * + * @param srcImg //源图像 + * @param templateImg //模板图像 + * @param threshold //相识度阈值,阈值调小可以一定程度解决不同手机分辨率的问题 + * @return //如果没有找到则返回(-1,-1)点 + */ +// public static Point matchTemplate(Bitmap srcImg, Bitmap templateImg, double threshold) { +// +// if (threshold <= 0) { +// threshold = 0.5; +// } +// +// +// Mat tpl = new Mat(); +// Mat src = new Mat(); +// Utils.bitmapToMat(srcImg, src); +// Utils.bitmapToMat(templateImg, tpl); +// +// +// int height = src.rows() - tpl.rows() + 1; +// int width = src.cols() - tpl.cols() + 1; +// Mat result = new Mat(height, width, CvType.CV_32FC1); +// int method = Imgproc.TM_CCOEFF_NORMED; +// Imgproc.matchTemplate(src, tpl, result, method); +// Core.MinMaxLocResult minMaxResult = Core.minMaxLoc(result); +// org.opencv.core.Point maxloc = minMaxResult.maxLoc; +// if (minMaxResult.maxVal < threshold) { +// return new Point(-1, -1); +// } +// org.opencv.core.Point minloc = minMaxResult.minLoc; +// org.opencv.core.Point matchloc = null; +// matchloc = maxloc; +// return new Point((int) matchloc.x, (int) matchloc.y); +// +// } + + + /** + * 根据给定的宽和高进行resize + * + * @param origin 原图 + * @param newWidth 新图的宽 + * @param newHeight 新图的高 + * @return new Bitmap + */ + public static Bitmap resize(Bitmap origin, int newWidth, int newHeight) { + if (origin == null) { + return null; + } + int height = origin.getHeight(); + int width = origin.getWidth(); + float scaleWidth = ((float) newWidth) / width; + float scaleHeight = ((float) newHeight) / height; + Matrix matrix = new Matrix(); + matrix.postScale(scaleWidth, scaleHeight);// 使用后乘 + Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false); + if (!origin.isRecycled()) { + origin.recycle(); + } + return newBM; + } + +} diff --git a/app/src/main/java/org/yameida/worktool/utils/capture/MediaProjectionHolder.kt b/app/src/main/java/org/yameida/worktool/utils/capture/MediaProjectionHolder.kt new file mode 100644 index 0000000..2974e68 --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/capture/MediaProjectionHolder.kt @@ -0,0 +1,28 @@ +package org.yameida.worktool.utils.capture + +import android.media.projection.MediaProjection +import android.os.Handler +import android.os.Looper + +/** + * Created by Gallon on 2019/8/4. + */ +class MediaProjectionHolder { + + companion object { + var mMediaProjection: MediaProjection? = null + + @Synchronized + fun setMediaProjection(mediaProjection: MediaProjection) { + if (mMediaProjection == null) { + mMediaProjection = mediaProjection + mediaProjection.registerCallback(object : MediaProjection.Callback() { + override fun onStop() { + mMediaProjection = null + } + }, Handler(Looper.getMainLooper())) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/yameida/worktool/utils/capture/Point.java b/app/src/main/java/org/yameida/worktool/utils/capture/Point.java new file mode 100644 index 0000000..3a2079f --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/capture/Point.java @@ -0,0 +1,42 @@ +package org.yameida.worktool.utils.capture; + +public class Point { + + private int x = 0; + private int y = 0; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + public Point() { + } + + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } + + public boolean isEmpty() { + return x < 0 || y < 0; + } + + + @Override + public String toString() { + return "{" + x + "," + y + "}"; + } +} diff --git a/app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtil.java b/app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtil.java new file mode 100644 index 0000000..72aa0cb --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtil.java @@ -0,0 +1,119 @@ +package org.yameida.worktool.utils.capture; + + +import android.content.res.Configuration; +import android.graphics.Bitmap; + +import com.blankj.utilcode.util.LogUtils; +import com.blankj.utilcode.util.Utils; + + +/** + * 截图工具类 + */ +public class ScreenCaptureUtil { + + private static int screenOrientation; // 0 未设置 ,1竖屏 2横批 + + + /** + * 强制设置为横屏模式 + */ + public static void setHorizontalScreen() { + ScreenCaptureUtil.screenOrientation = 2; + } + + + /** + * 强制设置为竖屏模式 + */ + public static void setVerticalScreen() { + ScreenCaptureUtil.screenOrientation = 1; + } + + + /** + * 获取屏幕图像 + * + * @return + */ + public static Bitmap getScreenCap() { + Bitmap o_img; + boolean retry = false; + do { + if (retry) { + LogUtils.i("资源已被回收,尝试重试!"); + } + if (ScreenCaptureUtil.screenOrientation == 0) { + if (Utils.getApp().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + //竖屏 + o_img = ScreenCaptureUtilByMediaPro.getScreenCapVertical(); + } else { + //横屏 + o_img = ScreenCaptureUtilByMediaPro.getScreenCapHorizontal(); + } + } else if (ScreenCaptureUtil.screenOrientation == 1) { + o_img = ScreenCaptureUtilByMediaPro.getScreenCapVertical(); + } else { + o_img = ScreenCaptureUtilByMediaPro.getScreenCapHorizontal(); + } + retry = true; + } while (o_img == null || o_img.isRecycled()); + + + return Bitmap.createBitmap(o_img); + + +// 使用adb方式截图,性能低下,已废弃 +// if (System.currentTimeMillis() - getScreenTime < 1000) { +// return screenCache; +// } +// byte[] tempBuffer = new byte[100 * 1024 * 1024]; +// StringBuilder buffer = new StringBuilder(100 * 1024 * 1024); +// +// Process exec = null; +// try { +// exec = Runtime.getRuntime().exec("su -c /system/bin/screencap -p"); +// +// +// final InputStream inputStream = exec.getInputStream(); +// BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); +// //清空缓存内容 +// buffer.setLength(0); +// int count; +// while ((count = bufferedInputStream.read(tempBuffer)) > 0) { +// buffer.append(new String(tempBuffer, 0, count, StandardCharsets.ISO_8859_1)); +// } +// bufferedInputStream.close(); +// final int retCode = exec.waitFor(); +// exec.destroy(); +// tempBuffer = buffer.toString().getBytes(StandardCharsets.ISO_8859_1); +// screenCache = BitmapFactory.decodeByteArray(tempBuffer, 0, tempBuffer.length); +// getScreenTime = System.currentTimeMillis(); +// return screenCache; +// } catch (IOException e) { +// e.printStackTrace(); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// throw new NullPointerException("截图失败"); + + + } + + /** + * 指定范围截图 + * + * @param leftX + * @param leftY + * @param rigthX + * @param rightY + * @return + */ + public static Bitmap getScreenCap(int leftX, int leftY, int rigthX, int rightY) { + Bitmap bitmap = getScreenCap(); + return Image.cropBitmap(bitmap, leftX, leftY, rigthX, rightY); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtilByMediaPro.java b/app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtilByMediaPro.java new file mode 100644 index 0000000..b1f85fc --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtilByMediaPro.java @@ -0,0 +1,142 @@ +package org.yameida.worktool.utils.capture; + + +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.PixelFormat; +import android.hardware.display.DisplayManager; +import android.media.ImageReader; +import android.media.projection.MediaProjection; +import android.media.projection.MediaProjectionManager; +import android.os.Handler; + +import com.blankj.utilcode.util.ScreenUtils; + +import java.nio.ByteBuffer; + + +/** + * 截图的底层实现类 + * 这个类的方法不要在业务中直接调用 + * 业务中使用ScreenCaptureUtil类 + * + * @deprecated + */ +public class ScreenCaptureUtilByMediaPro { + public static MediaProjectionManager mProjectionManager; + public static Intent data; + public static int resultCode; + private static MediaProjection sMediaProjection; + private static ImageReader mImageReaderHorizontal; + private static ImageReader mImageReaderVertical; + private static Handler backgroundHandler; + + private static Bitmap bitmapCacheHorizontal; + private static Bitmap bitmapCacheVertical; + + public static void init() { + sMediaProjection = mProjectionManager.getMediaProjection(resultCode, data); + //start capture reader + mImageReaderHorizontal = ImageReader.newInstance(ScreenUtils.getScreenWidth(), ScreenUtils.getScreenHeight(), + PixelFormat.RGBA_8888, 2); + sMediaProjection.createVirtualDisplay( + "ScreenShot", + ScreenUtils.getScreenWidth(), + ScreenUtils.getScreenHeight(), + ScreenUtils.getScreenDensityDpi(), + DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, + mImageReaderHorizontal.getSurface(), + null, + null); + + + mImageReaderVertical = ImageReader.newInstance(ScreenUtils.getScreenHeight(), ScreenUtils.getScreenWidth(), + PixelFormat.RGBA_8888, 2); + sMediaProjection.createVirtualDisplay( + "ScreenShot", + ScreenUtils.getScreenHeight(), + ScreenUtils.getScreenWidth(), + ScreenUtils.getScreenDensityDpi(), + DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, + mImageReaderVertical.getSurface(), + null, + null); + + + } + + + /** + * 勿直接使用!! + * + * @return + */ + @Deprecated + public static Bitmap getScreenCapHorizontal() { + + Bitmap bitmap; + android.media.Image image; + do { + image = mImageReaderHorizontal.acquireLatestImage(); + if (image == null && bitmapCacheHorizontal != null) { + return bitmapCacheHorizontal; + } + } while (image == null); + bitmap = covetBitmap(image); + if (bitmapCacheHorizontal != null && !bitmapCacheHorizontal.isRecycled()) { + bitmapCacheHorizontal.recycle(); + bitmapCacheHorizontal = null; + } + bitmapCacheHorizontal = bitmap; + return bitmap; + } + + + /** + * 勿直接使用!! + * + * @return + */ + @Deprecated + public static Bitmap getScreenCapVertical() { + + Bitmap bitmap; + android.media.Image image; + do { + image = mImageReaderVertical.acquireLatestImage(); + if (image == null && bitmapCacheVertical != null) { + return bitmapCacheVertical; + } + } while (image == null); + bitmap = covetBitmap(image); + if (bitmapCacheVertical != null) { + bitmapCacheVertical.recycle(); + bitmapCacheVertical = null; + } + bitmapCacheVertical = bitmap; + return bitmap; + } + + + public static Bitmap covetBitmap(android.media.Image image) { + int width = image.getWidth(); + int height = image.getHeight(); + final android.media.Image.Plane[] planes = image.getPlanes(); + final ByteBuffer buffer = planes[0].getBuffer(); + //每个像素的间距 + int pixelStride = planes[0].getPixelStride(); + //总的间距 + int rowStride = planes[0].getRowStride(); + int rowPadding = rowStride - pixelStride * width; + Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888); + bitmap.copyPixelsFromBuffer(buffer); + bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height); + image.close(); + return bitmap; + } + + + public static void stop() { + sMediaProjection.stop(); + } +} diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c4aec85..5e5aa8a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -61,4 +61,30 @@ + + + \ No newline at end of file