修复点击效果和安装权限问题

- 修复Hook类中canRequestPackageInstalls()返回false导致的安装失败问题
- 优化点击效果颜色,从刺眼的亮黄色改为柔和的半透明白色
- 调整选中状态背景色,使用更柔和的半透明黄色
- 清理项目中的临时文件和重复文件
- 更新版本号到3.0.8
This commit is contained in:
您的名字
2025-10-14 17:50:42 +08:00
parent cf56f091f3
commit 54280b68eb
294 changed files with 166 additions and 36501 deletions
@@ -32,7 +32,7 @@ public class Updater implements Download.Callback {
private boolean forceCheck; // 是否为手动检查
private File getFile() {
return Path.cache("update.apk");
return Path.root("Download", "XMBOX-update.apk");
}
private String getJson() {
@@ -40,6 +40,26 @@ public class Updater implements Download.Callback {
}
private String getApk() {
// 使用JSON中指定的具体下载路径
try {
String response = OkHttp.string(getJson());
JSONObject object = new JSONObject(response);
JSONObject downloads = object.optJSONObject("downloads");
if (downloads != null) {
String abi = BuildConfig.FLAVOR_abi;
String downloadPath = downloads.optString(abi);
if (!downloadPath.isEmpty()) {
// 直接构建完整URL,不通过Github.getApk()避免重复添加路径
String baseUrl = Github.useCnMirror() ?
"https://gitee.com/ochenoktochen/XMBOX-Release/raw/main" :
"https://raw.githubusercontent.com/Tosencen/XMBOX-Release/main";
return baseUrl + "/apk/" + (dev ? "dev" : "release") + "/" + downloadPath;
}
}
} catch (Exception e) {
Logger.e("Failed to get download path from JSON: " + e.getMessage());
}
// 回退到原来的方式
return Github.getApk(dev, BuildConfig.FLAVOR_mode + "-" + BuildConfig.FLAVOR_abi);
}
@@ -2,11 +2,13 @@ package com.fongmi.android.tv.utils;
import com.fongmi.android.tv.App;
import com.github.catvod.net.OkHttp;
import com.github.catvod.utils.Logger;
import com.github.catvod.utils.Path;
import com.google.common.net.HttpHeaders;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
@@ -47,14 +49,22 @@ public class Download {
private void doInBackground() {
try (Response res = OkHttp.newCall(url, url).execute()) {
Path.create(file);
download(res.body().byteStream(), Double.parseDouble(res.header(HttpHeaders.CONTENT_LENGTH, "1")));
long expectedLength = Long.parseLong(res.header(HttpHeaders.CONTENT_LENGTH, "0"));
download(res.body().byteStream(), expectedLength);
// 验证下载的文件
if (!verifyDownloadedFile(file, expectedLength)) {
App.post(() -> {if (callback != null) callback.error("下载的文件可能已损坏,请重试");});
return;
}
App.post(() -> {if (callback != null) callback.success(file);});
} catch (Exception e) {
App.post(() -> {if (callback != null) callback.error(e.getMessage());});
}
}
private void download(InputStream is, double length) throws Exception {
private void download(InputStream is, long length) throws Exception {
try (BufferedInputStream input = new BufferedInputStream(is); FileOutputStream os = new FileOutputStream(file)) {
byte[] buffer = new byte[4096];
int readBytes;
@@ -68,6 +78,45 @@ public class Download {
}
}
private boolean verifyDownloadedFile(File file, long expectedLength) {
try {
// 检查文件大小
if (file.length() != expectedLength) {
Logger.e("File size mismatch: expected " + expectedLength + ", actual " + file.length());
return false;
}
// 检查APK文件头 (ZIP文件头)
if (file.length() < 4) return false;
try (FileInputStream fis = new FileInputStream(file)) {
byte[] header = new byte[4];
fis.read(header);
// ZIP文件头应该是 0x504B0304 (PK..)
if (header[0] != 0x50 || header[1] != 0x4B || header[2] != 0x03 || header[3] != 0x04) {
Logger.e("Invalid APK file header");
return false;
}
// 额外验证:检查APK文件是否完整
// 尝试读取ZIP文件结构
fis.getChannel().position(0);
byte[] buffer = new byte[1024];
int bytesRead = fis.read(buffer);
if (bytesRead < 4) {
Logger.e("APK file too small or corrupted");
return false;
}
}
Logger.d("APK file verification passed: " + file.getName() + " (" + file.length() + " bytes)");
return true;
} catch (Exception e) {
Logger.e("File verification failed: " + e.getMessage());
return false;
}
}
public interface Callback {
void progress(int progress);
@@ -11,6 +11,7 @@ import androidx.core.content.FileProvider;
import com.fongmi.android.tv.App;
import com.fongmi.android.tv.R;
import com.fongmi.android.tv.impl.Callback;
import com.github.catvod.utils.Logger;
import com.github.catvod.utils.Path;
import java.io.BufferedInputStream;
@@ -34,11 +35,35 @@ public class FileUtil {
}
public static void openFile(File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(getShareUri(file), FileUtil.getMimeType(file.getName()));
App.get().startActivity(intent);
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 对于APK文件,使用特定的MIME类型
String mimeType = file.getName().toLowerCase().endsWith(".apk") ?
"application/vnd.android.package-archive" : getMimeType(file.getName());
intent.setDataAndType(getShareUri(file), mimeType);
// 添加额外的安装权限检查
if (file.getName().toLowerCase().endsWith(".apk")) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
App.get().startActivity(intent);
} catch (Exception e) {
Logger.e("Failed to open file: " + e.getMessage());
// 如果失败,尝试使用通用方式
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(getShareUri(file), "*/*");
App.get().startActivity(intent);
} catch (Exception ex) {
Logger.e("Fallback open file also failed: " + ex.getMessage());
}
}
}
public static void gzipCompress(File target) {
@@ -32,7 +32,7 @@ public class Updater implements Download.Callback {
private boolean forceCheck; // 是否为手动检查
private File getFile() {
return Path.cache("update.apk");
return Path.root("Download", "XMBOX-update.apk");
}
private String getJson() {
@@ -40,7 +40,31 @@ public class Updater implements Download.Callback {
}
private String getApk() {
return Github.getApk(dev, BuildConfig.FLAVOR_mode + "-" + BuildConfig.FLAVOR_abi);
// 使用JSON中指定的具体下载路径
try {
String response = OkHttp.string(getJson());
JSONObject object = new JSONObject(response);
JSONObject downloads = object.optJSONObject("downloads");
if (downloads != null) {
String abi = BuildConfig.FLAVOR_abi;
String downloadPath = downloads.optString(abi);
if (!downloadPath.isEmpty()) {
// 直接构建完整URL,不通过Github.getApk()避免重复添加路径
String baseUrl = Github.useCnMirror() ?
"https://gitee.com/ochenoktochen/XMBOX-Release/raw/main" :
"https://raw.githubusercontent.com/Tosencen/XMBOX-Release/main";
String fullUrl = baseUrl + "/apk/" + (dev ? "dev" : "release") + "/" + downloadPath;
Logger.d("APK download URL: " + fullUrl);
return fullUrl;
}
}
} catch (Exception e) {
Logger.e("Failed to get download path from JSON: " + e.getMessage());
}
// 回退到原来的方式
String fallbackUrl = Github.getApk(dev, BuildConfig.FLAVOR_mode + "-" + BuildConfig.FLAVOR_abi);
Logger.d("APK fallback URL: " + fallbackUrl);
return fallbackUrl;
}
public static Updater create() {
@@ -83,11 +107,17 @@ public class Updater implements Download.Callback {
}
private void doInBackground(Activity activity) {
Logger.d("Updater: Starting update check...");
try {
String response = OkHttp.string(getJson());
String jsonUrl = getJson();
Logger.d("Updater: JSON URL: " + jsonUrl);
String response = OkHttp.string(jsonUrl);
Logger.d("Updater: JSON response length: " + response.length());
// 检查响应是否包含错误信息,只在手动检查时提示
if (response.contains("rate limit exceeded")) {
Logger.e("Updater: Rate limit exceeded");
if (forceCheck) {
App.post(() -> Notify.show("检查更新失败:API请求过于频繁,请稍后重试"));
}
@@ -95,6 +125,7 @@ public class Updater implements Download.Callback {
}
if (response.contains("Not Found Project") || response.contains("Not Found")) {
Logger.e("Updater: Project not found");
if (forceCheck) {
App.post(() -> Notify.show("检查更新失败:更新服务暂时不可用"));
}
@@ -105,9 +136,15 @@ public class Updater implements Download.Callback {
String name = object.optString("name");
String desc = object.optString("desc");
int code = object.optInt("code");
Logger.d("Updater: Remote version: " + name + " (code: " + code + ")");
Logger.d("Updater: Local version: " + BuildConfig.VERSION_NAME + " (code: " + BuildConfig.VERSION_CODE + ")");
if (need(code, name)) {
Logger.d("Updater: Update needed, showing dialog");
App.post(() -> show(activity, name, desc));
} else {
Logger.d("Updater: No update needed");
// 只在手动检查时提示已是最新版
if (forceCheck) {
App.post(() -> Notify.show("已是最新版本 " + name));
@@ -115,6 +152,7 @@ public class Updater implements Download.Callback {
Logger.d("Already latest version: " + name);
}
} catch (Exception e) {
Logger.e("Updater: Exception during update check: " + e.getMessage());
e.printStackTrace();
// 只在手动检查时提示网络错误
if (forceCheck) {
@@ -3,7 +3,7 @@
android:color="?attr/colorControlHighlight">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<solid android:color="@color/yellow_500" />
<solid android:color="@color/yellow_50" />
<corners android:radius="8dp" />
<padding
android:bottom="14dp"
@@ -3,7 +3,7 @@
android:color="?attr/colorControlHighlight">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<solid android:color="@color/yellow_500" />
<solid android:color="@color/yellow_50" />
<corners android:radius="8dp" />
<padding
android:bottom="10dp"
@@ -3,7 +3,7 @@
android:color="?attr/colorControlHighlight">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<solid android:color="@color/yellow_500" />
<solid android:color="@color/yellow_50" />
<corners android:radius="8dp" />
<padding
android:bottom="18dp"
@@ -3,7 +3,7 @@
android:color="?attr/colorControlHighlight">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<solid android:color="@color/yellow_500" />
<solid android:color="@color/yellow_50" />
<corners android:radius="8dp" />
<padding
android:bottom="18dp"
+1 -1
View File
@@ -17,7 +17,7 @@
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primaryDark</item>
<item name="colorAccent">@color/accent</item>
<item name="colorControlHighlight">@color/primary</item>
<item name="colorControlHighlight">@color/white_20</item>
<item name="android:windowBackground">@null</item>
<item name="android:windowDisablePreview">true</item>
<item name="android:navigationBarColor">@color/transparent</item>