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 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.common.utils.PackageFlavorHelper; import com.gh.gamecenter.core.utils.SentryHelper; import com.gh.gamecenter.entity.ApkEntity; import com.gh.gamecenter.entity.GameEntity; import com.gh.gamecenter.entity.GameUpdateEntity; 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.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; 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.ZipException; import java.util.zip.ZipFile; public class PackageUtils { private static long mLastInstalledPackageListTime = 0L; private static List mInstalledPackageList = null; public static final String publicKey = "OpenSSLRSAPublicKey{modulus=a8c4bb5748fec8d5c35db1a7a182d41ba4721a91131a417330af79ef4ddb43f9fa0ff4907b0a613bfe152de0ed8fc1b2e6f94a908aa98a5f7adc1ce814ba7ec919d75d9910bdfd8649b4789da6a90ffb61f0d23ac4f828a78fcd0d6f6120c1c43c1f87f7498a89eb40ca8e32dfc2f9d5c10d612b95192870223674e241e53305abf320d7eed76ded398778576e4db7b17b3bc6a792f13de5e43a6a5fae4276c73e6990ce97f68dff0ec16fc9594f175c8d49cd0d7877340d9de60942ca0efc737e50b6c295dfe0713e4532b4e810e1ea11b702b4a27753e41559cbceb247e7f044ec4e3ab2e8bccd8b9fd71286e63307550bcde86deee95adb8133076269135b,publicExponent=10001}"; private static final String TAG = "PackageUtils"; /* * 判断是否可以更新,只判断gh_version的大小 */ public static List getUpdateData(GameEntity gameEntity) { List 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.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())); updateList.add(updateEntity); } } // 非插件游戏更新 for (ApkEntity apkEntity : gameEntity.getApkNormal()) { // 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 = gameEntity.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 (gameEntity.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(); apkEntity.setPlatform(VHelper.PLATFORM_V); 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.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.setCurrentVersion(PackageUtils.getVersionNameByPackageName(apkEntity.getPackageName())); 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 (NameNotFoundException 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 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 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 publicKey.equals(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) { e.printStackTrace(); } 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 (publicKey.equals(getApkSignatureByPackageName(context, packageName)[0])) { 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(); SentryHelper.INSTANCE.onEvent("GET_PACKAGE_INFO_ERROR", "path", path); } return null; } /** * 此设备是否不能调用 packageManager.getPackageArchiveInfo 来获取 APK 信息 *

* 部分设备 (已知 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 *

* 令人迷惑的点: * 1. 同样的代码,同样的 APK 在 demo 包里调用 packageManager.getPackageArchiveInfo 并不会 ANR * 2. 把 packageManager.getPackageArchiveInfo 放在子线程调用一样会出现 ANR * 3. demo 里 manifest 中 application 配置和 targetSdk 也改成与光环一样也不会出现 ANR *

* 大概是光环的某个配置触发了系统的 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) { ArrayList allPackageName = getAllPackageName(context); return allPackageName.contains(packageName); } /* * 获取应用第一次安装的时间 */ 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) { e.printStackTrace(); } 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) { e.printStackTrace(); } 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 (NameNotFoundException 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 (NameNotFoundException 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 (NameNotFoundException e) { e.printStackTrace(); } return null; } /* * 获取所有已安装的软件的包名、版本(非系统应用) */ public static ArrayList getAllPackageName(Context context) { ArrayList list = new ArrayList<>(); List packageInfos = getInstalledPackages(context, 0); for (PackageInfo packageInfo : packageInfos) { if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { if (!context.getPackageName().equals(packageInfo.packageName)) { list.add(packageInfo.packageName); } } } return list; } public static ArrayList getAllPackageNameIncludeGh(Context context) { ArrayList list = new ArrayList<>(); List packageInfos = getInstalledPackages(context, 0); for (PackageInfo packageInfo : packageInfos) { if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { list.add(packageInfo.packageName); } } return list; } /* * 获取所有已安装的软件的包名(包括系统应用) */ public static ArrayList getAllPackageNameIncludeSystemApps(Context context) { ArrayList list = new ArrayList<>(); List packageInfos = getInstalledPackages(context, 0); for (PackageInfo packageInfo : packageInfos) { if (!context.getPackageName().equals(packageInfo.packageName)) { list.add(packageInfo.packageName); } } return list; } public static JSONArray getAppList(Context context) { JSONArray jsonArray = new JSONArray(); try { PackageManager pm = context.getPackageManager(); List packageInfos = getInstalledPackages(context, 0); for (PackageInfo packageInfo : packageInfos) { if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { JSONObject jsonObject = new JSONObject(); jsonObject.put("name", pm.getApplicationLabel(packageInfo.applicationInfo).toString()); jsonObject.put("package", packageInfo.packageName); jsonObject.put("version", packageInfo.versionName); 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 (JSONException | NameNotFoundException e) { e.printStackTrace(); return jsonObject; } } /* * 启动应用 */ 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, "启动失败"); } } /* * 根据包名,获取软件名称 */ public static String getNameByPackageName(Context context, String packageName) { try { PackageManager pm = context.getApplicationContext().getPackageManager(); ApplicationInfo applicationInfo = pm.getApplicationInfo( packageName, 0); return applicationInfo.loadLabel(pm).toString(); } catch (NameNotFoundException e) { e.printStackTrace(); } return null; } /** * todo 统一判断 *

* 判断游戏包(插件包) 是否可以更新 * * @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 统一判断 *

* 判断游戏包是否可以插件化 * * @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()); } /** * 获取调用者的进程名 * * @param context 调用者的上下文 * @return 进程名 */ public static String obtainProcessName(Context context) { if (PackageFlavorHelper.IS_TEST_FLAVOR) { try { final int pid = android.os.Process.myPid(); ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List listTaskInfo = am.getRunningAppProcesses(); if (listTaskInfo != null && !listTaskInfo.isEmpty()) { for (ActivityManager.RunningAppProcessInfo info : listTaskInfo) { if (info != null && info.pid == pid) { return info.processName; } } } } catch (Exception e) { // 遇到异常了让这次调用正常执行 e.printStackTrace(); return BuildConfig.APPLICATION_ID; } } else { return null; } return null; } /** * 应用是否在前台运行 */ public static boolean isAppOnForeground(Context context) { try { ActivityManager activityManager = (ActivityManager) context.getApplicationContext() .getSystemService(Context.ACTIVITY_SERVICE); if (activityManager == null) return false; List 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 void dumpInstalledListCache() { mLastInstalledPackageListTime = 0; } public static List getInstalledPackages(Context context, int flags) { Utils.log(TAG, "即将获取已安装应用列表"); // 简单 debounce 掉过于频繁的调用获取已安装列表调用 if (System.currentTimeMillis() - mLastInstalledPackageListTime < 1000 && mInstalledPackageList != null && mInstalledPackageList.size() > 0) { Utils.log(TAG, "使用了缓存的已安装应用列表"); return new ArrayList<>(mInstalledPackageList); } Utils.log(TAG, "调用系统 API 获取全新的已安装应用列表"); mLastInstalledPackageListTime = System.currentTimeMillis(); mInstalledPackageList = getInstalledPackagesInternal(context, flags); return mInstalledPackageList; } /** * 在5.1系统手机使用PackageManager获取已安装应用容易发生Package manager has died异常 * https://stackoverflow.com/questions/13235793/transactiontoolargeeception-when-trying-tÏo-get-a-list-of-applications-installed/30062632#30062632 */ private static List getInstalledPackagesInternal(Context context, int flags) { final PackageManager pm = context.getPackageManager(); try { return pm.getInstalledPackages(flags); } catch (Exception ignored) { //we don't care why it didn't succeed. We'll do it using an alternative way instead } // use fallback: Process process; List result = new ArrayList<>(); BufferedReader bufferedReader = null; try { process = Runtime.getRuntime().exec("pm list packages"); bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while ((line = bufferedReader.readLine()) != null) { final String packageName = line.substring(line.indexOf(':') + 1); final PackageInfo packageInfo = pm.getPackageInfo(packageName, flags); result.add(packageInfo); } process.waitFor(); } catch (Exception e) { e.printStackTrace(); if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } } finally { if (bufferedReader != null) try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } return result; } public static String getWebviewPath(Context context) { final PackageInfo webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(context); return webViewPackageInfo != null ? webViewPackageInfo.applicationInfo.sourceDir : null; } @WorkerThread public static List getApkAbiList(String path) { File file = new File(path); List abiList = new ArrayList<>(); if (!file.exists()) return abiList; ZipFile zipFile = null; String elementName; try { zipFile = new ZipFile(file); final Enumeration 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 (ZipException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (zipFile != null) { try { zipFile.close(); } catch (IOException e) { e.printStackTrace(); } } } return abiList; } public static boolean checkWebViewIsAvailable(Context context) { List webViewAbiList = HaloApp.getInstance().webViewAbiList; if (webViewAbiList != null) { 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; } } } }