905 lines
36 KiB
Java
905 lines
36 KiB
Java
package com.gh.common.util;
|
||
|
||
import android.app.ActivityManager;
|
||
import android.content.Context;
|
||
import android.content.Intent;
|
||
import android.content.pm.ApplicationInfo;
|
||
import android.content.pm.PackageInfo;
|
||
import android.content.pm.PackageManager;
|
||
import android.content.pm.PackageManager.NameNotFoundException;
|
||
import android.content.pm.Signature;
|
||
import android.graphics.drawable.Drawable;
|
||
import android.net.Uri;
|
||
import android.os.Build;
|
||
import android.os.Bundle;
|
||
import android.os.PowerManager;
|
||
import android.text.TextUtils;
|
||
import android.util.AndroidException;
|
||
|
||
import androidx.annotation.NonNull;
|
||
import androidx.annotation.Nullable;
|
||
import androidx.annotation.WorkerThread;
|
||
import androidx.webkit.WebViewCompat;
|
||
|
||
import com.android.apksig.ApkVerifier;
|
||
import com.android.apksig.internal.apk.ApkSigningBlockUtilsLite;
|
||
import com.g00fy2.versioncompare.Version;
|
||
import com.gh.common.xapk.XapkInstaller;
|
||
import com.gh.gamecenter.BuildConfig;
|
||
import com.gh.gamecenter.common.constant.Constants;
|
||
import com.gh.gamecenter.common.utils.ExtensionsKt;
|
||
import com.gh.gamecenter.core.utils.MD5Utils;
|
||
import com.gh.gamecenter.feature.entity.ApkEntity;
|
||
import com.gh.gamecenter.feature.entity.GameEntity;
|
||
import com.gh.gamecenter.entity.GameUpdateEntity;
|
||
import com.gh.gamecenter.feature.utils.SentryHelper;
|
||
import com.gh.gamecenter.manager.PackagesManager;
|
||
import com.gh.vspace.VHelper;
|
||
import com.gh.vspace.db.VGameEntity;
|
||
import com.halo.assistant.HaloApp;
|
||
import com.lightgame.utils.Utils;
|
||
|
||
import net.dongliu.apk.parser.ApkFile;
|
||
import net.dongliu.apk.parser.bean.ApkMeta;
|
||
|
||
import org.json.JSONArray;
|
||
import org.json.JSONException;
|
||
import org.json.JSONObject;
|
||
|
||
import java.io.ByteArrayInputStream;
|
||
import java.io.File;
|
||
import java.io.IOException;
|
||
import java.io.InputStream;
|
||
import java.security.cert.CertificateException;
|
||
import java.security.cert.CertificateFactory;
|
||
import java.security.cert.X509Certificate;
|
||
import java.util.ArrayList;
|
||
import java.util.Enumeration;
|
||
import java.util.HashMap;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.zip.ZipEntry;
|
||
import java.util.zip.ZipFile;
|
||
|
||
public class PackageUtils {
|
||
|
||
private static final String TAG = "PackageUtils";
|
||
|
||
public static String getInstallPackageInfoSourceDir(String packageName) {
|
||
try {
|
||
return HaloApp.getInstance().getApplication().getPackageManager().getPackageInfo(packageName,
|
||
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).applicationInfo.sourceDir;
|
||
} catch (NameNotFoundException e) {
|
||
// do nothing
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 根据packageName和模拟器apk文件的MD5去判断目标文件是否需要更新
|
||
*
|
||
* @param packageName 包名
|
||
* @param MD5Str 文件MD5
|
||
*/
|
||
public static boolean isInstalledApkMatchedMd5(String packageName, String MD5Str) {
|
||
if (MD5Str == null){
|
||
Utils.log("MD5Str is null");
|
||
return false;
|
||
}
|
||
String path = getInstallPackageInfoSourceDir(packageName);
|
||
if (path == null){
|
||
Utils.log("File path null");
|
||
return false;
|
||
}
|
||
File file = new File(path);
|
||
return !(MD5Str.equals(MD5Utils.calculateMD5(file)));
|
||
}
|
||
|
||
/**
|
||
* 判断是否可以更新
|
||
* @param gameEntity 游戏摘要实体
|
||
* @param isVGame 是否为畅玩游戏(走特殊的判断更新逻辑)
|
||
*/
|
||
public static List<GameUpdateEntity> getUpdateData(GameEntity gameEntity, boolean isVGame) {
|
||
|
||
List<GameUpdateEntity> updateList = new ArrayList<>();
|
||
|
||
// 插件更新
|
||
for (ApkEntity apkEntity : gameEntity.getApk()) {
|
||
if (isCanUpdate(apkEntity, gameEntity.getId())) {
|
||
GameUpdateEntity updateEntity = new GameUpdateEntity();
|
||
updateEntity.setId(gameEntity.getId());
|
||
updateEntity.setName(gameEntity.getName());
|
||
updateEntity.setIcon(gameEntity.getIcon());
|
||
updateEntity.setRawIcon(gameEntity.getRawIcon());
|
||
updateEntity.setIconSubscript(gameEntity.getIconSubscript());
|
||
updateEntity.setIconFloat(gameEntity.getIconFloat());
|
||
updateEntity.setPackageName(apkEntity.getPackageName());
|
||
updateEntity.setSize(apkEntity.getSize());
|
||
updateEntity.setVersion(apkEntity.getVersion());
|
||
updateEntity.setGhVersion(apkEntity.getGhVersion());
|
||
updateEntity.setUrl(apkEntity.getUrl());
|
||
updateEntity.setPlatform(apkEntity.getPlatform());
|
||
updateEntity.setEtag(apkEntity.getEtag());
|
||
updateEntity.setBrief(gameEntity.getBrief());
|
||
updateEntity.setTagStyle(gameEntity.getTagStyle());
|
||
updateEntity.setDownload(gameEntity.getDownload());
|
||
updateEntity.setIndexPlugin(gameEntity.getIndexPlugin());
|
||
updateEntity.setPluginDesc(gameEntity.getPluginDesc());
|
||
updateEntity.setFormat(apkEntity.getFormat());
|
||
updateEntity.setSignature(apkEntity.getSignature());
|
||
updateEntity.setCategory(gameEntity.getCategory());
|
||
updateEntity.setCurrentVersion(PackageUtils.getVersionNameByPackageName(apkEntity.getPackageName()));
|
||
if (gameEntity.isLandPageAddressDialog()) {
|
||
updateEntity.setLandPageAddressDialog(gameEntity.getLandPageAddressDialog());
|
||
}
|
||
updateList.add(updateEntity);
|
||
}
|
||
}
|
||
|
||
// 非插件游戏更新
|
||
for (ApkEntity apkEntity : gameEntity.getApk()) {
|
||
// ghVersion 不存在即是非插件游戏
|
||
if (TextUtils.isEmpty(apkEntity.getGhVersion())) {
|
||
int versionCodeFromRequest = apkEntity.getVersionCode();
|
||
int versionCodeFromInstalledApp = getVersionCodeByPackageName(apkEntity.getPackageName());
|
||
String versionFromRequest = apkEntity.getVersion();
|
||
String versionFromInstalledApp = getVersionNameByPackageName(apkEntity.getPackageName());
|
||
|
||
// 是否需要显示更新
|
||
boolean shouldShowUpdate = apkEntity.getForce();
|
||
// 普通游戏根据本地是否有安装来确定是否 qualified,畅玩游戏直接进判断
|
||
boolean isUpdateQualified =
|
||
isVGame || (!TextUtils.isEmpty(versionFromRequest) && !TextUtils.isEmpty(versionFromInstalledApp));
|
||
|
||
if (shouldShowUpdate && isUpdateQualified) {
|
||
|
||
// 根据版本判断是否需要更新
|
||
shouldShowUpdate = new Version(versionFromRequest).isHigherThan(versionFromInstalledApp);
|
||
|
||
// versionName 没法判定的时候尝试使用 versionCode 去判断
|
||
if (!shouldShowUpdate && versionCodeFromRequest != 0) {
|
||
shouldShowUpdate = versionCodeFromRequest > versionCodeFromInstalledApp;
|
||
}
|
||
|
||
// 畅玩游戏根据 md5 是否一致确定是否需要更新
|
||
if (isVGame) {
|
||
VGameEntity vGameEntity = VHelper.getVGameSnapshot(gameEntity.getId(), apkEntity.getPackageName());
|
||
if (vGameEntity != null) {
|
||
String md5FromInstalledVGame = ExtensionsKt.getMetaExtra(vGameEntity.getDownloadEntity(), Constants.APK_MD5);
|
||
String md5FromRequest = apkEntity.getMd5();
|
||
|
||
shouldShowUpdate = md5FromRequest != null && !md5FromRequest.equals(md5FromInstalledVGame);
|
||
} else {
|
||
shouldShowUpdate = false;
|
||
}
|
||
}
|
||
|
||
if (shouldShowUpdate) {
|
||
GameUpdateEntity updateEntity = new GameUpdateEntity();
|
||
updateEntity.setId(gameEntity.getId());
|
||
updateEntity.setName(gameEntity.getName());
|
||
updateEntity.setIcon(gameEntity.getIcon());
|
||
updateEntity.setRawIcon(gameEntity.getRawIcon());
|
||
updateEntity.setIconSubscript(gameEntity.getIconSubscript());
|
||
updateEntity.setIconFloat(gameEntity.getIconFloat());
|
||
updateEntity.setPackageName(apkEntity.getPackageName());
|
||
updateEntity.setSize(apkEntity.getSize());
|
||
updateEntity.setVersion(apkEntity.getVersion());
|
||
updateEntity.setGhVersion(apkEntity.getGhVersion());
|
||
updateEntity.setUrl(apkEntity.getUrl());
|
||
updateEntity.setPlatform(apkEntity.getPlatform());
|
||
updateEntity.setEtag(apkEntity.getEtag());
|
||
updateEntity.setMd5(apkEntity.getMd5());
|
||
updateEntity.setDownloadStatus(gameEntity.getDownloadStatus());
|
||
updateEntity.setBrief(gameEntity.getBrief());
|
||
updateEntity.setTagStyle(gameEntity.getTagStyle());
|
||
updateEntity.setIndexPlugin(gameEntity.getIndexPlugin());
|
||
updateEntity.setPluginDesc(gameEntity.getPluginDesc());
|
||
updateEntity.setFormat(apkEntity.getFormat());
|
||
updateEntity.setSignature(apkEntity.getSignature());
|
||
updateEntity.setCategory(gameEntity.getCategory());
|
||
updateEntity.setVGameUpdate(isVGame);
|
||
updateEntity.setCurrentVersion(PackageUtils.getVersionNameByPackageName(apkEntity.getPackageName()));
|
||
if (gameEntity.isLandPageAddressDialog()) {
|
||
updateEntity.setLandPageAddressDialog(gameEntity.getLandPageAddressDialog());
|
||
}
|
||
updateList.add(updateEntity);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return updateList;
|
||
}
|
||
|
||
/*
|
||
* 获取meta-data
|
||
*/
|
||
public static Object getMetaData(Context context, String packageName, String name) {
|
||
try {
|
||
Bundle metaDate = context.getApplicationContext().getPackageManager().getApplicationInfo(
|
||
packageName, PackageManager.GET_META_DATA).metaData;
|
||
if (metaDate != null) {
|
||
return metaDate.get(name);
|
||
}
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
return null;
|
||
}
|
||
|
||
|
||
/**
|
||
* 获取已安装游戏的光环ID(游戏ID)
|
||
*
|
||
* @param packageName
|
||
* @return
|
||
*/
|
||
@Nullable
|
||
public static Object getGhId(String packageName) {
|
||
return getMetaData(HaloApp.getInstance().getApplication(), packageName, "gh_id");
|
||
}
|
||
|
||
@Nullable
|
||
public static Map<String, String> getSideLoadedInfo() {
|
||
Context context = HaloApp.getInstance().getApplicationContext();
|
||
|
||
String packageName = null;
|
||
try {
|
||
final PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||
final PackageManager packageManager = context.getPackageManager();
|
||
|
||
if (packageInfo != null && packageManager != null) {
|
||
packageName = packageInfo.packageName;
|
||
|
||
// getInstallSourceInfo requires INSTALL_PACKAGES permission which is only given to system
|
||
// apps.
|
||
final String installerPackageName = packageManager.getInstallerPackageName(packageName);
|
||
|
||
final Map<String, String> sideLoadedInfo = new HashMap<>();
|
||
|
||
if (installerPackageName != null) {
|
||
sideLoadedInfo.put("is_side_loaded", "false");
|
||
// could be amazon, google play etc
|
||
sideLoadedInfo.put("installer_store", installerPackageName);
|
||
} else {
|
||
// if it's installed via adb, system apps or untrusted sources
|
||
sideLoadedInfo.put("is_side_loaded", "true");
|
||
}
|
||
|
||
return sideLoadedInfo;
|
||
}
|
||
} catch (Exception e) {
|
||
Utils.log(e.getLocalizedMessage());
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
|
||
/*
|
||
* 判断是否为光环签名
|
||
*/
|
||
public static boolean isSignedByGh(Context context, String packageName) {
|
||
String signature = getApkSignatureByPackageName(context, packageName)[0];
|
||
|
||
// 判断当前已安装应用是否为光环签名。若签名列表为空,直接判断为光环签名
|
||
return signature == null || SignatureRepository.isSignEmpty() || SignatureRepository.isSignMatched(signature);
|
||
}
|
||
|
||
/*
|
||
* 根据包名,获取apk的签名信息
|
||
* String[0] 为系统风格的公钥字符串
|
||
* String[1] 为接口风格的公钥字符串
|
||
* 请自行根据需要取用
|
||
*/
|
||
@NonNull
|
||
public static String[] getApkSignatureByPackageName(Context context, String packageName) {
|
||
try {
|
||
PackageInfo packageInfo = context.getApplicationContext().getPackageManager()
|
||
.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
|
||
Signature[] signatures = packageInfo.signatures;
|
||
|
||
// 使用幸运破解器破解安卓签名认证可能会出现不用签名也能装的情况,这里有可能是空的
|
||
if (signatures[0] != null) {
|
||
return parseSignature(signatures[0].toByteArray());
|
||
} else {
|
||
return new String[]{null, null};
|
||
}
|
||
} catch (NameNotFoundException e) {
|
||
// do nothing
|
||
}
|
||
return new String[]{null, null};
|
||
}
|
||
|
||
/**
|
||
* 要安装的 APK 包与已安装的对应包名的签名公钥是否一致
|
||
*
|
||
* @param context 上下文
|
||
* @param packageName 要获取已安装签名公钥的包名
|
||
* @param apkFilePath 要获取签名公钥的 apk 文件地址
|
||
* @return 签名公钥是否一致
|
||
*/
|
||
public static boolean isInstalledAppAndApkFileShareTheSameSignature(Context context, String packageName, String apkFilePath) {
|
||
try {
|
||
String[] installedPublicKeys = getApkSignatureByPackageName(context, packageName);
|
||
|
||
String installedPublicKey1 = installedPublicKeys[0];
|
||
String installedPublicKey2 = installedPublicKeys[1];
|
||
|
||
String v1SignaturePublicKey = getV1SignatureFromFile(apkFilePath);
|
||
if (!TextUtils.isEmpty(v1SignaturePublicKey)) {
|
||
return v1SignaturePublicKey.equals(installedPublicKey1) || v1SignaturePublicKey.equals(installedPublicKey2);
|
||
}
|
||
String v1SignaturePublicKeyFromSystemApi = getV1SignatureFromFileBySystemApi(apkFilePath);
|
||
if (!TextUtils.isEmpty(v1SignaturePublicKeyFromSystemApi)) {
|
||
return v1SignaturePublicKeyFromSystemApi.equals(installedPublicKey1);
|
||
}
|
||
|
||
return getV2SignatureFromFile(apkFilePath).equals(installedPublicKey1);
|
||
} catch (Throwable e) {
|
||
e.printStackTrace();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 从 APK 文件中获取 V1 签名公钥
|
||
*
|
||
* @param apkFilePath apk 地址
|
||
* @return 公钥字符串
|
||
*/
|
||
private static String getV1SignatureFromFile(String apkFilePath) {
|
||
try (ZipFile apkFile = new ZipFile(apkFilePath)) {
|
||
Enumeration<?> entries = apkFile.entries();
|
||
|
||
while (entries.hasMoreElements()) {
|
||
ZipEntry entry = ((ZipEntry) entries.nextElement());
|
||
String entryName = entry.getName();
|
||
if (entryName.contains("META-INF") && entryName.endsWith(".RSA")) {
|
||
InputStream is = apkFile.getInputStream(entry);
|
||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||
X509Certificate cert = (X509Certificate) cf.generateCertificates(is).toArray()[0];
|
||
return cert.getPublicKey().toString();
|
||
}
|
||
}
|
||
|
||
ApkVerifier verifier = new ApkVerifier.Builder(new File(apkFilePath)).build();
|
||
ApkVerifier.Result result = verifier.retrieveV1SignatureOnly();
|
||
|
||
return result.getV1SchemeSigners().get(0).getCertificate().getPublicKey().toString();
|
||
} catch (Throwable e) {
|
||
return "";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 用系统 API 获取 V1 签名 (最后一招了) (大文件可能会 ANR,但 ANR 一会可用的话比不能用好点!)
|
||
*/
|
||
private static String getV1SignatureFromFileBySystemApi(String apkFilePath) {
|
||
return parseSignature(HaloApp.getInstance().getPackageManager().getPackageArchiveInfo(apkFilePath, PackageManager.GET_SIGNATURES).signatures[0].toByteArray())[0];
|
||
}
|
||
|
||
/**
|
||
* 从 APK 文件中获取 V2 签名公钥
|
||
*
|
||
* @param apkFilePath apk 地址
|
||
* @return 公钥字符串
|
||
*/
|
||
private static String getV2SignatureFromFile(String apkFilePath) {
|
||
try {
|
||
ApkVerifier verifier = new ApkVerifier.Builder(new File(apkFilePath)).build();
|
||
ApkVerifier.Result result = verifier.retrieveV2SignatureOnly();
|
||
|
||
return result.getV2SchemeSigners().get(0).getCertificate().getPublicKey().toString();
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
return "";
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 解析签名
|
||
*/
|
||
private static String[] parseSignature(byte[] signature) {
|
||
String[] ret;
|
||
try {
|
||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(
|
||
new ByteArrayInputStream(signature));
|
||
ret = new String[]{cert.getPublicKey().toString(), cert.getSerialNumber().toString()};
|
||
if (ret[0].startsWith("DSA")) {
|
||
ret[1] = "DSAPublicKey{" + ApkSigningBlockUtilsLite.toHex(cert.getPublicKey().getEncoded()) + "}";
|
||
}
|
||
} catch (CertificateException e) {
|
||
ret = new String[]{null, null};
|
||
e.printStackTrace();
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* 根据 path 获取 apk 信息确定处理方式
|
||
*
|
||
* @return true 为直接唤起系统 PackageInstaller, false 为需要插件化
|
||
*/
|
||
public static boolean isCanLaunchSetup(Context context, String path) {
|
||
String packageName = getPackageNameByPath(context, path);
|
||
|
||
if (TextUtils.isEmpty(packageName)) {
|
||
return true;
|
||
}
|
||
|
||
boolean isContain = PackagesManager.isInstalled(packageName);
|
||
if (!isContain) {
|
||
return true;
|
||
}
|
||
|
||
boolean isInstalled = isInstalled(context, packageName);
|
||
if (!isInstalled) {
|
||
return true;
|
||
}
|
||
|
||
// 判断当前已安装应用是否为光环签名
|
||
if (isSignedByGh(context, packageName)) {
|
||
return true;
|
||
}
|
||
|
||
return isInstalledAppAndApkFileShareTheSameSignature(context, packageName, path);
|
||
}
|
||
|
||
/*
|
||
* 根据包名,获取卸载游戏的意图
|
||
*/
|
||
public static Intent getUninstallIntent(Context context, String path) {
|
||
Intent uninstallIntent = new Intent();
|
||
uninstallIntent.setAction(Intent.ACTION_DELETE);
|
||
uninstallIntent.addCategory(Intent.CATEGORY_DEFAULT);
|
||
String packageName = getPackageNameByPath(context, path);
|
||
uninstallIntent.setData(Uri.parse("package:" + packageName));
|
||
InstallUtils.getInstance().addUninstall(packageName);
|
||
return uninstallIntent;
|
||
}
|
||
|
||
/*
|
||
* 根据路径,获取apk的包名
|
||
*/
|
||
public static String getPackageNameByPath(Context context, String path) {
|
||
|
||
// 部分设备 (已知 vivo 5.1.1) 在调用 packageManager.getPackageArchiveInfo 获取比较大的 APK 文件时会出现 ANR
|
||
// 大于 1G 的 APK 就走另类方法
|
||
if (isDeviceUnableToHandleBigApkFile(path)) {
|
||
// XAPK 不存在 AndroidManifest
|
||
if (path.contains(XapkInstaller.XAPK_EXTENSION_NAME)) {
|
||
return null;
|
||
}
|
||
return getPackageNameByPathAlternative(path);
|
||
}
|
||
|
||
PackageManager packageManager = context.getApplicationContext().getPackageManager();
|
||
try {
|
||
PackageInfo info = packageManager.getPackageArchiveInfo(path, 0);
|
||
if (info != null) {
|
||
ApplicationInfo appInfo = info.applicationInfo;
|
||
return appInfo.packageName;
|
||
}
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 此设备是否不能调用 packageManager.getPackageArchiveInfo 来获取 APK 信息
|
||
* <p>
|
||
* 部分设备 (已知 vivo 5.1.1 及 5.0.1 的设备) 在调用 packageManager.getPackageArchiveInfo 获取比较大的 APK 文件时会出现 ANR
|
||
*/
|
||
public static boolean isDeviceUnableToHandleBigApkFile(String path) {
|
||
if ("vivo".equals(Build.MANUFACTURER)
|
||
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||
File file = new File(path);
|
||
|
||
if (file != null && file.length() > 1024 * 1024 * 1024) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 从 APK 文件里读包名的另类方法
|
||
* 部分设备 (已知 vivo 5.1.1) 在调用 packageManager.getPackageArchiveInfo 获取比较大的 APK 文件时会出现 ANR
|
||
* <p>
|
||
* 令人迷惑的点:
|
||
* 1. 同样的代码,同样的 APK 在 demo 包里调用 packageManager.getPackageArchiveInfo 并不会 ANR
|
||
* 2. 把 packageManager.getPackageArchiveInfo 放在子线程调用一样会出现 ANR
|
||
* 3. demo 里 manifest 中 application 配置和 targetSdk 也改成与光环一样也不会出现 ANR
|
||
* <p>
|
||
* 大概是光环的某个配置触发了系统的 bug ?
|
||
*/
|
||
private static String getPackageNameByPathAlternative(String path) {
|
||
ApkFile apkParser = null;
|
||
try {
|
||
apkParser = new ApkFile(new File(path));
|
||
ApkMeta apkMeta = apkParser.getApkMeta();
|
||
apkParser.close();
|
||
return apkMeta.getPackageName();
|
||
} catch (Throwable e) {
|
||
e.printStackTrace();
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 根据包名,判断是否已安装该游戏
|
||
*
|
||
* 注意:目测只对能启动的app有效(有桌面图标),对一些没有桌面图标的应用无效(参考应用:魅族游戏框架)
|
||
*/
|
||
public static boolean isInstalled(Context context, String packageName) {
|
||
try {
|
||
Intent intent = context.getApplicationContext().getPackageManager().getLaunchIntentForPackage(packageName);
|
||
return intent != null;
|
||
} catch (IllegalArgumentException exception) {
|
||
// 一些设备调用获取 intent 的时候会触发 Parcel.readException !
|
||
exception.printStackTrace();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据包名,判断是否已安装该游戏
|
||
* 不区分是否有桌面图标
|
||
*/
|
||
public static boolean isInstalledFromAllPackage(Context context, String packageName) {
|
||
try {
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||
context.getPackageManager().getPackageInfo(
|
||
packageName,
|
||
PackageManager.PackageInfoFlags.of(0)
|
||
);
|
||
} else {
|
||
context.getPackageManager().getPackageInfo(packageName, 0);
|
||
}
|
||
return true;
|
||
} catch (Exception e) {
|
||
boolean isInstalled = PackageHelper.INSTANCE.getLocalPackageNameSet().contains(packageName);
|
||
|
||
if (isInstalled) {
|
||
// 能进这里说明上面的 try {} 结果有问题,记录一下
|
||
SentryHelper.INSTANCE.onEvent("CHECK_INSTALLED_CONFLICT", "packageName", packageName);
|
||
}
|
||
|
||
return isInstalled;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 获取应用第一次安装的时间
|
||
*/
|
||
public static long getInstalledTime(Context context, String packageName) {
|
||
try {
|
||
if (context == null) return 0;
|
||
PackageInfo packageInfo = context.getApplicationContext().getPackageManager()
|
||
.getPackageInfo(packageName, 0);
|
||
return packageInfo.firstInstallTime;
|
||
} catch (NameNotFoundException e) {
|
||
// do nothing
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 返回光环助手的版本信息
|
||
*/
|
||
public static String getGhVersionName() {
|
||
return BuildConfig.VERSION_NAME;
|
||
}
|
||
|
||
/*
|
||
* 返回光环助手的版本code
|
||
*/
|
||
public static int getGhVersionCode() {
|
||
return BuildConfig.VERSION_CODE;
|
||
}
|
||
|
||
/**
|
||
* 获取光环助手最后安装更新的时间
|
||
*/
|
||
public static long getHaloLastUpdateTime() {
|
||
try {
|
||
return HaloApp.getInstance().getApplication().getPackageManager()
|
||
.getPackageInfo(BuildConfig.APPLICATION_ID, 0).lastUpdateTime;
|
||
} catch (NameNotFoundException e) {
|
||
// do nothing
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 获取apk的 versionName
|
||
*/
|
||
public static String getVersionNameByPackageName(String packageName) {
|
||
try {
|
||
return HaloApp.getInstance().getApplication().getPackageManager().getPackageInfo(packageName,
|
||
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).versionName;
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/*
|
||
* 获取apk的版本 versionCode
|
||
*/
|
||
public static int getVersionCodeByPackageName(String packageName) {
|
||
try {
|
||
return HaloApp.getInstance().getApplication().getPackageManager().getPackageInfo(packageName,
|
||
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).versionCode;
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
|
||
/*
|
||
* 获取应用的 icon
|
||
*/
|
||
public static Drawable getIconByPackageName(Context context, String packageName) {
|
||
try {
|
||
PackageManager packageManager = context.getApplicationContext().getPackageManager();
|
||
return packageManager.getApplicationIcon(packageName);
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public static JSONArray getAppList(Context context) {
|
||
JSONArray jsonArray = new JSONArray();
|
||
try {
|
||
List<String> packageNameList = PackageHelper.INSTANCE.getInstalledPackageNameList(context, 0);
|
||
for (String packageName : packageNameList) {
|
||
JSONObject jsonObject = new JSONObject();
|
||
jsonObject.put("package", packageName);
|
||
jsonArray.put(jsonObject);
|
||
}
|
||
} catch (JSONException e) {
|
||
e.printStackTrace();
|
||
}
|
||
return jsonArray;
|
||
}
|
||
|
||
public static JSONObject getAppBasicInfoByPackageName(String packageName) {
|
||
JSONObject jsonObject = new JSONObject();
|
||
PackageManager pm = HaloApp.getInstance().getApplication().getPackageManager();
|
||
try {
|
||
PackageInfo packageInfo = HaloApp.getInstance().getApplication().getPackageManager().getPackageInfo(packageName,
|
||
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
|
||
if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
|
||
jsonObject.put("name", pm.getApplicationLabel(packageInfo.applicationInfo).toString());
|
||
jsonObject.put("package", packageName);
|
||
jsonObject.put("version", packageInfo.versionName);
|
||
}
|
||
return jsonObject;
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
return jsonObject;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 启动应用
|
||
* 请使用 PackageLauncher.launchApp()
|
||
*/
|
||
@Deprecated
|
||
public static void launchApplicationByPackageName(Context context, String packageName) {
|
||
try {
|
||
Intent intent = context.getApplicationContext().getPackageManager().getLaunchIntentForPackage(packageName);
|
||
if (intent != null) {
|
||
context.startActivity(intent);
|
||
} else {
|
||
Utils.toast(context, "启动失败");
|
||
}
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
Utils.toast(context, "启动失败");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* todo 统一判断
|
||
* <p>
|
||
* 判断游戏包(插件包) 是否可以更新
|
||
*
|
||
* @param apkEntity apkEntity 必须是已安装的游戏
|
||
* @param gameId 游戏id
|
||
* @return true:可以更新 false:不可以更新
|
||
*/
|
||
public static boolean isCanUpdate(ApkEntity apkEntity, String gameId) {
|
||
// gh_version: gh + timestamp
|
||
String gh_version = (String) PackageUtils.getMetaData(
|
||
HaloApp.getInstance().getApplication(),
|
||
apkEntity.getPackageName(),
|
||
"gh_version");
|
||
|
||
// gh_version: game id
|
||
Object gh_id = PackageUtils.getMetaData(
|
||
HaloApp.getInstance().getApplication(),
|
||
apkEntity.getPackageName(),
|
||
"gh_id");
|
||
|
||
if (gh_version != null && apkEntity.getGhVersion() != null && gh_id != null) {
|
||
gh_version = gh_version.substring(2);
|
||
try {
|
||
return Long.parseLong(gh_version) < Long.parseLong(apkEntity.getGhVersion()) && apkEntity
|
||
.getForce() && gh_id.equals(gameId);
|
||
} catch (NumberFormatException exception) {
|
||
// gh_id 可能出錯
|
||
exception.printStackTrace();
|
||
return false;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public static boolean isNonPluginUpdatable(ApkEntity apkEntity, GameEntity gameEntity) {
|
||
// 非插件游戏更新
|
||
// ghVersion 不存在即是非插件游戏
|
||
if (TextUtils.isEmpty(apkEntity.getGhVersion())) {
|
||
String versionFromRequest = apkEntity.getVersion();
|
||
String versionFromInstalledApp = getVersionNameByPackageName(apkEntity.getPackageName());
|
||
|
||
// 是否需要显示更新
|
||
boolean shouldShowUpdate = apkEntity.getForce();
|
||
|
||
if (shouldShowUpdate && !TextUtils.isEmpty(versionFromRequest) && !TextUtils.isEmpty(versionFromInstalledApp)) {
|
||
// 根据版本判断是否需要更新
|
||
shouldShowUpdate = new Version(versionFromRequest).isHigherThan(versionFromInstalledApp);
|
||
|
||
if (shouldShowUpdate) {
|
||
GameUpdateEntity updateEntity = new GameUpdateEntity();
|
||
updateEntity.setId(gameEntity.getId());
|
||
updateEntity.setName(gameEntity.getName());
|
||
updateEntity.setIcon(gameEntity.getIcon());
|
||
updateEntity.setPackageName(apkEntity.getPackageName());
|
||
updateEntity.setSize(apkEntity.getSize());
|
||
updateEntity.setVersion(apkEntity.getVersion());
|
||
updateEntity.setGhVersion(apkEntity.getGhVersion());
|
||
updateEntity.setUrl(apkEntity.getUrl());
|
||
updateEntity.setPlatform(apkEntity.getPlatform());
|
||
updateEntity.setEtag(apkEntity.getEtag());
|
||
updateEntity.setBrief(gameEntity.getBrief());
|
||
updateEntity.setTagStyle(gameEntity.getTagStyle());
|
||
updateEntity.setIndexPlugin(gameEntity.getIndexPlugin());
|
||
updateEntity.setPluginDesc(gameEntity.getPluginDesc());
|
||
updateEntity.setFormat(apkEntity.getFormat());
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* todo 统一判断
|
||
* <p>
|
||
* 判断游戏包是否可以插件化
|
||
*
|
||
* @param apkEntity apkEntity 必须是已安装的游戏
|
||
* @return true:可以插件化 false:不可以插件化
|
||
*/
|
||
public static boolean isCanPluggable(ApkEntity apkEntity) {
|
||
String gh_id = (String) PackageUtils.getMetaData(
|
||
HaloApp.getInstance().getApplication(),
|
||
apkEntity.getPackageName(),
|
||
"gh_id");
|
||
|
||
return PackageUtils.isInstalled(HaloApp.getInstance().getApplication(), apkEntity.getPackageName())
|
||
&& gh_id == null
|
||
&& !TextUtils.isEmpty(apkEntity.getGhVersion())
|
||
&& !PackageUtils.isSignedByGh(HaloApp.getInstance().getApplication(), apkEntity.getPackageName());
|
||
}
|
||
|
||
/**
|
||
* 应用是否在前台运行
|
||
*/
|
||
public static boolean isAppOnForeground(Context context) {
|
||
try {
|
||
ActivityManager activityManager = (ActivityManager) context.getApplicationContext()
|
||
.getSystemService(Context.ACTIVITY_SERVICE);
|
||
if (activityManager == null) return false;
|
||
|
||
List<ActivityManager.RunningAppProcessInfo> appProcesses =
|
||
activityManager.getRunningAppProcesses();
|
||
if (appProcesses == null) return false;
|
||
|
||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||
if (pm == null) return false;
|
||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
|
||
if (!pm.isInteractive()) return false;
|
||
} else {
|
||
if (!pm.isScreenOn()) return false;
|
||
}
|
||
|
||
String packageName = context.getApplicationContext().getPackageName();
|
||
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
|
||
// The name of the process that this object is associated with.
|
||
if (appProcess.processName.equals(packageName)
|
||
&& appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
|
||
return true;
|
||
}
|
||
}
|
||
} catch (NullPointerException e) {
|
||
e.printStackTrace();
|
||
return false;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public static String getWebviewPath(Context context) {
|
||
final PackageInfo webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(context);
|
||
return webViewPackageInfo != null ? webViewPackageInfo.applicationInfo.sourceDir : null;
|
||
}
|
||
|
||
@WorkerThread
|
||
public static List<String> getApkAbiList(String path) {
|
||
File file = new File(path);
|
||
List<String> abiList = new ArrayList<>();
|
||
if (!file.exists()) return abiList;
|
||
String elementName;
|
||
try (ZipFile zipFile = new ZipFile(file)) {
|
||
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||
while (entries.hasMoreElements()) {
|
||
ZipEntry entry = entries.nextElement();
|
||
if (entry.isDirectory()) {
|
||
continue;
|
||
}
|
||
elementName = entry.getName();
|
||
if (elementName.startsWith("lib/armeabi-v7a")
|
||
|| elementName.startsWith("lib/arm64-v8a")
|
||
|| elementName.startsWith("lib/x86")
|
||
|| elementName.startsWith("lib/armeabi")
|
||
|| elementName.startsWith("lib/x86_64")) {
|
||
String abiName = elementName.substring(4, elementName.lastIndexOf("/"));
|
||
if (!abiList.contains(abiName)) {
|
||
abiList.add(abiName);
|
||
}
|
||
}
|
||
}
|
||
return abiList;
|
||
} catch (IOException e) {
|
||
e.printStackTrace();
|
||
}
|
||
return abiList;
|
||
}
|
||
|
||
public static boolean checkWebViewIsAvailable(Context context) {
|
||
List<String> webViewAbiList = HaloApp.getInstance().getWebViewAbiList();
|
||
|
||
if (webViewAbiList != null && !webViewAbiList.isEmpty()) {
|
||
if (webViewAbiList.size() == 2 && webViewAbiList.contains("x86") && webViewAbiList.contains("x86_64")) {
|
||
return true;
|
||
}
|
||
if (webViewAbiList.size() == 1 && (webViewAbiList.contains("x86") || webViewAbiList.contains("x86_64"))) {
|
||
return true;
|
||
}
|
||
if (Build.CPU_ABI.equals("arm64-v8a")) {
|
||
return webViewAbiList.contains(Build.CPU_ABI);
|
||
}
|
||
return true;
|
||
} else {
|
||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||
return true;
|
||
} else {
|
||
return WebViewCompat.getCurrentWebViewPackage(context) != null;
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|