From 64f2de69e34508bd624ddc8dfb1521477e4ccee8 Mon Sep 17 00:00:00 2001 From: tongtongstudio Date: Wed, 9 Apr 2025 23:02:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8A=E4=BC=A0=E5=92=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 10 +- app/src/main/AndroidManifest.xml | 33 +- .../activity/main/MainActivity.java | 505 ++++++++++++++++++ .../activity/{ => main}/MainViewModel.java | 2 +- .../UpdateActivity.java} | 20 +- .../activity/update/UpdateViewModel.java | 18 + .../activity/upload/UploadActivity.java | 242 +++++++++ .../activity/upload/UploadViewModel.java | 18 + .../example/mir4updater/bean/FileItem.java | 56 ++ .../mir4updater/bean/UploadFilesInfo.java | 38 ++ .../mir4updater/config/CommonConfig.java | 8 + .../example/mir4updater/utils/ApkUtils.java | 90 ++++ .../example/mir4updater/utils/HashUtils.java | 83 +++ .../mir4updater/utils/PermissionUtils.java | 30 ++ .../example/mir4updater/utils/UriUtils.java | 20 + .../main/res/drawable/bt_permission_bg.xml | 18 + app/src/main/res/drawable/bt_update_bg.xml | 18 + app/src/main/res/drawable/bt_upload_bg.xml | 18 + .../main/res/drawable/dialog_background.xml | 26 + app/src/main/res/layout/activity_main.xml | 132 ++++- app/src/main/res/layout/activity_update.xml | 21 + app/src/main/res/layout/activity_upload.xml | 64 +++ .../main/res/layout/dialog_permissions.xml | 86 +++ app/src/main/res/values/colors.xml | 241 +++++++++ app/src/main/res/values/strings.xml | 3 +- app/src/main/res/values/styles.xml | 9 +- app/src/main/res/xml/file_paths.xml | 18 + settings.gradle | 2 +- 28 files changed, 1806 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/example/mir4updater/activity/main/MainActivity.java rename app/src/main/java/com/example/mir4updater/activity/{ => main}/MainViewModel.java (89%) rename app/src/main/java/com/example/mir4updater/activity/{MainActivity.java => update/UpdateActivity.java} (54%) create mode 100644 app/src/main/java/com/example/mir4updater/activity/update/UpdateViewModel.java create mode 100644 app/src/main/java/com/example/mir4updater/activity/upload/UploadActivity.java create mode 100644 app/src/main/java/com/example/mir4updater/activity/upload/UploadViewModel.java create mode 100644 app/src/main/java/com/example/mir4updater/bean/FileItem.java create mode 100644 app/src/main/java/com/example/mir4updater/bean/UploadFilesInfo.java create mode 100644 app/src/main/java/com/example/mir4updater/config/CommonConfig.java create mode 100644 app/src/main/java/com/example/mir4updater/utils/ApkUtils.java create mode 100644 app/src/main/java/com/example/mir4updater/utils/HashUtils.java create mode 100644 app/src/main/java/com/example/mir4updater/utils/PermissionUtils.java create mode 100644 app/src/main/java/com/example/mir4updater/utils/UriUtils.java create mode 100644 app/src/main/res/drawable/bt_permission_bg.xml create mode 100644 app/src/main/res/drawable/bt_update_bg.xml create mode 100644 app/src/main/res/drawable/bt_upload_bg.xml create mode 100644 app/src/main/res/drawable/dialog_background.xml create mode 100644 app/src/main/res/layout/activity_update.xml create mode 100644 app/src/main/res/layout/activity_upload.xml create mode 100644 app/src/main/res/layout/dialog_permissions.xml create mode 100644 app/src/main/res/xml/file_paths.xml diff --git a/app/build.gradle b/app/build.gradle index 7061634..2b5efb7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,13 +9,13 @@ def releaseTime() { } android { - compileSdkVersion 29 + compileSdkVersion 30 // buildToolsVersion "34.0.0" defaultConfig { applicationId "com.example.mir4updater" minSdkVersion 24 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 1 versionName "1.0" @@ -107,6 +107,7 @@ android { } } } + } @@ -120,12 +121,16 @@ dependencies { implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.viewpager2:viewpager2:1.0.0" implementation "androidx.fragment:fragment:1.4.1" + implementation "androidx.documentfile:documentfile:1.0.1" implementation 'androidx.legacy:legacy-support-v4:1.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + implementation 'com.google.android.play:app-update:2.1.0' + implementation 'com.google.android.play:app-update-ktx:2.1.0' // Kotlin扩展 + //RxJava implementation 'io.reactivex.rxjava3:rxjava:3.0.0' implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' @@ -144,7 +149,6 @@ dependencies { //google implementation 'com.google.code.gson:gson:2.9.0' implementation 'com.google.zxing:core:3.5.0' - implementation 'com.google.android.material:material:1.4.0' //生命周期管理 implementation 'com.trello.rxlifecycle4:rxlifecycle:4.0.2' implementation 'com.trello.rxlifecycle4:rxlifecycle-android:4.0.2' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0c685c8..e527a45 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,22 +1,51 @@ + + + + + + + + + - + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/mir4updater/activity/main/MainActivity.java b/app/src/main/java/com/example/mir4updater/activity/main/MainActivity.java new file mode 100644 index 0000000..dcc616b --- /dev/null +++ b/app/src/main/java/com/example/mir4updater/activity/main/MainActivity.java @@ -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 { + 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 appUpdateInfoTask = mAppUpdateManager.getAppUpdateInfo(); +// appUpdateInfoTask.addOnSuccessListener(new OnSuccessListener() { +// @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 fileList = new ArrayList<>(); + + private void startTraversal(DocumentFile root) { + fileList.clear(); + // 遍历完成后更新UI(例如RecyclerView) + traverseDocumentFile(root); + } + + // 递归遍历DocumentFile + // 递归遍历并填充列表 + private void traverseDocumentFile(DocumentFile documentFile, List 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>() { + @Override + public void subscribe(@io.reactivex.rxjava3.annotations.NonNull ObservableEmitter> 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>() { + @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 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 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 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(); + } + } +} diff --git a/app/src/main/java/com/example/mir4updater/activity/MainViewModel.java b/app/src/main/java/com/example/mir4updater/activity/main/MainViewModel.java similarity index 89% rename from app/src/main/java/com/example/mir4updater/activity/MainViewModel.java rename to app/src/main/java/com/example/mir4updater/activity/main/MainViewModel.java index d05a4b1..d19c3e3 100644 --- a/app/src/main/java/com/example/mir4updater/activity/MainViewModel.java +++ b/app/src/main/java/com/example/mir4updater/activity/main/MainViewModel.java @@ -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; diff --git a/app/src/main/java/com/example/mir4updater/activity/MainActivity.java b/app/src/main/java/com/example/mir4updater/activity/update/UpdateActivity.java similarity index 54% rename from app/src/main/java/com/example/mir4updater/activity/MainActivity.java rename to app/src/main/java/com/example/mir4updater/activity/update/UpdateActivity.java index d86999c..3c96f93 100644 --- a/app/src/main/java/com/example/mir4updater/activity/MainActivity.java +++ b/app/src/main/java/com/example/mir4updater/activity/update/UpdateActivity.java @@ -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 { +public class UpdateActivity extends BaseMvvmActivity { + + @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{ + + @Override + public ActivityUpdateBinding getVDBinding() { + return binding; + } + + @Override + public void onDestroy() { + + } +} diff --git a/app/src/main/java/com/example/mir4updater/activity/upload/UploadActivity.java b/app/src/main/java/com/example/mir4updater/activity/upload/UploadActivity.java new file mode 100644 index 0000000..20d20be --- /dev/null +++ b/app/src/main/java/com/example/mir4updater/activity/upload/UploadActivity.java @@ -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 { + 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 fileList = new ArrayList<>(); + + private void startTraversal(DocumentFile root) { + fileList.clear(); + // 遍历完成后更新UI(例如RecyclerView) + traverseDocumentFile(root); + } + + // 递归遍历DocumentFile + // 递归遍历并填充列表 + private void traverseDocumentFile(DocumentFile documentFile, List 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>() { + @Override + public void subscribe(@NonNull ObservableEmitter> 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>() { + @Override + public void onSubscribe(@NonNull Disposable d) { + Log.e("traverseDocumentFile", "onSubscribe: "); + } + + @Override + public void onNext(@NonNull List 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(); + } + + } +} diff --git a/app/src/main/java/com/example/mir4updater/activity/upload/UploadViewModel.java b/app/src/main/java/com/example/mir4updater/activity/upload/UploadViewModel.java new file mode 100644 index 0000000..2132e15 --- /dev/null +++ b/app/src/main/java/com/example/mir4updater/activity/upload/UploadViewModel.java @@ -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 { + + @Override + public ActivityUploadBinding getVDBinding() { + return binding; + } + + @Override + public void onDestroy() { + + } +} diff --git a/app/src/main/java/com/example/mir4updater/bean/FileItem.java b/app/src/main/java/com/example/mir4updater/bean/FileItem.java new file mode 100644 index 0000000..2d11c70 --- /dev/null +++ b/app/src/main/java/com/example/mir4updater/bean/FileItem.java @@ -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(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mir4updater/bean/UploadFilesInfo.java b/app/src/main/java/com/example/mir4updater/bean/UploadFilesInfo.java new file mode 100644 index 0000000..c154efb --- /dev/null +++ b/app/src/main/java/com/example/mir4updater/bean/UploadFilesInfo.java @@ -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 files; + + public long getHashCode() { + return hashCode; + } + + public void setHashCode(long hashCode) { + this.hashCode = hashCode; + } + + public List getFiles() { + return files; + } + + public void setFiles( List files) { + this.files = files; + } + + @NonNull + @Override + public String toString() { + return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString(); + } +} diff --git a/app/src/main/java/com/example/mir4updater/config/CommonConfig.java b/app/src/main/java/com/example/mir4updater/config/CommonConfig.java new file mode 100644 index 0000000..d5b5b07 --- /dev/null +++ b/app/src/main/java/com/example/mir4updater/config/CommonConfig.java @@ -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"; + +} diff --git a/app/src/main/java/com/example/mir4updater/utils/ApkUtils.java b/app/src/main/java/com/example/mir4updater/utils/ApkUtils.java new file mode 100644 index 0000000..85e4fd2 --- /dev/null +++ b/app/src/main/java/com/example/mir4updater/utils/ApkUtils.java @@ -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 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; + } +} diff --git a/app/src/main/java/com/example/mir4updater/utils/HashUtils.java b/app/src/main/java/com/example/mir4updater/utils/HashUtils.java new file mode 100644 index 0000000..8b8548d --- /dev/null +++ b/app/src/main/java/com/example/mir4updater/utils/HashUtils.java @@ -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(); + } +} + diff --git a/app/src/main/java/com/example/mir4updater/utils/PermissionUtils.java b/app/src/main/java/com/example/mir4updater/utils/PermissionUtils.java new file mode 100644 index 0000000..0af48c5 --- /dev/null +++ b/app/src/main/java/com/example/mir4updater/utils/PermissionUtils.java @@ -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; + } +} diff --git a/app/src/main/java/com/example/mir4updater/utils/UriUtils.java b/app/src/main/java/com/example/mir4updater/utils/UriUtils.java new file mode 100644 index 0000000..c57168e --- /dev/null +++ b/app/src/main/java/com/example/mir4updater/utils/UriUtils.java @@ -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 + ); + } +} diff --git a/app/src/main/res/drawable/bt_permission_bg.xml b/app/src/main/res/drawable/bt_permission_bg.xml new file mode 100644 index 0000000..211e31f --- /dev/null +++ b/app/src/main/res/drawable/bt_permission_bg.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bt_update_bg.xml b/app/src/main/res/drawable/bt_update_bg.xml new file mode 100644 index 0000000..d6f2998 --- /dev/null +++ b/app/src/main/res/drawable/bt_update_bg.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bt_upload_bg.xml b/app/src/main/res/drawable/bt_upload_bg.xml new file mode 100644 index 0000000..21f3b5f --- /dev/null +++ b/app/src/main/res/drawable/bt_upload_bg.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dialog_background.xml b/app/src/main/res/drawable/dialog_background.xml new file mode 100644 index 0000000..70157ff --- /dev/null +++ b/app/src/main/res/drawable/dialog_background.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9a83d3f..d7de177 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -2,26 +2,140 @@ + tools:context=".activity.main.MainActivity"> + + type="com.example.mir4updater.activity.main.MainActivity.BtnClick" /> + + + + + + + - + app:layout_constraintTop_toTopOf="parent"> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_update.xml b/app/src/main/res/layout/activity_update.xml new file mode 100644 index 0000000..80debbf --- /dev/null +++ b/app/src/main/res/layout/activity_update.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_upload.xml b/app/src/main/res/layout/activity_upload.xml new file mode 100644 index 0000000..78bf8a4 --- /dev/null +++ b/app/src/main/res/layout/activity_upload.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_permissions.xml b/app/src/main/res/layout/dialog_permissions.xml new file mode 100644 index 0000000..cec6d24 --- /dev/null +++ b/app/src/main/res/layout/dialog_permissions.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 030098f..6dfa484 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,4 +3,245 @@ #6200EE #3700B3 #03DAC5 + + + #00000000 + #FFE2C59B + #FFFFFFF0 + #FFFFFFE0 + #FFFFFF00 + #FFFFFAFA + #FFFFFAF0 + #FFFFFACD + #FFFFF8DC + #FFFFF5EE + #FFFFF0F5 + #FFFFEFD5 + #FFFFEBCD + #FFFFE4E1 + #FFFFE4C4 + #FFFFE4B5 + #FFFFDEAD + #FFFFDAB9 + #FFFFD700 + #FFFFC0CB + #FFFFB6C1 + #FFFFA500 + #FFFFA07A + #FFFF8C00 + #FFFF7F50 + #FFFF69B4 + #FFFF6347 + #FFFF4500 + #FFFF1493 + #FFFF00FF + #FFFF0000 + #FFFDF5E6 + #FFFAFAD2 + #FFFAF0E6 + #FFFAEBD7 + #FFFA8072 + #FFF8F8FF + #FFF5FFFA + #FFF5F5F5 + #FFF5F5DC + #FFF5DEB3 + #FFF4A460 + #FFF0FFFF + #FFF0F8FF + #FFF0E68C + #FFF08080 + #FFEEE8AA + #FFEE82EE + #FFE9967A + #FFE6E6FA + #FFE0FFFF + #FFDEB887 + #FFDDA0DD + #FFDCDCDC + #FFDC143C + #FFDB7093 + #FFDAA520 + #FFDA70D6 + #FFD8BFD8 + #FFD3D3D3 + #FFD2B48C + #FFD2691E + #FFCD853F + #FFCD5C5C + #FFC71585 + #FFC0C0C0 + #FFBDB76B + #FFBC8F8F + #FFBA55D3 + #FFB8860B + #FFB22222 + #FFB0E0E6 + #FFB0C4DE + #FFAFEEEE + #FFADFF2F + #FFADD8E6 + #FFA9A9A9 + #FFA52A2A + #FFA0522D + #FF9932CC + #FF98FB98 + #FF9400D3 + #FF9370DB + #FF90EE90 + #FF8FBC8F + #FF8B4513 + #FF8B008B + #FF8B0000 + #FF8A2BE2 + #FF87CEFA + #FF87CEEB + #FF808080 + #FF808000 + #FF800080 + #FF800000 + #FF7FFFD4 + #FF7FFF00 + #FF7CFC00 + #FF7B68EE + #FF778899 + #FF708090 + #FF6B8E23 + #FF6A5ACD + #FF696969 + #FF66CDAA + #FF6495ED + #FF5F9EA0 + #FF556B2F + #FF4B0082 + #FF48D1CC + #FF483D8B + #FF4682B4 + #FF4169E1 + #FF40E0D0 + #FF3CB371 + #FF32CD32 + #FF2F4F4F + #FF2E8B57 + #FF228B22 + #FF20B2AA + #FF1E90FF + #FF191970 + #FF00FFFF + #FF00FF7F + #FF00FF00 + #FF00FA9A + #FF00CED1 + #FF00BFFF + #FF008B8B + #FF008080 + #FF008000 + #FF006400 + #FF0000FF + #FF0000CD + #FF00008B + #FF000080 + #FF2B2B2B + + + #ffffff + #ffffe0 + #fffaf0 + #fffacd + #fff8dc + #fff5ee + #fff0f5 + #ffefd5 + #ffebcd + #ffe4e1 + #ffdead + #ffdab9 + #ffb6c1 + #ffa07a + #ff8c00 + #ff69b4 + #ff4500 + #ff1493 + #ff00ff + #fdf5e6 + #fafad2 + #faebd7 + #f8f8ff + #f5fffa + #f5f5f5 + #f4a460 + #f0fff0 + #f0f8ff + #f08080 + #eee8aa + #e9967a + #e0ffff + #deb887 + #dcdcdc + #db7093 + #d3d3d3 + #d3d3d3 + #cd5c5c + #c71585 + #bdb76b + #bc8f8f + #ba55d3 + #b8860b + #b0e0e6 + #b0c4de + #afeeee + #adff2f + #add8e6 + #a9a9a9 + #a9a9a9 + #9932cc + #98fb98 + #9400d3 + #9370db + #90ee90 + #8fbc8f + #8b4513 + #8b008b + #8b0000 + #8a2be2 + #87cefa + #87ceeb + #808080 + #7cfc00 + #7b68ee + #778899 + #778899 + #708090 + #708090 + #6b8e23 + #6a5acd + #696969 + #696969 + #66cdaa + #6495ed + #5f9ea0 + #556b2f + #48d1cc + #483d8b + #4682b4 + #4169e1 + #3cb371 + #32cd32 + #2f4f4f + #2f4f4f + #2e8b57 + #228b22 + #20b2aa + #1e90ff + #191970 + #00ffff + #00ff7f + #00fa9a + #00ced1 + #00bfff + #008b8b + #006400 + #0000cd + #00008b + #000000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d8eaa91..9f2e5c6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ - Mir4Updater + MIR4Updater + 请先确保MIR4已经更新到最新版本\n点击 创建更新 会计算app内部所有文件的sha1\n点击 上传 将会把MIR4内部的所有pak文件上传至服务器\n计算和上传用时可能会很久 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 5885930..d9a3652 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,7 +1,14 @@ - --> + +