增加上传和更新页面
This commit is contained in:
@@ -0,0 +1,505 @@
|
||||
package com.example.mir4updater.activity.main;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import com.example.mir4updater.R;
|
||||
import com.example.mir4updater.activity.update.UpdateActivity;
|
||||
import com.example.mir4updater.activity.upload.UploadActivity;
|
||||
import com.example.mir4updater.base.mvvm.BaseMvvmActivity;
|
||||
import com.example.mir4updater.bean.FileItem;
|
||||
import com.example.mir4updater.config.CommonConfig;
|
||||
import com.example.mir4updater.databinding.ActivityMainBinding;
|
||||
import com.example.mir4updater.utils.HashUtils;
|
||||
import com.example.mir4updater.utils.PermissionUtils;
|
||||
import com.google.android.gms.tasks.OnFailureListener;
|
||||
import com.google.android.gms.tasks.OnSuccessListener;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.android.play.core.appupdate.AppUpdateInfo;
|
||||
import com.google.android.play.core.appupdate.AppUpdateManager;
|
||||
import com.google.android.play.core.appupdate.AppUpdateManagerFactory;
|
||||
import com.google.android.play.core.install.model.AppUpdateType;
|
||||
import com.google.android.play.core.install.model.UpdateAvailability;
|
||||
import com.hjq.permissions.OnPermissionCallback;
|
||||
import com.hjq.permissions.Permission;
|
||||
import com.hjq.permissions.XXPermissions;
|
||||
import com.hjq.toast.Toaster;
|
||||
import com.trello.rxlifecycle4.RxLifecycle;
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.ObservableEmitter;
|
||||
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
|
||||
import io.reactivex.rxjava3.core.Observer;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBinding> {
|
||||
private static final String TAG = "MainActivity";
|
||||
|
||||
private static final int APP_UPDATE_REQUEST_CODE = 7897;
|
||||
private static final int REQUEST_CODE = 1234;
|
||||
private static final int ANDROID_REQUEST_CODE = 9819;
|
||||
private static final int REQUEST_CODE_OPEN_DIR = 7890;
|
||||
|
||||
//google play 更新
|
||||
// private AppUpdateManager mAppUpdateManager;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean setNightMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setfitWindow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.activity_main;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataBinding() {
|
||||
mViewModel.setCtx(this);
|
||||
mViewModel.setLifecycle(getLifecycleSubject());
|
||||
mViewModel.setVDBinding(mViewDataBinding);
|
||||
mViewDataBinding.setClick(new BtnClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initData() {
|
||||
// mAppUpdateManager = AppUpdateManagerFactory.create(this);
|
||||
// Task<AppUpdateInfo> appUpdateInfoTask = mAppUpdateManager.getAppUpdateInfo();
|
||||
// appUpdateInfoTask.addOnSuccessListener(new OnSuccessListener<AppUpdateInfo>() {
|
||||
// @Override
|
||||
// public void onSuccess(AppUpdateInfo appUpdateInfo) {
|
||||
// if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE &&
|
||||
// appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
|
||||
// try {
|
||||
// mAppUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.IMMEDIATE, MainActivity.this, APP_UPDATE_REQUEST_CODE);
|
||||
// } catch (IntentSender.SendIntentException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// });
|
||||
// appUpdateInfoTask.addOnFailureListener(new OnFailureListener() {
|
||||
// @Override
|
||||
// public void onFailure(@NonNull Exception e) {
|
||||
// Log.e(TAG, "onFailure: " + e.getMessage());
|
||||
// }
|
||||
// });
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// getLocationPermission();
|
||||
checkPermission();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
Log.e(TAG, "onActivityResult: " + requestCode);
|
||||
if (requestCode == APP_UPDATE_REQUEST_CODE) {
|
||||
if (resultCode != RESULT_OK) {
|
||||
// 处理更新失败的情况
|
||||
}
|
||||
} else if (requestCode == REQUEST_CODE) {
|
||||
|
||||
} else if (requestCode == ANDROID_REQUEST_CODE) {
|
||||
readFileList();
|
||||
if (data != null) {
|
||||
Uri treeUri = data.getData();
|
||||
saveUriPermission(treeUri); // 保存权限
|
||||
}
|
||||
} else if (requestCode == REQUEST_CODE_OPEN_DIR) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (data != null) {
|
||||
Uri treeUri = data.getData();
|
||||
Log.e(TAG, "onActivityResult: " + treeUri);
|
||||
DocumentFile documentFile = DocumentFile.fromTreeUri(this, treeUri);
|
||||
startTraversal(documentFile);
|
||||
saveUriPermission(treeUri); // 保存权限
|
||||
listFiles(treeUri); // 遍历文件
|
||||
// traverseDirectoryInternal(treeUri, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPermission() {
|
||||
mViewDataBinding.setStoragePermission(XXPermissions.isGranted(this, PermissionUtils.Location_Permission));
|
||||
mViewDataBinding.setAppStoragePermission(PermissionUtils.checkAppData(MainActivity.this));
|
||||
}
|
||||
|
||||
// 在Activity中启动遍历
|
||||
private List<FileItem> fileList = new ArrayList<>();
|
||||
|
||||
private void startTraversal(DocumentFile root) {
|
||||
fileList.clear();
|
||||
// 遍历完成后更新UI(例如RecyclerView)
|
||||
traverseDocumentFile(root);
|
||||
}
|
||||
|
||||
// 递归遍历DocumentFile
|
||||
// 递归遍历并填充列表
|
||||
private void traverseDocumentFile(DocumentFile documentFile, List<FileItem> resultList, String path) {
|
||||
if (documentFile == null || !documentFile.exists()) return;
|
||||
|
||||
DocumentFile[] files = documentFile.listFiles();
|
||||
if (files == null) return;
|
||||
|
||||
for (DocumentFile file : files) {
|
||||
if (!file.isDirectory()) {
|
||||
Log.i(TAG, "traverseDocumentFile: " + getFileMd5(file.getUri()));
|
||||
}
|
||||
// 创建条目并加入列表
|
||||
FileItem item = new FileItem(
|
||||
file.getName(),
|
||||
file.isDirectory(),
|
||||
file.length(),
|
||||
file.getUri(),
|
||||
path
|
||||
);
|
||||
resultList.add(item);
|
||||
|
||||
// 如果是目录则递归遍历
|
||||
if (file.isDirectory()) {
|
||||
traverseDocumentFile(file, resultList, path + file.getName() + "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void traverseDocumentFile(DocumentFile root) {
|
||||
Observable.create(new ObservableOnSubscribe<List<FileItem>>() {
|
||||
@Override
|
||||
public void subscribe(@io.reactivex.rxjava3.annotations.NonNull ObservableEmitter<List<FileItem>> emitter) throws Throwable {
|
||||
fileList.clear();
|
||||
traverseDocumentFile(root, fileList, "/");
|
||||
emitter.onNext(fileList);
|
||||
}
|
||||
}).compose(RxLifecycle.bindUntilEvent(getLifecycleSubject(), ActivityEvent.DESTROY))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Observer<List<FileItem>>() {
|
||||
@Override
|
||||
public void onSubscribe(@io.reactivex.rxjava3.annotations.NonNull Disposable d) {
|
||||
Log.e("traverseDocumentFile", "onSubscribe: ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@io.reactivex.rxjava3.annotations.NonNull List<FileItem> fileItems) {
|
||||
Log.e("traverseDocumentFile", "onNext: " + fileItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Log.e("traverseDocumentFile", "onError: " + e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Log.e("traverseDocumentFile", "onComplete: ");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String getFileMd5(Uri uri) {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = getContentResolver().openInputStream(uri);
|
||||
if (inputStream != null) {
|
||||
return HashUtils.calculateMd5(inputStream);
|
||||
}
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
Log.e("FileHashUtils", "Error calculating MD5", e);
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
Log.e("FileHashUtils", "Error closing stream", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void requestDirectoryAccess() {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.addFlags(
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
);
|
||||
|
||||
// 设置初始目录为 Android/data/com.xxx.yyy
|
||||
Uri initialUri = Uri.parse("content://com.android.externalstorage.documents/tree/primary:Android%2Fdata%2Fcom.xxx.yyy");
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri);
|
||||
|
||||
startActivityForResult(intent, REQUEST_CODE_OPEN_DIR);
|
||||
}
|
||||
|
||||
private void saveUriPermission(Uri uri) {
|
||||
// 持久化 URI 权限
|
||||
getContentResolver().takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
);
|
||||
|
||||
// 保存到 SharedPreferences(可选)
|
||||
getSharedPreferences("MyPrefs", MODE_PRIVATE)
|
||||
.edit()
|
||||
.putString("saved_uri", uri.toString())
|
||||
.apply();
|
||||
Log.e(TAG, "saveUriPermission: " + uri.toString());
|
||||
}
|
||||
|
||||
private void listFiles(Uri treeUri) {
|
||||
ContentResolver resolver = getContentResolver();
|
||||
|
||||
// 构建子文件 URI
|
||||
String documentId = DocumentsContract.getTreeDocumentId(treeUri);
|
||||
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId);
|
||||
|
||||
String[] projection = new String[]{
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||
DocumentsContract.Document.COLUMN_MIME_TYPE
|
||||
};
|
||||
|
||||
try (Cursor cursor = resolver.query(
|
||||
childrenUri,
|
||||
projection,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
String name = cursor.getString(0);
|
||||
String mimeType = cursor.getString(1);
|
||||
|
||||
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
Log.e("DIR", "Directory: " + name);
|
||||
} else {
|
||||
Log.e("FILE", "File: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
Log.e("ERROR", "权限不足: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void traverseDirectoryInternal(Uri treeUri, int depth) {
|
||||
ContentResolver resolver = getContentResolver();
|
||||
String documentId = DocumentsContract.getTreeDocumentId(treeUri);
|
||||
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId);
|
||||
|
||||
String[] projection = new String[]{
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID
|
||||
};
|
||||
|
||||
try (Cursor cursor = resolver.query(childrenUri, projection, null, null, null)) {
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
String name = cursor.getString(0);
|
||||
String mimeType = cursor.getString(1);
|
||||
String childDocId = cursor.getString(2);
|
||||
|
||||
// 缩进显示层级
|
||||
StringBuilder indent = new StringBuilder();
|
||||
for (int i = 0; i < depth; i++) indent.append(" ");
|
||||
|
||||
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
// 目录:递归遍历
|
||||
Log.e("DIR", indent + "├─ " + name);
|
||||
Uri childUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, childDocId);
|
||||
traverseDirectoryInternal(childUri, depth + 1);
|
||||
} else {
|
||||
// 文件
|
||||
Log.e("FILE", indent + "├─ " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
Log.e("ERROR", "权限不足: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void readFileContent(Uri fileUri) {
|
||||
ContentResolver resolver = getContentResolver();
|
||||
try (InputStream inputStream = resolver.openInputStream(fileUri);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
stringBuilder.append(line).append("\n");
|
||||
}
|
||||
|
||||
Log.d("FILE_CONTENT", stringBuilder.toString());
|
||||
} catch (Exception e) {
|
||||
Log.e("ERROR", "读取文件失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void readFileList() {
|
||||
Uri treeUri = Uri.parse(CommonConfig.MIR4_CONTENT_PATH);
|
||||
DocumentFile documentFile = DocumentFile.fromTreeUri(this, treeUri);
|
||||
if (documentFile != null) {
|
||||
Log.e(TAG, "onActivityResult: canRead = " + documentFile.canRead());
|
||||
Log.e(TAG, "onActivityResult: " + Arrays.toString(documentFile.listFiles()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void getFile() {
|
||||
File file = new File("/sdcard/Android/data/com.wemade.mir4global/files/");
|
||||
Log.e(TAG, "getFile: " + Arrays.toString(file.list()));
|
||||
}
|
||||
|
||||
private void getPermssions() {
|
||||
|
||||
|
||||
// 检查是否有存储权限
|
||||
// boolean granted = Environment.isExternalStorageManager();
|
||||
// Log.e(TAG, "getFile: granted = " + granted);
|
||||
// if (granted) {
|
||||
// // 已授权,可访问所有文件
|
||||
// } else {
|
||||
// // 在activity中请求存储权限
|
||||
// Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
// .setData(Uri.parse("package:" + getPackageName()));
|
||||
// startActivityForResult(intent, 0);
|
||||
// }
|
||||
|
||||
// //把需要授权的目录转为Uri
|
||||
// Uri uri = UriUtils.getUriFromFile(MainActivity.this, "/storage/emulated/0/Android/data/com.wemade.mir4global/");
|
||||
// Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
// intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri);
|
||||
// intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
// | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
// | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
// //这里的第二个参数是标志的意思,用来判断是谁把结果回调过来的。
|
||||
// startActivityForResult(intent, 1001);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// 请求Android/data目录的权限,Android/obb目录则把data替换成obb即可。
|
||||
Uri treeUri = Uri.parse(CommonConfig.MIR4_CONTENT_PATH);
|
||||
DocumentFile documentFile = DocumentFile.fromTreeUri(this, treeUri);
|
||||
if (documentFile != null) {
|
||||
if (documentFile.canRead()) {
|
||||
readFileList();
|
||||
} else {
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, documentFile.getUri());
|
||||
startActivityForResult(intent, ANDROID_REQUEST_CODE);
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
// intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
// startActivityForResult(intent, REQUEST_CODE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void getLocationPermission() {
|
||||
XXPermissions.with(this)
|
||||
// 申请单个权限
|
||||
.permission(PermissionUtils.Location_Permission)
|
||||
// 申请多个权限
|
||||
// .permission(Permission.Group.STORAGE)
|
||||
// 设置权限请求拦截器(局部设置)
|
||||
//.interceptor(new PermissionInterceptor())
|
||||
// 设置不触发错误检测机制(局部设置)
|
||||
//.unchecked()
|
||||
.request(new OnPermissionCallback() {
|
||||
@Override
|
||||
public void onGranted(@NonNull List<String> permissions, boolean allGranted) {
|
||||
Log.e(TAG, "onGranted: permissions = " + permissions + " allGranted = " + allGranted);
|
||||
if (!allGranted) {
|
||||
// Toaster.show("获取部分权限成功,但部分权限未正常授予");
|
||||
return;
|
||||
}
|
||||
Log.e(TAG, "onGranted: 获取权限成功");
|
||||
// getFile();
|
||||
// getPermssions();
|
||||
// requestDirectoryAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenied(@NonNull List<String> permissions, boolean doNotAskAgain) {
|
||||
Log.e(TAG, "onDenied: permissions = " + permissions + " doNotAskAgain = " + doNotAskAgain);
|
||||
if (doNotAskAgain) {
|
||||
Toaster.show("被永久拒绝授权,请手动授予权限");
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(MainActivity.this, permissions);
|
||||
} else {
|
||||
Log.e(TAG, "onGranted: 获取权限失败");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public class BtnClick {
|
||||
public void upload(View view) {
|
||||
startActivity(new Intent(MainActivity.this, UploadActivity.class));
|
||||
}
|
||||
|
||||
public void update(View view) {
|
||||
startActivity(new Intent(MainActivity.this, UpdateActivity.class));
|
||||
}
|
||||
|
||||
public void getStoragePermission(View view) {
|
||||
getLocationPermission();
|
||||
}
|
||||
|
||||
public void getAppStoragePermission(View view) {
|
||||
getPermssions();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.mir4updater.activity;
|
||||
package com.example.mir4updater.activity.main;
|
||||
|
||||
import com.example.mir4updater.base.mvvm.BaseViewModel;
|
||||
import com.example.mir4updater.databinding.ActivityMainBinding;
|
||||
@@ -1,14 +1,24 @@
|
||||
package com.example.mir4updater.activity;
|
||||
package com.example.mir4updater.activity.update;
|
||||
|
||||
import com.example.mir4updater.R;
|
||||
import com.example.mir4updater.base.mvvm.BaseMvvmActivity;
|
||||
import com.example.mir4updater.databinding.ActivityMainBinding;
|
||||
import com.example.mir4updater.databinding.ActivityUpdateBinding;
|
||||
|
||||
public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBinding> {
|
||||
public class UpdateActivity extends BaseMvvmActivity<UpdateViewModel, ActivityUpdateBinding> {
|
||||
|
||||
@Override
|
||||
public boolean setNightMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setfitWindow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.activity_main;
|
||||
return R.layout.activity_update;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,7 +39,7 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
|
||||
}
|
||||
|
||||
public class BtnClick{
|
||||
public class BtnClick {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.example.mir4updater.activity.update;
|
||||
|
||||
import com.example.mir4updater.base.mvvm.BaseViewModel;
|
||||
import com.example.mir4updater.databinding.ActivityUpdateBinding;
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
|
||||
public class UpdateViewModel extends BaseViewModel <ActivityUpdateBinding, ActivityEvent>{
|
||||
|
||||
@Override
|
||||
public ActivityUpdateBinding getVDBinding() {
|
||||
return binding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package com.example.mir4updater.activity.upload;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import com.blankj.utilcode.util.GsonUtils;
|
||||
import com.example.mir4updater.R;
|
||||
import com.example.mir4updater.base.mvvm.BaseMvvmActivity;
|
||||
import com.example.mir4updater.bean.FileItem;
|
||||
import com.example.mir4updater.bean.UploadFilesInfo;
|
||||
import com.example.mir4updater.config.CommonConfig;
|
||||
import com.example.mir4updater.databinding.ActivityUploadBinding;
|
||||
import com.example.mir4updater.utils.ApkUtils;
|
||||
import com.example.mir4updater.utils.HashUtils;
|
||||
import com.trello.rxlifecycle4.RxLifecycle;
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.ObservableEmitter;
|
||||
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
|
||||
import io.reactivex.rxjava3.core.Observer;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class UploadActivity extends BaseMvvmActivity<UploadViewModel, ActivityUploadBinding> {
|
||||
private static final String TAG = "UploadActivity";
|
||||
|
||||
private static final int ANDROID_REQUEST_CODE = 9819;
|
||||
|
||||
private DocumentFile mDocumentFile;
|
||||
|
||||
@Override
|
||||
public boolean setNightMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setfitWindow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.activity_upload;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataBinding() {
|
||||
mViewModel.setCtx(this);
|
||||
mViewModel.setLifecycle(getLifecycleSubject());
|
||||
mViewModel.setVDBinding(mViewDataBinding);
|
||||
mViewDataBinding.setClick(new BtnClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initData() {
|
||||
Log.e(TAG, "initData: " + getFilesDir());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
Log.e(TAG, "onActivityResult: " + requestCode);
|
||||
if (requestCode == ANDROID_REQUEST_CODE) {
|
||||
startTraversal(mDocumentFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void getPermssions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// 请求Android/data目录的权限,Android/obb目录则把data替换成obb即可。
|
||||
Uri treeUri = Uri.parse(CommonConfig.MIR4_CONTENT_PATH);
|
||||
mDocumentFile = DocumentFile.fromTreeUri(this, treeUri);
|
||||
if (mDocumentFile != null) {
|
||||
if (mDocumentFile.canRead()) {
|
||||
startTraversal(mDocumentFile);
|
||||
} else {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, mDocumentFile.getUri());
|
||||
startActivityForResult(intent, ANDROID_REQUEST_CODE);
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
// intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
// startActivityForResult(intent, REQUEST_CODE);
|
||||
}
|
||||
|
||||
// 在Activity中启动遍历
|
||||
private List<FileItem> fileList = new ArrayList<>();
|
||||
|
||||
private void startTraversal(DocumentFile root) {
|
||||
fileList.clear();
|
||||
// 遍历完成后更新UI(例如RecyclerView)
|
||||
traverseDocumentFile(root);
|
||||
}
|
||||
|
||||
// 递归遍历DocumentFile
|
||||
// 递归遍历并填充列表
|
||||
private void traverseDocumentFile(DocumentFile documentFile, List<FileItem> resultList, String path) {
|
||||
if (documentFile == null || !documentFile.exists()) return;
|
||||
|
||||
DocumentFile[] files = documentFile.listFiles();
|
||||
if (files == null) return;
|
||||
|
||||
for (DocumentFile file : files) {
|
||||
if (!file.isDirectory()) {
|
||||
Log.i(TAG, "traverseDocumentFile: " + getFileMd5(file.getUri()));
|
||||
}
|
||||
// 创建条目并加入列表
|
||||
FileItem item = new FileItem(
|
||||
file.getName(),
|
||||
file.isDirectory(),
|
||||
file.length(),
|
||||
file.getUri(),
|
||||
path
|
||||
);
|
||||
resultList.add(item);
|
||||
|
||||
// 如果是目录则递归遍历
|
||||
if (file.isDirectory()) {
|
||||
traverseDocumentFile(file, resultList, path + file.getName() + "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将会生成一个文件列表的json文件,以文件的sha1作为唯一标识上传至服务器
|
||||
*
|
||||
* @param root
|
||||
*/
|
||||
private void traverseDocumentFile(DocumentFile root) {
|
||||
Observable.create(new ObservableOnSubscribe<List<FileItem>>() {
|
||||
@Override
|
||||
public void subscribe(@NonNull ObservableEmitter<List<FileItem>> emitter) throws Throwable {
|
||||
fileList.clear();
|
||||
traverseDocumentFile(root, fileList, "/");
|
||||
emitter.onNext(fileList);
|
||||
}
|
||||
}).compose(RxLifecycle.bindUntilEvent(getLifecycleSubject(), ActivityEvent.DESTROY))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Observer<List<FileItem>>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
Log.e("traverseDocumentFile", "onSubscribe: ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull List<FileItem> fileItems) {
|
||||
Log.e("traverseDocumentFile", "onNext: " + fileItems);
|
||||
Log.e("traverseDocumentFile", "onNext: " + fileItems.hashCode());
|
||||
File dir = getExternalFilesDir(null); // 默认目录,可指定子目录如 "Documents"
|
||||
File file = new File(dir, System.currentTimeMillis() + ".json");
|
||||
try (FileWriter writer = new FileWriter(file)) {
|
||||
UploadFilesInfo uploadFilesInfo = new UploadFilesInfo();
|
||||
uploadFilesInfo.setHashCode(fileItems.hashCode());
|
||||
uploadFilesInfo.setFiles(fileItems);
|
||||
String content = GsonUtils.toJson(uploadFilesInfo);
|
||||
writer.write(content);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e("traverseDocumentFile", "onError: " + e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Log.e("traverseDocumentFile", "onComplete: ");
|
||||
mViewDataBinding.tvUpdate.setEnabled(true);
|
||||
mViewDataBinding.tvUpdate.setText("创建更新");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String getFileMd5(Uri uri) {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = getContentResolver().openInputStream(uri);
|
||||
if (inputStream != null) {
|
||||
return HashUtils.calculateMd5(inputStream);
|
||||
}
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
Log.e("FileHashUtils", "Error calculating MD5", e);
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
Log.e("FileHashUtils", "Error closing stream", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public class BtnClick {
|
||||
public void openMir4(View view) {
|
||||
ApkUtils.openPackage(UploadActivity.this, CommonConfig.MIR4_PACKAGE_NAME);
|
||||
}
|
||||
|
||||
public void createUpdate(View view) {
|
||||
mViewDataBinding.tvUpdate.setEnabled(false);
|
||||
mViewDataBinding.tvUpdate.setText("请稍候");
|
||||
getPermssions();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.example.mir4updater.activity.upload;
|
||||
|
||||
import com.example.mir4updater.base.mvvm.BaseViewModel;
|
||||
import com.example.mir4updater.databinding.ActivityUploadBinding;
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
|
||||
public class UploadViewModel extends BaseViewModel<ActivityUploadBinding, ActivityEvent> {
|
||||
|
||||
@Override
|
||||
public ActivityUploadBinding getVDBinding() {
|
||||
return binding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
||||
}
|
||||
}
|
||||
56
app/src/main/java/com/example/mir4updater/bean/FileItem.java
Normal file
56
app/src/main/java/com/example/mir4updater/bean/FileItem.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package com.example.mir4updater.bean;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class FileItem implements Serializable {
|
||||
private static final long serialVersionUID = -2104809069386725078L;
|
||||
|
||||
|
||||
private String name;
|
||||
private boolean isDirectory;
|
||||
private long size;
|
||||
private Uri uri;
|
||||
private String pathName;
|
||||
|
||||
public FileItem(String name, boolean isDirectory, long size, Uri uri,String path) {
|
||||
this.name = name != null ? name : "Unnamed";
|
||||
this.isDirectory = isDirectory;
|
||||
this.size = isDirectory ? 0 : size; // 目录大小为0
|
||||
this.uri = uri;
|
||||
this.pathName = path;
|
||||
}
|
||||
|
||||
// Getter方法
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isDirectory() {
|
||||
return isDirectory;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public String getPathName() {
|
||||
return pathName;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.example.mir4updater.bean;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
public class UploadFilesInfo implements Serializable {
|
||||
private static final long serialVersionUID = 6178596317886210704L;
|
||||
|
||||
long hashCode;
|
||||
List<FileItem> files;
|
||||
|
||||
public long getHashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
public void setHashCode(long hashCode) {
|
||||
this.hashCode = hashCode;
|
||||
}
|
||||
|
||||
public List<FileItem> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public void setFiles( List<FileItem> files) {
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.example.mir4updater.config;
|
||||
|
||||
public class CommonConfig {
|
||||
public static final String MMKV_ID = "InterProcessKV";
|
||||
public static final String MIR4_PACKAGE_NAME ="com.wemade.mir4global";
|
||||
public static final String MIR4_CONTENT_PATH = "content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata%2Fcom.wemade.mir4global%2Ffiles";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.example.mir4updater.utils;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hjq.toast.Toaster;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ApkUtils {
|
||||
private static final String TAG = "ApkUtils";
|
||||
|
||||
public static boolean openPackage(Context context, String packageName) {
|
||||
Context pkgContext = getPackageContext(context, packageName);
|
||||
Intent intent = getAppOpenIntentByPackageName(context, packageName);
|
||||
if (pkgContext != null && intent != null) {
|
||||
pkgContext.startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Context getPackageContext(Context context, String packageName) {
|
||||
Context pkgContext = null;
|
||||
if (context.getPackageName().equals(packageName)) {
|
||||
pkgContext = context;
|
||||
} else {
|
||||
// 创建第三方应用的上下文环境
|
||||
try {
|
||||
pkgContext = context.createPackageContext(packageName,
|
||||
Context.CONTEXT_IGNORE_SECURITY
|
||||
| Context.CONTEXT_INCLUDE_CODE);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return pkgContext;
|
||||
}
|
||||
|
||||
public static Intent getAppOpenIntentByPackageName(Context context, String packageName) {
|
||||
//Activity完整名
|
||||
String mainAct = null;
|
||||
//根据包名寻找
|
||||
PackageManager pkgMag = context.getPackageManager();
|
||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
List<ResolveInfo> list = pkgMag.queryIntentActivities(intent, PackageManager.GET_ACTIVITIES);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
ResolveInfo info = list.get(i);
|
||||
if (info.activityInfo.packageName.equals(packageName)) {
|
||||
mainAct = info.activityInfo.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (TextUtils.isEmpty(mainAct)) {
|
||||
return null;
|
||||
}
|
||||
intent.setComponent(new ComponentName(packageName, mainAct));
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static boolean openPackage(Context context, String packageName, String className) {
|
||||
if (TextUtils.isEmpty(className)) {
|
||||
return openPackage(context, packageName);
|
||||
}
|
||||
ComponentName cn = new ComponentName(packageName, className);
|
||||
Intent intent = new Intent();
|
||||
intent.setComponent(cn);
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (context != null) {
|
||||
try {
|
||||
context.startActivity(intent);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "openPackage: " + e.getMessage());
|
||||
Toaster.show("打开失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.example.mir4updater.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class HashUtils {
|
||||
|
||||
|
||||
public static String getFileMD5(File file) throws NoSuchAlgorithmException, IOException {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
byte[] dataBytes = new byte[1024];
|
||||
|
||||
int bytesRead;
|
||||
while ((bytesRead = fis.read(dataBytes)) != -1) {
|
||||
md.update(dataBytes, 0, bytesRead);
|
||||
}
|
||||
|
||||
byte[] mdBytes = md.digest();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte mdByte : mdBytes) {
|
||||
sb.append(Integer.toString((mdByte & 0xff) + 0x100, 16).substring(1));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String calculateMd5(InputStream inputStream)
|
||||
throws NoSuchAlgorithmException, IOException {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
return bytesToHex(digest.digest());
|
||||
}
|
||||
|
||||
|
||||
public static String calculateSHA1(File file) throws NoSuchAlgorithmException, IOException {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
byte[] hashBytes = digest.digest();
|
||||
return bytesToHex(hashBytes);
|
||||
}
|
||||
|
||||
public static String calculateSHA256(File file) throws NoSuchAlgorithmException, IOException {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
byte[] hashBytes = digest.digest();
|
||||
return bytesToHex(hashBytes);
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
String hex = String.format("%02x", b & 0xFF);
|
||||
/*大写*/
|
||||
// String hex = String.format("%02X", b & 0xFF);
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.example.mir4updater.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import com.example.mir4updater.config.CommonConfig;
|
||||
import com.hjq.permissions.Permission;
|
||||
|
||||
public class PermissionUtils {
|
||||
public static final String[] Location_Permission = new String[]{
|
||||
// Permission.READ_EXTERNAL_STORAGE,
|
||||
// Permission.WRITE_EXTERNAL_STORAGE,
|
||||
Permission.MANAGE_EXTERNAL_STORAGE,
|
||||
};
|
||||
|
||||
public static boolean checkAppData(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// 请求Android/data目录的权限,Android/obb目录则把data替换成obb即可。
|
||||
Uri treeUri = Uri.parse(CommonConfig.MIR4_CONTENT_PATH);
|
||||
DocumentFile documentFile = DocumentFile.fromTreeUri(context, treeUri);
|
||||
if (documentFile != null) {
|
||||
return documentFile.canRead();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.example.mir4updater.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class UriUtils {
|
||||
// 转换文件路径为 content:// Uri
|
||||
public static Uri getUriFromFile(Context context, String filePath) {
|
||||
File file = new File(filePath);
|
||||
return FileProvider.getUriForFile(
|
||||
context,
|
||||
context.getPackageName() + ".fileprovider", // 必须与 Manifest 中的 authorities 一致
|
||||
file
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user