应用权限框架更新,重新调试剪裁文件流程。
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Wed Dec 10 22:51:19 GMT 2025
|
||||
#Thu Dec 11 00:57:37 GMT 2025
|
||||
stageCount=15
|
||||
libraryProject=
|
||||
baseVersion=15.12
|
||||
publishVersion=15.12.14
|
||||
buildCount=5
|
||||
buildCount=13
|
||||
baseBetaVersion=15.12.15
|
||||
|
||||
@@ -44,7 +44,6 @@ public class App extends GlobalApplication {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
setIsDebugging(BuildConfig.DEBUG);
|
||||
PermissionUtils.init(this);
|
||||
|
||||
// 初始化活动窗口管理
|
||||
WinBoLLActivityManager.init(this);
|
||||
|
||||
@@ -58,7 +58,7 @@ public class MainActivity extends WinBoLLActivity {
|
||||
|
||||
// ======================== 静态常量(移除缓存相关键)========================
|
||||
public static final String TAG = "MainActivity";
|
||||
private static final int REQUEST_WRITE_STORAGE_PERMISSION = 1001;
|
||||
private static final int REQUEST_READ_MEDIA_IMAGES = 1001;
|
||||
public static final int MSG_RELOAD_APPCONFIG = 0;
|
||||
public static final int MSG_CURRENTVALUEBATTERY = 1;
|
||||
public static final int MSG_LOAD_BACKGROUND = 2;
|
||||
@@ -123,10 +123,8 @@ public class MainActivity extends WinBoLLActivity {
|
||||
loadNonCriticalViewDelayed();
|
||||
|
||||
// 权限申请
|
||||
if (!PermissionUtils.getInstance().hasFullStoragePermission()) {
|
||||
PermissionUtils.getInstance().requestFullStoragePermission(this);
|
||||
}
|
||||
}
|
||||
PermissionUtils.getInstance().checkAndRequestMediaImagesPermission(this, REQUEST_READ_MEDIA_IMAGES);
|
||||
}
|
||||
|
||||
// 移除 onSaveInstanceState 方法
|
||||
// 移除 onRestoreInstanceState 方法
|
||||
@@ -217,6 +215,14 @@ public class MainActivity extends WinBoLLActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == REQUEST_READ_MEDIA_IMAGES) {
|
||||
PermissionUtils.getInstance().handleStoragePermissionResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
moveTaskToBack(true);
|
||||
|
||||
@@ -3,6 +3,7 @@ package cc.winboll.studio.powerbell.activities;
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.graphics.BitmapFactory;
|
||||
@@ -14,11 +15,24 @@ import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import cc.winboll.studio.libaes.views.AToolbar;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
@@ -27,26 +41,27 @@ import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog;
|
||||
import cc.winboll.studio.powerbell.dialogs.YesNoAlertDialog;
|
||||
import cc.winboll.studio.powerbell.model.BackgroundBean;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
import cc.winboll.studio.powerbell.utils.BitmapCacheUtils;
|
||||
import cc.winboll.studio.powerbell.utils.FileUtils;
|
||||
import cc.winboll.studio.powerbell.utils.ImageCropUtils;
|
||||
import cc.winboll.studio.powerbell.utils.PermissionUtils;
|
||||
import cc.winboll.studio.powerbell.utils.UriUtil;
|
||||
import cc.winboll.studio.powerbell.views.BackgroundView;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
|
||||
|
||||
public static final String TAG = "BackgroundSettingsActivity";
|
||||
private BackgroundSourceUtils mBgSourceUtils;
|
||||
private PermissionUtils mPermissionUtils;
|
||||
private BitmapCacheUtils mBitmapCache;
|
||||
|
||||
// 新增:手动定义 Android 13 对应的 SDK 版本号
|
||||
private static final int SDK_VERSION_TIRAMISU = 33;
|
||||
|
||||
public static final int REQUEST_SELECT_PICTURE = 0;
|
||||
public static final int REQUEST_TAKE_PHOTO = 1;
|
||||
public static final int REQUEST_CROP_IMAGE = 2;
|
||||
private static final int REQUEST_READ_MEDIA = 1001;
|
||||
|
||||
private Toolbar mToolbar;
|
||||
private BackgroundView mBackgroundView;
|
||||
@@ -73,6 +88,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
mBgSourceUtils = BackgroundSourceUtils.getInstance(this);
|
||||
mBgSourceUtils.loadSettings();
|
||||
mPermissionUtils = PermissionUtils.getInstance();
|
||||
mBitmapCache = BitmapCacheUtils.getInstance();
|
||||
|
||||
File tempDir = new File(App.getTempDirPath());
|
||||
if (!tempDir.exists()) {
|
||||
@@ -95,9 +111,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
mBgSourceUtils.setCurrentSourceToPreview();
|
||||
}
|
||||
|
||||
Uri uriSelectedImage = UriUtil.getUriForFile(this, new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()));
|
||||
// 创建预览数据剪裁环境
|
||||
mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(mBgSourceUtils.getPreviewBackgroundBean());
|
||||
//Uri uriSelectedImage = UriUtil.getUriForFile(this, new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()));
|
||||
File fTestSourceExist = new File(mBgSourceUtils.getCurrentBackgroundBean().getBackgroundFilePath());
|
||||
boolean isCreateNewPreviewBean = !fTestSourceExist.exists();
|
||||
mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(isCreateNewPreviewBean, mBgSourceUtils.getPreviewBackgroundBean());
|
||||
doubleRefreshPreview();
|
||||
LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成");
|
||||
}
|
||||
@@ -109,18 +126,18 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
}
|
||||
|
||||
private void initToolbar() {
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(mToolbar);
|
||||
mToolbar.setSubtitle(getTag());
|
||||
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【导航栏】点击返回");
|
||||
finish();
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【导航栏】点击返回");
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initClickListeners() {
|
||||
@@ -151,10 +168,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
|
||||
boolean isImageType(String lowerMimeType) {
|
||||
return lowerMimeType.equals("image/jpeg")
|
||||
|| lowerMimeType.equals("image/png")
|
||||
|| lowerMimeType.equals("image/tiff")
|
||||
|| lowerMimeType.equals("image/jpg")
|
||||
|| lowerMimeType.equals("image/svg+xml");
|
||||
|| lowerMimeType.equals("image/png")
|
||||
|| lowerMimeType.equals("image/tiff")
|
||||
|| lowerMimeType.equals("image/jpg")
|
||||
|| lowerMimeType.equals("image/svg+xml");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -173,81 +190,98 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
}
|
||||
};
|
||||
|
||||
// ====================== 核心修改:Java 7 兼容的相册选择点击事件 ======================
|
||||
private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】选择图片");
|
||||
// 适配 API 30+ 权限逻辑
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// API 30+ 优先判断全文件管理权限
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
mPermissionUtils.requestFullStoragePermission(BackgroundSettingsActivity.this);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 低版本判断传统读写权限
|
||||
if (!mPermissionUtils.hasFullStoragePermission()) {
|
||||
mPermissionUtils.requestFullStoragePermission(BackgroundSettingsActivity.this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】选择图片");
|
||||
// 适配Android 13+权限(已替换为手动定义的常量)
|
||||
if (Build.VERSION.SDK_INT >= SDK_VERSION_TIRAMISU) {
|
||||
if (mPermissionUtils.checkAndRequestMediaImagesPermission(BackgroundSettingsActivity.this, REQUEST_READ_MEDIA)) {
|
||||
launchImageSelector();
|
||||
}
|
||||
} else {
|
||||
if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) {
|
||||
launchImageSelector();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// API 30+ 推荐的标准化相册选择 Intent
|
||||
Intent pickIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
pickIntent.setType("image/*");
|
||||
// 添加权限 Flag,确保 Uri 可读取
|
||||
pickIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
// 持久化 Uri 权限,避免后续操作失效
|
||||
pickIntent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
}
|
||||
private void launchImageSelector() {
|
||||
LogUtils.d(TAG, "【选图权限】已获取,启动选择器");
|
||||
Intent[] intents = new Intent[3];
|
||||
Intent getContentIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
getContentIntent.setType("image/*");
|
||||
getContentIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
getContentIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intents[0] = getContentIntent;
|
||||
|
||||
// 构建选择器,统一用户交互
|
||||
Intent chooser = Intent.createChooser(pickIntent, "选择图片");
|
||||
if (chooser.resolveActivity(getPackageManager()) != null) {
|
||||
startActivityForResult(chooser, REQUEST_SELECT_PICTURE);
|
||||
LogUtils.d(TAG, "【选图意图】启动图片选择");
|
||||
} else {
|
||||
LogUtils.d(TAG, "【选图意图】无相册应用");
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ToastUtils.show("未找到相册应用,请安装后重试");
|
||||
new AlertDialog.Builder(BackgroundSettingsActivity.this)
|
||||
.setTitle("无图片选择应用")
|
||||
.setMessage("需要安装相册应用才能选择图片")
|
||||
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent marketIntent = new Intent(Intent.ACTION_VIEW);
|
||||
marketIntent.setData(Uri.parse("market://details?id=com.android.gallery3d"));
|
||||
if (marketIntent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(marketIntent);
|
||||
} else {
|
||||
ToastUtils.show("无法打开应用商店");
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton("取消", null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
Intent pickIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
pickIntent.setType("image/*");
|
||||
pickIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intents[1] = pickIntent;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
Intent openDocIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
openDocIntent.setType("image/*");
|
||||
openDocIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
openDocIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
intents[2] = openDocIntent;
|
||||
}
|
||||
|
||||
Intent validIntent = null;
|
||||
for (int i = 0; i < intents.length; i++) {
|
||||
Intent intent = intents[i];
|
||||
if (intent != null && intent.resolveActivity(getPackageManager()) != null) {
|
||||
validIntent = intent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (validIntent != null) {
|
||||
Intent chooser = Intent.createChooser(validIntent, "选择图片");
|
||||
chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
startActivityForResult(chooser, REQUEST_SELECT_PICTURE);
|
||||
LogUtils.d(TAG, "【选图意图】启动图片选择");
|
||||
} else {
|
||||
LogUtils.d(TAG, "【选图意图】无相册应用");
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ToastUtils.show("未找到相册应用,请安装后重试");
|
||||
new AlertDialog.Builder(BackgroundSettingsActivity.this)
|
||||
.setTitle("无图片选择应用")
|
||||
.setMessage("需要安装相册应用才能选择图片")
|
||||
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent marketIntent = new Intent(Intent.ACTION_VIEW);
|
||||
marketIntent.setData(Uri.parse("market://details?id=com.android.gallery3d"));
|
||||
if (marketIntent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(marketIntent);
|
||||
} else {
|
||||
ToastUtils.show("无法打开应用商店");
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton("取消", null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private View.OnClickListener onCropPictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】固定比例裁剪");
|
||||
// 调用裁剪工具类:传入上下文、预览图、固定比例(按视图宽高)、请求码
|
||||
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
mBackgroundView.getWidth(),
|
||||
mBackgroundView.getHeight(),
|
||||
false,
|
||||
REQUEST_CROP_IMAGE);
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
mBackgroundView.getWidth(),
|
||||
mBackgroundView.getHeight(),
|
||||
false,
|
||||
REQUEST_CROP_IMAGE
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -255,13 +289,13 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】自由裁剪");
|
||||
// 调用裁剪工具类:传入上下文、预览图、自由裁剪(比例参数传0)、请求码
|
||||
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
REQUEST_CROP_IMAGE);
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
REQUEST_CROP_IMAGE
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -286,7 +320,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPermissionUtils.hasFullStoragePermission()) {
|
||||
if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) {
|
||||
LogUtils.d(TAG, "【拍照权限】已获取");
|
||||
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
try {
|
||||
@@ -300,7 +334,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
LogUtils.e(TAG, "【拍照失败】");
|
||||
}
|
||||
} else {
|
||||
mPermissionUtils.requestFullStoragePermission(BackgroundSettingsActivity.this);
|
||||
LogUtils.d(TAG, "【拍照权限】已申请");
|
||||
}
|
||||
}
|
||||
@@ -318,7 +351,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】像素拾取");
|
||||
String targetImagePath = mBgSourceUtils.getCurrentBackgroundFilePath();
|
||||
String targetImagePath = mBgSourceUtils.getCurrentBackgroundBean().getBackgroundFilePath();
|
||||
File targetFile = new File(targetImagePath);
|
||||
if (targetFile == null || !targetFile.exists() || targetFile.length() <= 0) {
|
||||
ToastUtils.show("无有效图片可拾取像素");
|
||||
@@ -352,7 +385,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
LogUtils.d(TAG, "【回调触发】requestCode:" + requestCode + ",resultCode:" + resultCode);
|
||||
|
||||
try {
|
||||
if (requestCode == PermissionUtils.REQUEST_CODE_STORAGE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (requestCode == PermissionUtils.REQUEST_READ_MEDIA_IMAGES && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
handleStoragePermissionCallback();
|
||||
return;
|
||||
}
|
||||
@@ -393,8 +426,12 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
}
|
||||
|
||||
private void handleTakePhotoResult(int resultCode, Intent data) {
|
||||
if (resultCode != RESULT_OK || !mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
|
||||
if (resultCode != RESULT_OK || data == null) {
|
||||
handleOperationCancelOrFail();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
|
||||
ToastUtils.show("拍照文件无效");
|
||||
return;
|
||||
}
|
||||
@@ -410,13 +447,13 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
mBgSourceUtils.saveFileToPreviewBean(mfTakePhoto, mfTakePhoto.getAbsolutePath());
|
||||
doubleRefreshPreview();
|
||||
|
||||
// 拍照后启动固定比例裁剪(调用工具类)
|
||||
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
mBackgroundView.getWidth(),
|
||||
mBackgroundView.getHeight(),
|
||||
false,
|
||||
REQUEST_CROP_IMAGE);
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
mBackgroundView.getWidth(),
|
||||
mBackgroundView.getHeight(),
|
||||
false,
|
||||
REQUEST_CROP_IMAGE
|
||||
);
|
||||
LogUtils.d(TAG, "【拍照完成】已启动裁剪");
|
||||
}
|
||||
|
||||
@@ -457,36 +494,68 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
}
|
||||
|
||||
private void handleSelectPictureResult(int resultCode, Intent data) {
|
||||
if (resultCode != RESULT_OK || data == null) {
|
||||
handleOperationCancelOrFail();
|
||||
return;
|
||||
}
|
||||
if (resultCode != RESULT_OK || data == null) {
|
||||
handleOperationCancelOrFail();
|
||||
return;
|
||||
}
|
||||
|
||||
Uri selectedImage = data.getData();
|
||||
if (selectedImage == null) {
|
||||
ToastUtils.show("图片Uri为空");
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "【选图回调】Uri : " + selectedImage.toString());
|
||||
Uri selectedImage = data.getData();
|
||||
if (selectedImage == null) {
|
||||
ToastUtils.show("图片Uri为空");
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "【选图回调】系统返回Uri : " + selectedImage.toString());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
getContentResolver().takePersistableUriPermission(
|
||||
selectedImage,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
);
|
||||
LogUtils.d(TAG, "【选图权限】已添加持久化权限");
|
||||
}
|
||||
// 1. 申请持久化读取权限
|
||||
if (Build.VERSION.SDK_INT >= SDK_VERSION_TIRAMISU) {
|
||||
getContentResolver().takePersistableUriPermission(
|
||||
selectedImage,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
);
|
||||
LogUtils.d(TAG, "【选图权限】已添加持久化权限");
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "【选图同步】路径绑定完成");
|
||||
// 选图后启动固定比例裁剪(调用工具类)
|
||||
putUriFileToPreviewSource(selectedImage);
|
||||
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
mBackgroundView.getWidth(),
|
||||
mBackgroundView.getHeight(),
|
||||
false,
|
||||
REQUEST_CROP_IMAGE);
|
||||
}
|
||||
// 2. 强制拷贝到应用私有目录(彻底规避系统虚拟路径问题)
|
||||
// String privateImagePath = copyUriToPrivateDir(selectedImage);
|
||||
// if (TextUtils.isEmpty(privateImagePath)) {
|
||||
// ToastUtils.show("系统图片路径无效,拷贝失败");
|
||||
// LogUtils.e(TAG, "【选图校验】系统Uri拷贝失败,Uri=" + selectedImage.toString());
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 3. 严格校验私有目录文件有效性
|
||||
// File srcFile = new File(privateImagePath);
|
||||
// if (srcFile == null
|
||||
// || !srcFile.exists()
|
||||
// || !srcFile.isFile()
|
||||
// || srcFile.length() <= 0) {
|
||||
// ToastUtils.show("系统图片文件无效");
|
||||
// LogUtils.e(TAG, "【选图校验】私有目录文件无效,路径=" + privateImagePath);
|
||||
// // 清理无效文件
|
||||
// if (srcFile != null && srcFile.exists()) {
|
||||
// srcFile.delete();
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
//LogUtils.d(TAG, "【选图校验】系统图片校验通过,私有路径:" + privateImagePath);
|
||||
|
||||
// 4. 同步到预览 Bean 并启动裁剪
|
||||
if (putUriFileToPreviewSource(selectedImage)) {
|
||||
LogUtils.d(TAG, "【选图同步】路径绑定完成");
|
||||
mBitmapCache.removeCachedBitmap(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath());
|
||||
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
mBackgroundView.getWidth(),
|
||||
mBackgroundView.getHeight(),
|
||||
false,
|
||||
REQUEST_CROP_IMAGE
|
||||
);
|
||||
} else {
|
||||
ToastUtils.show("图片同步失败");
|
||||
LogUtils.e(TAG, "【选图同步】文件复制失败");
|
||||
}
|
||||
}
|
||||
|
||||
boolean putUriFileToPreviewSource(Uri srcUriFile) {
|
||||
File srcFile = new File(UriUtil.getFilePathFromUri(this, srcUriFile));
|
||||
@@ -494,6 +563,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
}
|
||||
|
||||
boolean putUriFileToPreviewSource(File srcFile) {
|
||||
LogUtils.d(TAG, String.format("putUriFileToPreviewSource(File srcFile) srcFile %s", srcFile));
|
||||
mBgSourceUtils.loadSettings();
|
||||
File dstFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath());
|
||||
return FileUtils.copyFile(srcFile, dstFile);
|
||||
@@ -531,16 +601,15 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟300ms调用双重刷新,确保文件写入完成
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isFinishing()) {
|
||||
doubleRefreshPreview();
|
||||
LogUtils.d(TAG, "handleCropImageResult: 触发双重刷新");
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isFinishing()) {
|
||||
doubleRefreshPreview();
|
||||
LogUtils.d(TAG, "handleCropImageResult: 触发双重刷新");
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
handleOperationCancelOrFail();
|
||||
}
|
||||
@@ -597,11 +666,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
LogUtils.d(TAG, "adjustBitmapToFinalRatio: 调整前:" + originalWidth + "x" + originalHeight + ",调整后:" + targetWidth + "x" + targetHeight);
|
||||
|
||||
Bitmap adjustedBitmap = Bitmap.createBitmap(
|
||||
originalBitmap,
|
||||
(originalWidth - targetWidth) / 2,
|
||||
(originalHeight - targetHeight) / 2,
|
||||
targetWidth,
|
||||
targetHeight
|
||||
originalBitmap,
|
||||
(originalWidth - targetWidth) / 2,
|
||||
(originalHeight - targetHeight) / 2,
|
||||
targetWidth,
|
||||
targetHeight
|
||||
);
|
||||
|
||||
return adjustedBitmap;
|
||||
@@ -685,20 +754,32 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
|
||||
private void doubleRefreshPreview() {
|
||||
LogUtils.d(TAG, "【双重刷新】开始");
|
||||
// 1. 清空缓存工具类的旧数据
|
||||
// 清空缓存工具类的旧数据
|
||||
if (mBgSourceUtils != null) {
|
||||
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
|
||||
if (previewBean != null && previewBean.isUseBackgroundFile()) {
|
||||
String bgPath = previewBean.isUseBackgroundScaledCompressFile()
|
||||
? previewBean.getBackgroundScaledCompressFilePath()
|
||||
: previewBean.getBackgroundFilePath();
|
||||
// 强制清除缓存
|
||||
App._mBitmapCacheUtils.removeCachedBitmap(bgPath);
|
||||
? previewBean.getBackgroundScaledCompressFilePath()
|
||||
: previewBean.getBackgroundFilePath();
|
||||
mBitmapCache.removeCachedBitmap(bgPath);
|
||||
LogUtils.d(TAG, "【双重刷新】已清空工具类缓存:" + bgPath);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 重新加载最新数据
|
||||
// 清空 BackgroundView 自身的 Bitmap 引用
|
||||
// if (mBackgroundView != null) {
|
||||
// if (mBackgroundView instanceof ImageView) {
|
||||
// ((ImageView) mBackgroundView).setImageBitmap(null);
|
||||
// }
|
||||
// Drawable drawable = mBackgroundView.getBackground();
|
||||
// if (drawable != null) {
|
||||
// drawable.setCallback(null);
|
||||
// mBackgroundView.setBackground(null);
|
||||
// }
|
||||
// LogUtils.d(TAG, "【双重刷新】已清空控件 Bitmap 引用");
|
||||
// }
|
||||
|
||||
// 重新加载最新数据
|
||||
if (mBackgroundView != null && !isFinishing()) {
|
||||
mBgSourceUtils.loadSettings();
|
||||
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
||||
@@ -708,23 +789,23 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 延长延迟到200ms,确保文件完全加载
|
||||
// 延长延迟到200ms,确保文件完全加载
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mBackgroundView != null && !isFinishing()) {
|
||||
mBgSourceUtils.loadSettings();
|
||||
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
||||
LogUtils.d(TAG, "【双重刷新】第二重完成");
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
@Override
|
||||
public void run() {
|
||||
if (mBackgroundView != null && !isFinishing()) {
|
||||
mBgSourceUtils.loadSettings();
|
||||
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
||||
LogUtils.d(TAG, "【双重刷新】第二重完成");
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
private void handleOperationCancelOrFail() {
|
||||
mBgSourceUtils.setCurrentSourceToPreview();
|
||||
LogUtils.d(TAG, "【操作回调】取消或失败");
|
||||
ToastUtils.show("操作取消或失败");
|
||||
ToastUtils.show("【操作回调】取消或失败");
|
||||
doubleRefreshPreview();
|
||||
}
|
||||
|
||||
@@ -752,30 +833,92 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
@Override
|
||||
public void finish() {
|
||||
if (isCommitSettings) {
|
||||
setResult(RESULT_OK);
|
||||
super.finish();
|
||||
} else {
|
||||
// 如果预览背景改变过就提示是否更换背景
|
||||
if (isPreviewBackgroundChanged) {
|
||||
YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener() {
|
||||
@Override
|
||||
public void onYes() {
|
||||
mBgSourceUtils.commitPreviewSourceToCurrent();
|
||||
isCommitSettings = true;
|
||||
finish();
|
||||
}
|
||||
@Override
|
||||
public void onYes() {
|
||||
mBgSourceUtils.commitPreviewSourceToCurrent();
|
||||
isCommitSettings = true;
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNo() {
|
||||
isCommitSettings = true;
|
||||
finish();
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onNo() {
|
||||
isCommitSettings = true;
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 如果预览背景未改变就直接退出
|
||||
setResult(RESULT_OK);
|
||||
isCommitSettings = true;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析Uri到真实文件路径,兼容所有Android版本
|
||||
* @param uri 图片的Content Uri
|
||||
* @return 真实文件路径 / null
|
||||
*/
|
||||
// private String getRealPathFromUri(Uri uri) {
|
||||
// String path = null;
|
||||
// String[] projection = {MediaStore.Images.Media.DATA};
|
||||
// Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
|
||||
// if (cursor != null) {
|
||||
// if (cursor.moveToFirst()) {
|
||||
// int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
|
||||
// path = cursor.getString(columnIndex);
|
||||
// }
|
||||
// cursor.close();
|
||||
// }
|
||||
// // 针对Android 10+沙箱限制,拷贝到私有目录
|
||||
// if (TextUtils.isEmpty(path)) {
|
||||
//
|
||||
// path = copyUriToPrivateDir(uri);
|
||||
// }
|
||||
// return path;
|
||||
// }
|
||||
|
||||
/**
|
||||
* 将Uri对应的图片拷贝到应用私有临时目录
|
||||
* @param uri 图片Uri
|
||||
* @return 拷贝后的文件路径 / null
|
||||
*/
|
||||
// private String copyUriToPrivateDir(Uri uri) {
|
||||
// try {
|
||||
// InputStream inputStream = getContentResolver().openInputStream(uri);
|
||||
// if (inputStream == null) {
|
||||
// return null;
|
||||
// }
|
||||
// File tempDir = new File(App.getTempDirPath());
|
||||
// if (!tempDir.exists()) {
|
||||
// tempDir.mkdirs();
|
||||
// }
|
||||
// String fileName = "select_temp_" + System.currentTimeMillis() + ".jpg";
|
||||
// File destFile = new File(tempDir, fileName);
|
||||
//
|
||||
// OutputStream outputStream = new FileOutputStream(destFile);
|
||||
// byte[] buffer = new byte[1024 * 4];
|
||||
// int len;
|
||||
// while ((len = inputStream.read(buffer)) != -1) {
|
||||
// outputStream.write(buffer, 0, len);
|
||||
// }
|
||||
// outputStream.flush();
|
||||
// inputStream.close();
|
||||
// outputStream.close();
|
||||
//
|
||||
// LogUtils.d(TAG, "【选图拷贝】文件已拷贝到私有目录:" + destFile.getAbsolutePath());
|
||||
// return destFile.getAbsolutePath();
|
||||
// } catch (IOException e) {
|
||||
// LogUtils.e(TAG, "【选图拷贝】异常:" + e.getMessage());
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import cc.winboll.studio.powerbell.utils.PermissionUtils;
|
||||
public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "SettingsActivity";
|
||||
private static final int REQUEST_READ_MEDIA_IMAGES = 1001;
|
||||
|
||||
private Toolbar mToolbar;
|
||||
|
||||
@@ -52,10 +53,15 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
|
||||
|
||||
public void onCheckPermission(View view) {
|
||||
//ToastUtils.show("onCheckPermission");
|
||||
if (PermissionUtils.getInstance().hasFullStoragePermission()) {
|
||||
ToastUtils.show("【权限检查】存储权限已全部获取");
|
||||
} else {
|
||||
PermissionUtils.getInstance().requestFullStoragePermission(this);
|
||||
PermissionUtils.getInstance().checkAndRequestMediaImagesPermission(this, REQUEST_READ_MEDIA_IMAGES);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == REQUEST_READ_MEDIA_IMAGES) {
|
||||
PermissionUtils.getInstance().handleStoragePermissionResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,15 @@ import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import androidx.core.content.FileProvider;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.BuildConfig;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.model.BackgroundBean;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
@@ -19,8 +22,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.UUID;
|
||||
import android.os.Build;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -248,57 +249,72 @@ public class BackgroundSourceUtils {
|
||||
/*
|
||||
* 创建预览数据剪裁环境
|
||||
*/
|
||||
public boolean createAndUpdatePreviewEnvironmentForCropping(BackgroundBean oldPreviewBackgroundBean) {
|
||||
public boolean createAndUpdatePreviewEnvironmentForCropping(boolean isCreateNewPreviewBean, BackgroundBean previewBackgroundBean) {
|
||||
InputStream is = null;
|
||||
FileOutputStream fos = null;
|
||||
|
||||
try {
|
||||
clearCropTempFiles();
|
||||
Uri uri = UriUtil.getUriForFile(mContext, oldPreviewBackgroundBean.getBackgroundFilePath());
|
||||
//String szType = mContext.getContentResolver().getType(uri);
|
||||
// 2. 截取MIME类型后缀(如从image/jpeg中提取jpeg)【核心新增逻辑】
|
||||
String fileSuffix = FileUtils.getFileSuffix(mContext, uri);
|
||||
String newCropFileName = UUID.randomUUID().toString() + System.currentTimeMillis();
|
||||
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
|
||||
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
|
||||
String fileSuffix;
|
||||
|
||||
if (FileUtils.isFileExists(oldPreviewBackgroundBean.getBackgroundScaledCompressFilePath())) {
|
||||
FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundScaledCompressFilePath()), mCropResultFile);
|
||||
} else {
|
||||
if (isCreateNewPreviewBean) {
|
||||
fileSuffix = "png";
|
||||
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
|
||||
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
|
||||
DrawableToFileUtils.saveDrawableToFile(mContext, R.drawable.blank10x10, mCropSourceFile.getAbsolutePath());
|
||||
mCropResultFile.createNewFile();
|
||||
}
|
||||
|
||||
if (FileUtils.isFileExists(oldPreviewBackgroundBean.getBackgroundFilePath())) {
|
||||
FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundFilePath()), mCropSourceFile);
|
||||
} else {
|
||||
mCropSourceFile.createNewFile();
|
||||
// 1. 打开Uri输入流(兼容content:///file:// 等多种Uri格式)
|
||||
is = mContext.getContentResolver().openInputStream(uri);
|
||||
if (is == null) {
|
||||
LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败,Uri:" + uri.toString());
|
||||
return false;
|
||||
}
|
||||
if (FileUtils.isFileExists(previewBackgroundBean.getBackgroundFilePath())) {
|
||||
File fSourceFileOld = new File(previewBackgroundBean.getBackgroundFilePath());
|
||||
Uri uri = UriUtil.getUriForFile(mContext, fSourceFileOld);
|
||||
fileSuffix = FileUtils.getFileSuffix(mContext, uri);
|
||||
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
|
||||
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
|
||||
|
||||
// 2. 初始化选图临时文件输出流(Java7 手动创建流,不依赖try-with-resources)
|
||||
fos = new FileOutputStream(mCropSourceFile);
|
||||
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,平衡读写性能与内存占用
|
||||
int readLen; // 每次读取的字节长度
|
||||
|
||||
// 3. 流复制(Java7 标准while循环,避免Java8+语法)
|
||||
while ((readLen = is.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, readLen); // 精准写入读取到的字节,避免空字节填充
|
||||
}
|
||||
|
||||
// 4. 强制同步写入磁盘(解决Android 10+ 异步写入导致的文件无效问题)
|
||||
fos.flush();
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.getFD().sync(); // 确保数据写入物理磁盘,而非缓存
|
||||
} catch (IOException e) {
|
||||
LogUtils.w(TAG, "【选图解析】文件同步到磁盘失败,用flush()兜底:" + e.getMessage());
|
||||
fos.flush();
|
||||
FileUtils.copyFile(new File(previewBackgroundBean.getBackgroundFilePath()), mCropSourceFile);
|
||||
if (FileUtils.isFileExists(previewBackgroundBean.getBackgroundScaledCompressFilePath())) {
|
||||
FileUtils.copyFile(new File(previewBackgroundBean.getBackgroundScaledCompressFilePath()), mCropResultFile);
|
||||
} else {
|
||||
mCropResultFile.createNewFile();
|
||||
}
|
||||
|
||||
mCropSourceFile.createNewFile();
|
||||
// 1. 打开Uri输入流(兼容content:///file:// 等多种Uri格式)
|
||||
is = mContext.getContentResolver().openInputStream(uri);
|
||||
if (is == null) {
|
||||
LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败,Uri:" + uri.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 初始化选图临时文件输出流(Java7 手动创建流,不依赖try-with-resources)
|
||||
fos = new FileOutputStream(mCropSourceFile);
|
||||
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,平衡读写性能与内存占用
|
||||
int readLen; // 每次读取的字节长度
|
||||
|
||||
// 3. 流复制(Java7 标准while循环,避免Java8+语法)
|
||||
while ((readLen = is.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, readLen); // 精准写入读取到的字节,避免空字节填充
|
||||
}
|
||||
|
||||
// 4. 强制同步写入磁盘(解决Android 10+ 异步写入导致的文件无效问题)
|
||||
fos.flush();
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.getFD().sync(); // 确保数据写入物理磁盘,而非缓存
|
||||
} catch (IOException e) {
|
||||
LogUtils.w(TAG, "【选图解析】文件同步到磁盘失败,用flush()兜底:" + e.getMessage());
|
||||
fos.flush();
|
||||
}
|
||||
}
|
||||
|
||||
// 解析成功日志(打印文件信息,便于问题排查)
|
||||
LogUtils.d(TAG, "【选图解析】Uri解析成功!");
|
||||
LogUtils.d(TAG, "→ 原Uri:" + uri.toString());
|
||||
} else {
|
||||
return createAndUpdatePreviewEnvironmentForCropping(true, previewBackgroundBean);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 加载图片数据模型数据
|
||||
@@ -312,13 +328,12 @@ public class BackgroundSourceUtils {
|
||||
// 保存数据模型数据更改
|
||||
saveSettings();
|
||||
|
||||
// 6. 解析成功日志(打印文件信息,便于问题排查)
|
||||
LogUtils.d(TAG, "【选图解析】Uri解析成功!");
|
||||
LogUtils.d(TAG, "→ 原Uri:" + uri.toString());
|
||||
// 解析成功日志(打印文件信息,便于问题排查)
|
||||
LogUtils.d(TAG, "→ 图片剪裁数据源:" + mCropSourceFile.getAbsolutePath());
|
||||
LogUtils.d(TAG, "→ 图片剪裁数据源文件大小:" + mCropSourceFile.length() + " bytes");
|
||||
LogUtils.d(TAG, "→ 剪裁结果数据文件:" + mCropResultFile.getAbsolutePath());
|
||||
LogUtils.d(TAG, "→ 剪裁结果数据文件大小:" + mCropResultFile.length() + " bytes");
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -380,30 +395,30 @@ public class BackgroundSourceUtils {
|
||||
/**
|
||||
* 获取正式背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验)
|
||||
*/
|
||||
public String getCurrentBackgroundFilePath() {
|
||||
String fileName = currentBackgroundBean.getBackgroundFileName();
|
||||
if (TextUtils.isEmpty(fileName)) {
|
||||
LogUtils.e(TAG, "【路径管理】正式背景文件名为空,返回空路径");
|
||||
return "";
|
||||
}
|
||||
File file = new File(fBackgroundSourceDir, fileName);
|
||||
LogUtils.d(TAG, "【路径管理】正式背景路径:" + file.getAbsolutePath());
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验)
|
||||
*/
|
||||
public String getPreviewBackgroundFilePath() {
|
||||
String fileName = previewBackgroundBean.getBackgroundFileName();
|
||||
if (TextUtils.isEmpty(fileName)) {
|
||||
LogUtils.e(TAG, "【路径管理】预览背景文件名为空,返回空路径");
|
||||
return "";
|
||||
}
|
||||
File file = new File(fBackgroundSourceDir, fileName);
|
||||
LogUtils.d(TAG, "【路径管理】预览背景路径:" + file.getAbsolutePath());
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
// public String getCurrentBackgroundFilePath() {
|
||||
// String fileName = currentBackgroundBean.getBackgroundFileName();
|
||||
// if (TextUtils.isEmpty(fileName)) {
|
||||
// LogUtils.e(TAG, "【路径管理】正式背景文件名为空,返回空路径");
|
||||
// return "";
|
||||
// }
|
||||
// File file = new File(fBackgroundSourceDir, fileName);
|
||||
// LogUtils.d(TAG, "【路径管理】正式背景路径:" + file.getAbsolutePath());
|
||||
// return file.getAbsolutePath();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取预览背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验)
|
||||
// */
|
||||
// public String getPreviewBackgroundFilePath() {
|
||||
// String fileName = previewBackgroundBean.getBackgroundFileName();
|
||||
// if (TextUtils.isEmpty(fileName)) {
|
||||
// LogUtils.e(TAG, "【路径管理】预览背景文件名为空,返回空路径");
|
||||
// return "";
|
||||
// }
|
||||
// File file = new File(fBackgroundSourceDir, fileName);
|
||||
// LogUtils.d(TAG, "【路径管理】预览背景路径:" + file.getAbsolutePath());
|
||||
// return file.getAbsolutePath();
|
||||
// }
|
||||
|
||||
/**
|
||||
* 获取预览背景压缩图片路径(同步修复:移除 loadSettings(),强化非空校验,统一指向BackgroundCrops目录)
|
||||
|
||||
@@ -1,113 +1,134 @@
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.Settings;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
/**
|
||||
* 存储权限工具类(适配 API 30+)
|
||||
* 核心支持:1. 传统读写权限 2. MANAGE_EXTERNAL_STORAGE 全文件权限
|
||||
* 权限申请工具类
|
||||
* 适配 Android 13+ 媒体权限 & 低版本存储权限
|
||||
* 兼容 SDK 版本低于 33 的编译环境
|
||||
*/
|
||||
public class PermissionUtils {
|
||||
private static final String TAG = "PermissionUtils";
|
||||
// 存储权限请求码
|
||||
public static final int REQUEST_STORAGE = 1000;
|
||||
// 媒体图片权限请求码(Android 13+)
|
||||
public static final int REQUEST_READ_MEDIA_IMAGES = 1001;
|
||||
|
||||
// 手动定义 Android 13+ 媒体图片权限常量(解决 SDK 低于 33 无法识别问题)
|
||||
private static final String READ_MEDIA_IMAGES = "android.permission.READ_MEDIA_IMAGES";
|
||||
// Android 13 对应的 SDK 版本号(替代 Build.VERSION_CODES.TIRAMISU)
|
||||
private static final int SDK_VERSION_TIRAMISU = 33;
|
||||
|
||||
// 单例模式
|
||||
private static volatile PermissionUtils sInstance;
|
||||
private Context mContext;
|
||||
|
||||
// 权限请求码
|
||||
public static final int REQUEST_CODE_STORAGE = 1001;
|
||||
public static final int REQUEST_CODE_MANAGE_STORAGE = 1002;
|
||||
|
||||
private PermissionUtils(Context context) {
|
||||
this.mContext = context.getApplicationContext();
|
||||
}
|
||||
private PermissionUtils() {}
|
||||
|
||||
public static PermissionUtils getInstance() {
|
||||
if (sInstance == null) {
|
||||
throw new IllegalStateException("请先调用 init(Context) 初始化");
|
||||
synchronized (PermissionUtils.class) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new PermissionUtils();
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public static void init(Context context) {
|
||||
if (sInstance == null) {
|
||||
synchronized (PermissionUtils.class) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new PermissionUtils(context);
|
||||
/**
|
||||
* 检查并请求 存储权限(Android 12及以下)
|
||||
*/
|
||||
public boolean checkAndRequestStoragePermission(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// Android 11+ 无需申请 READ_EXTERNAL_STORAGE,直接返回true
|
||||
return true;
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
String[] permissions = {
|
||||
android.Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
};
|
||||
if (ContextCompat.checkSelfPermission(activity, permissions[0]) != PackageManager.PERMISSION_GRANTED
|
||||
|| ContextCompat.checkSelfPermission(activity, permissions[1]) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(activity, permissions, REQUEST_STORAGE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并请求 媒体图片权限(Android 13+)
|
||||
* 兼容 SDK 编译版本低于 33 的情况
|
||||
*/
|
||||
public boolean checkAndRequestMediaImagesPermission(Activity activity, int requestCode) {
|
||||
// 用数值 33 替代 Build.VERSION_CODES.TIRAMISU
|
||||
if (Build.VERSION.SDK_INT >= SDK_VERSION_TIRAMISU) {
|
||||
// 用手动定义的权限常量替代 android.Manifest.permission.READ_MEDIA_IMAGES
|
||||
if (ContextCompat.checkSelfPermission(activity, READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(activity, new String[]{READ_MEDIA_IMAGES}, requestCode);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 低版本已通过存储权限覆盖,直接返回true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限请求结果处理
|
||||
*/
|
||||
public void handleStoragePermissionResult(Activity activity, int requestCode, String[] permissions, int[] grantResults) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_STORAGE:
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
LogUtils.d(TAG, "存储权限申请成功");
|
||||
} else {
|
||||
LogUtils.e(TAG, "存储权限申请失败");
|
||||
}
|
||||
break;
|
||||
case REQUEST_READ_MEDIA_IMAGES:
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
LogUtils.d(TAG, "媒体图片权限申请成功");
|
||||
} else {
|
||||
LogUtils.e(TAG, "媒体图片权限申请失败");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LogUtils.d(TAG, "未知权限请求码:" + requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有管理所有文件权限(Android 11+)
|
||||
*/
|
||||
public boolean checkManageExternalStoragePermission(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
return android.os.Environment.isExternalStorageManager();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求管理所有文件权限(Android 11+)
|
||||
*/
|
||||
public void requestManageExternalStoragePermission(Activity activity, int requestCode) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
try {
|
||||
android.content.Intent intent = new android.content.Intent(
|
||||
android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
|
||||
);
|
||||
intent.setData(android.net.Uri.parse("package:" + activity.getPackageName()));
|
||||
activity.startActivityForResult(intent, requestCode);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "请求管理文件权限异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否拥有完整的文件管理权限
|
||||
*/
|
||||
public boolean hasFullStoragePermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// API 30+ 需判断 MANAGE_EXTERNAL_STORAGE 权限
|
||||
return Environment.isExternalStorageManager();
|
||||
} else {
|
||||
// 低版本判断传统读写权限
|
||||
int read = ContextCompat.checkSelfPermission(mContext, android.Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
int write = ContextCompat.checkSelfPermission(mContext, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
return read == PackageManager.PERMISSION_GRANTED && write == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请文件管理权限(自动适配系统版本)
|
||||
* @param activity 发起申请的 Activity
|
||||
*/
|
||||
public void requestFullStoragePermission(android.app.Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// API 30+ 跳转系统设置页开启权限
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
|
||||
Uri uri = Uri.fromParts("package", mContext.getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
activity.startActivityForResult(intent, REQUEST_CODE_MANAGE_STORAGE);
|
||||
} else {
|
||||
// 低版本申请传统读写权限
|
||||
androidx.core.app.ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
new String[]{
|
||||
android.Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
},
|
||||
REQUEST_CODE_STORAGE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理低版本权限申请结果(在 Activity 的 onRequestPermissionsResult 中调用)
|
||||
* @param activity 上下文
|
||||
* @param requestCode 请求码
|
||||
* @param permissions 权限数组
|
||||
* @param grantResults 授权结果
|
||||
* @return 权限是否申请成功
|
||||
*/
|
||||
public boolean handleStoragePermissionResult(android.app.Activity activity, int requestCode, String[] permissions, int[] grantResults) {
|
||||
// 仅处理传统存储权限的请求结果
|
||||
if (requestCode != REQUEST_CODE_STORAGE) {
|
||||
return false;
|
||||
}
|
||||
// 校验授权结果:读写权限均需授予
|
||||
boolean isGranted = grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
&& grantResults[1] == PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
if (isGranted) {
|
||||
// 权限授予成功,提示用户
|
||||
Toast.makeText(activity, "存储权限申请成功", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
// 权限被拒绝,引导用户手动开启
|
||||
Toast.makeText(activity, "存储权限被拒绝,部分功能无法使用", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
return isGranted;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user