From 6d2e0853ff653dbd96e0aaeb4f3e88145001e3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=90=9B=E9=99=B6?= Date: Mon, 13 Mar 2023 11:01:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E3=80=90=E5=85=89=E7=8E=AF=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E3=80=91OPPO=E6=89=8B=E6=9C=BA=E6=8B=A6=E6=88=AA?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E7=9B=B8=E5=85=B3=E4=BC=98=E5=8C=96=20https:?= =?UTF-8?q?//jira.shanqu.cc/browse/GHZS-1343?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 +- .../java/com/gh/common/constant/Config.java | 12 +- .../com/gh/common/util/NewFlatLogUtils.kt | 22 +++ .../com/gh/common/util/PackageInstaller.kt | 159 ++++++++++-------- .../java/com/gh/common/xapk/XapkInstaller.kt | 2 +- .../gamecenter/entity/NewApiSettingsEntity.kt | 16 +- .../gh/gamecenter/manager/UpdateManager.java | 2 +- .../gamecenter/receiver/InstallReceiver.java | 2 +- .../retrofit/service/ApiService.java | 8 +- .../java/com/gh/gamecenter/vpn/VpnHelper.kt | 108 ++++++++++++ .../src/debug/assets/assistant-android-mock | 2 +- .../java/com/gh/gamecenter/mock/Mocks.kt | 2 +- .../activityresult/ActResultRequest.java | 43 +++++ .../OnActResultEventDispatcherFragment.java | 38 +++++ .../gamecenter/common/utils/DialogHelper.kt | 9 + .../src/main/res/layout/dialog_guide.xml | 56 ++++-- module_common/src/main/res/values/strings.xml | 1 + .../gamecenter/core/provider/IVpnProvider.kt | 11 +- module_vpn/build.gradle | 8 +- .../com/gh/gamecenter/vpn/VpnProviderImpl.kt | 58 +++++-- 20 files changed, 444 insertions(+), 119 deletions(-) create mode 100644 app/src/main/java/com/gh/gamecenter/vpn/VpnHelper.kt create mode 100644 module_common/src/main/java/com/gh/gamecenter/common/activityresult/ActResultRequest.java create mode 100644 module_common/src/main/java/com/gh/gamecenter/common/activityresult/OnActResultEventDispatcherFragment.java diff --git a/app/build.gradle b/app/build.gradle index b5a18d6ee9..9278744e5e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -230,8 +230,7 @@ dependencies { // debugImplementation "com.gu.android:toolargetool:${toolargetool}" // 需要使用调试时才启用 debugImplementation "com.github.nichbar:WhatTheStack:${whatTheStack}" - debugImplementation "io.github.didi.dokit:dokitx:${dokit}" - +// debugImplementation "io.github.didi.dokit:dokitx:${dokit}" implementation "androidx.multidex:multidex:${multiDex}" implementation "androidx.fragment:fragment-ktx:${fragment}" @@ -312,6 +311,7 @@ dependencies { implementation(project(':module_sensors_data')) { exclude group: 'androidx.swiperefreshlayout' } + implementation(project(':module_vpn')) // 默认不接入光能模块,提高编译速度 // debugImplementation(project(':module_energy')) { // exclude group: 'androidx.swiperefreshlayout' diff --git a/app/src/main/java/com/gh/common/constant/Config.java b/app/src/main/java/com/gh/common/constant/Config.java index aaade0c872..17582c7a95 100644 --- a/app/src/main/java/com/gh/common/constant/Config.java +++ b/app/src/main/java/com/gh/common/constant/Config.java @@ -132,6 +132,16 @@ public class Config { return !"off".equals(getSettings().getGameSmooth()); } + /** + * VPN 开关选项是否开启 + */ + public static boolean isVpnOptionEnabled() { + if (mNewApiSettingsEntity == null || mNewApiSettingsEntity.getInstall().getVpnRequired() == null) { + return false; + } + + return mNewApiSettingsEntity.getInstall().getVpnRequired().getShouldShowVpnOption(); + } public static boolean isShowPlugin(String gameId) { SharedPreferences preferences = getPreferences(); @@ -346,7 +356,7 @@ public class Config { getNewSettings(channel); RetrofitManager.getInstance() - .getApi().getSettings(PackageUtils.getGhVersionName(), channel) + .getApi().getSettings(PackageUtils.getGhVersionName(), channel, Build.MANUFACTURER, Build.MODEL, Build.VERSION.SDK_INT) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Response() { diff --git a/app/src/main/java/com/gh/common/util/NewFlatLogUtils.kt b/app/src/main/java/com/gh/common/util/NewFlatLogUtils.kt index ed0ce4c8c2..de3cb4939d 100644 --- a/app/src/main/java/com/gh/common/util/NewFlatLogUtils.kt +++ b/app/src/main/java/com/gh/common/util/NewFlatLogUtils.kt @@ -1465,4 +1465,26 @@ object NewFlatLogUtils { } log(json, "event", false) } + + fun logVpnHintDialogShow(gameId: String, gameName: String) { + val json = json { + KEY_EVENT to "profile_picture_default_dialog_click" + KEY_GAME_ID to gameId + KEY_GAME_NAME to gameName + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + fun logVpnHintDialogClick(gameId: String, gameName: String, button: String) { + val json = json { + KEY_EVENT to "profile_picture_default_dialog_click" + KEY_GAME_ID to gameId + KEY_GAME_NAME to gameName + "button" to button + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/util/PackageInstaller.kt b/app/src/main/java/com/gh/common/util/PackageInstaller.kt index 61590f256d..6f1364bb77 100644 --- a/app/src/main/java/com/gh/common/util/PackageInstaller.kt +++ b/app/src/main/java/com/gh/common/util/PackageInstaller.kt @@ -1,11 +1,11 @@ package com.gh.common.util -import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build +import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.core.content.FileProvider import com.gh.common.dialog.InstallPermissionDialogFragment @@ -13,25 +13,18 @@ import com.gh.common.xapk.XapkInstaller import com.gh.download.server.BrowserInstallHelper import com.gh.gamecenter.BuildConfig import com.gh.gamecenter.common.constant.Constants -import com.gh.gamecenter.common.retrofit.BiResponse import com.gh.gamecenter.common.utils.DialogHelper import com.gh.gamecenter.common.utils.getExtension import com.gh.gamecenter.common.utils.getMetaExtra -import com.gh.gamecenter.common.utils.toRequestBody import com.gh.gamecenter.core.utils.CurrentActivityHolder import com.gh.gamecenter.core.utils.MD5Utils -import com.gh.gamecenter.core.utils.SPUtils import com.gh.gamecenter.core.utils.ToastUtils -import com.gh.gamecenter.retrofit.RetrofitManager +import com.gh.gamecenter.vpn.VpnHelper import com.gh.vspace.VHelper import com.halo.assistant.HaloApp import com.lightgame.download.DownloadEntity import com.lightgame.download.FileUtils import com.lightgame.utils.Utils -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers -import okhttp3.ResponseBody -import retrofit2.HttpException import java.io.File object PackageInstaller { @@ -64,15 +57,17 @@ object PackageInstaller { return } - // TODO 此处可能遇到 activity 是 WXEntryActivity - // TODO 当 activity 全部出栈,但是应用还在下载游戏,下载完会唤不起安装! + // 已知问题 + // 1. 此处可能遇到 activity 是 WXEntryActivity,因为 WXEntryActivity 不是 AppCompatActivity 调不起弹窗 + // 2. 当 activity 全部出栈,但是应用还在下载游戏,下载完会唤不起安装 if (currentActivity is AppCompatActivity && !currentActivity.isFinishing) { InstallPermissionDialogFragment.show(currentActivity, downloadEntity) { isFromPermissionGrantedCallback -> // 取消状态栏下载完成的通知,若存在 downloadEntity.meta[Constants.MARK_ALREADY_TRIGGERED_INSTALLATION] = "YES" DownloadNotificationHelper.addOrUpdateDownloadNotification(downloadEntity) - if (isFromPermissionGrantedCallback && Build.VERSION.SDK_INT >= 31) { + // 安卓12后的设备,需要重启应用来继续解压 XAPK 文件 + if (isFromPermissionGrantedCallback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val pm = context.packageManager val intent = pm.getLaunchIntentForPackage(context.packageName) val mainIntent = Intent.makeRestartActivityTask(intent!!.component) @@ -82,7 +77,7 @@ object PackageInstaller { if (isXapk) { XapkInstaller.install(context, downloadEntity, showUnzipToast) } else { - install(context, downloadEntity.isPlugin, downloadEntity.path) + install(context, downloadEntity.isPlugin, downloadEntity.path, downloadEntity) } } } @@ -94,9 +89,11 @@ object PackageInstaller { * * 除非特殊情况,请勿使用此方法进行应用安装操作 * 除非你已经确定该文件一定是Apk + * + * @param downloadEntity 仅用来记录日志用,非必须可为空 */ @JvmStatic - fun install(context: Context, isPluggin: Boolean = false, pkgPath: String?) { + fun install(context: Context, isPluggin: Boolean = false, pkgPath: String?, downloadEntity: DownloadEntity? = null) { if (pkgPath.isNullOrEmpty()) { ToastUtils.toast("下载文件异常") return @@ -112,20 +109,13 @@ object PackageInstaller { } if (PackageUtils.isCanLaunchSetup(context, pkgPath)) { -// val app = HaloApp.getInstance() + val currentActivity = CurrentActivityHolder.getCurrentActivity() - HaloApp.put(Constants.LAST_INSTALL_GAME, pkgPath) - -// val downloadEntity = -// DownloadManager.getInstance().allDownloadEntity.firstOrNull { -// it.path == pkgPath -// } -// if (downloadEntity != null) { -// showCertificateDialogIfNeededBeforeInstall(app, downloadEntity, pkgPath) -// } else { - val installIntent = getInstallIntent(context, pkgPath) - context.startActivity(installIntent) -// } + if (VpnHelper.shouldUseVpn() && currentActivity is AppCompatActivity) { + turnOnVpnThenInstall(currentActivity, pkgPath, downloadEntity) + } else { + install(context, pkgPath) + } } else { if (isPluggin) { DialogHelper.showPluginDialog(context) { @@ -143,47 +133,13 @@ object PackageInstaller { } } - @SuppressLint("CheckResult") - fun showCertificateDialogIfNeededBeforeInstall(context: Context, downloadEntity: DownloadEntity, pkgPath: String) { - val isOverwrite = LunchType.UPDATE.name == SPUtils.getString(Constants.SP_INSTALL_TYPE) - val hashMap = hashMapOf(Pair("url", downloadEntity.url), Pair("overwrite", isOverwrite)) - RetrofitManager.getInstance().api.postCertificationCheck(hashMap.toRequestBody()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : BiResponse() { - override fun onSuccess(data: ResponseBody) { - val installIntent = getInstallIntent(context, pkgPath) - context.startActivity(installIntent) - } - - override fun onFailure(exception: Exception) { - super.onFailure(exception) - - if (exception is HttpException) { - val resultString = - (exception as HttpException).response().errorBody() - ?.string() - // 未实名 - when { - resultString?.contains("403117") == true -> { - RealNameHelper.showRealNameUncertificatedDialog( - downloadEntity - ) - } - resultString?.contains("403118") == true -> { - RealNameHelper.showRealNameUnqualifiedDialog( - downloadEntity - ) - } - resultString?.contains("400004") == true -> { - ToastUtils.toast("安装服务异常,缺少参数") - } - } - } else { - ToastUtils.toast("安装服务异常,${exception.localizedMessage}请稍候再试") - } - } - }) + /** + * 最终执行安装的方法 + */ + private fun install(context: Context, pkgPath: String) { + HaloApp.put(Constants.LAST_INSTALL_GAME, pkgPath) + val installIntent = getInstallIntent(context, pkgPath) + context.startActivity(installIntent) } /** @@ -271,4 +227,71 @@ object PackageInstaller { fun createDownloadId(gameName: String?): String { return MD5Utils.getContentMD5(gameName + "_" + System.currentTimeMillis()) } + + /** + * 启动 VPN 然后安装应用 (若没有授权会先提醒授权 VPN ,若拒绝授权会回落到直接执行安装) + */ + private fun turnOnVpnThenInstall(currentActivity: AppCompatActivity, + pkgPath: String, + downloadEntity: DownloadEntity?) { + if (VpnHelper.isVpnPermissionGranted(currentActivity) == true) { + VpnHelper.startVpn(currentActivity) { shouldShowVpnError -> + if (shouldShowVpnError) { + ToastUtils.toast("安装防护功能启动失败") + } + install(currentActivity, pkgPath) + } + } else { + val isTheFirstTimeToShowVpnHintDialog = VpnHelper.isTheFistTimeToShowVpnHintDialog() + + downloadEntity?.let { + NewFlatLogUtils.logVpnHintDialogShow(it.gameId, it.name) + } + + // 将 VPN 提示弹窗标记为已读(已读后的下一次显示"不再提醒"按钮) + VpnHelper.setVpnHintDialogShowed() + DialogHelper.showGuideDialog( + context = currentActivity, + title = "建议开启安装防护", + content = "为了帮助您安装官方的光环助手游戏包,并加速安装进程,避免安装游戏时被手机厂商替换,导致重复下载浪费流量及手机存储空间,建议您开启安装防护功能,光环助手将向您申请开启VPN权限", + confirmText = "立即授权开启防护", + cancelText = "不再提醒", + confirmClickCallback = { + VpnHelper.startVpn(currentActivity) { shouldShowVpnError -> + if (shouldShowVpnError) { + ToastUtils.toast("安装防护功能启动失败") + } + install(currentActivity, pkgPath) + } + + downloadEntity?.let { + NewFlatLogUtils.logVpnHintDialogClick(it.gameId, it.name, "立即授权") + } + }, + cancelClickCallback = { + VpnHelper.ignoreVpnHintDialog() + install(currentActivity, pkgPath) + downloadEntity?.let { + NewFlatLogUtils.logVpnHintDialogClick(it.gameId, it.name, "不再提醒") + } + }, + extraConfig = DialogHelper.Config( + showCloseIcon = true, + showAlternativeCancelStyle = !isTheFirstTimeToShowVpnHintDialog + ), + uiModificationCallback = { binding, dialog -> + binding.cancelTv.visibility = View.GONE + binding.closeContainer.setOnClickListener { + install(currentActivity, pkgPath) + dialog.dismiss() + + downloadEntity?.let { + NewFlatLogUtils.logVpnHintDialogClick(it.gameId, it.name, "关闭按钮") + } + } + } + ) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/xapk/XapkInstaller.kt b/app/src/main/java/com/gh/common/xapk/XapkInstaller.kt index 4aeae9e6e3..5db75dd102 100644 --- a/app/src/main/java/com/gh/common/xapk/XapkInstaller.kt +++ b/app/src/main/java/com/gh/common/xapk/XapkInstaller.kt @@ -152,7 +152,7 @@ object XapkInstaller : IXapkUnzipListener { return@execute } - PackageInstaller.install(mContext, downloadEntity.isPlugin, pkgPath) + PackageInstaller.install(mContext, downloadEntity.isPlugin, pkgPath, downloadEntity) downloadEntity.meta[XAPK_UNZIP_PERCENT] = "100.0" downloadEntity.meta[XAPK_UNZIP_STATUS] = XapkUnzipStatus.SUCCESS.name diff --git a/app/src/main/java/com/gh/gamecenter/entity/NewApiSettingsEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/NewApiSettingsEntity.kt index 766b832d0e..f6baef26aa 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/NewApiSettingsEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/NewApiSettingsEntity.kt @@ -11,7 +11,8 @@ class NewApiSettingsEntity( var startAd: StartupAdEntity? = null,//开屏图片广告 var startup: StartupAdEntity? = null,//启动文案广告 @SerializedName("user_interested_game") - var userInterestedGame: Boolean = false //偏好设置状态开关 + var userInterestedGame: Boolean = false, //偏好设置状态开关 + var install: Install, // 安装相关的 ) { /** * @@ -24,4 +25,17 @@ class NewApiSettingsEntity( val setting: Boolean, val install: Boolean ) + + // VPN 配置 + class Install( + @SerializedName("vpn_required") + val vpnRequired: VpnSetting? = null + ) + + class VpnSetting( + @SerializedName("current_device") + val shouldShowVpnOption: Boolean, + @SerializedName("packages") + val vpnMatchedPackagesName: HashSet + ) } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/manager/UpdateManager.java b/app/src/main/java/com/gh/gamecenter/manager/UpdateManager.java index abacc6ba99..97c3d812ab 100644 --- a/app/src/main/java/com/gh/gamecenter/manager/UpdateManager.java +++ b/app/src/main/java/com/gh/gamecenter/manager/UpdateManager.java @@ -393,7 +393,7 @@ public class UpdateManager { // 产品不接受显示下载完成文案从立即更新变为立即安装,所以文案为立即更新时一律不执行安装 if (isUpdateFileDownloaded(md5) && confirmTextView.getText() != "立即更新") { DataLogUtils.uploadUpgradeLog(context, "install"); //上传更新安装数据 - PackageInstaller.install(context, false, path); + PackageInstaller.install(context, false, path, null); } else { MtaHelper.onEvent("软件更新", "下载开始"); diff --git a/app/src/main/java/com/gh/gamecenter/receiver/InstallReceiver.java b/app/src/main/java/com/gh/gamecenter/receiver/InstallReceiver.java index 25b0d49df2..d4ece605e8 100644 --- a/app/src/main/java/com/gh/gamecenter/receiver/InstallReceiver.java +++ b/app/src/main/java/com/gh/gamecenter/receiver/InstallReceiver.java @@ -50,7 +50,7 @@ public class InstallReceiver extends BroadcastReceiver { if (downloadEntity != null) { PackageInstaller.install(context, downloadEntity); } else { - PackageInstaller.install(context, false, path); + PackageInstaller.install(context, false, path, null); } } else { Intent intent2 = new Intent(); diff --git a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java index 05a2544e1c..fd2a42884b 100644 --- a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java +++ b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java @@ -808,7 +808,13 @@ public interface ApiService { @GET("settings") - Observable getSettings(@Query("version") String version, @Query("channel") String channel); + Observable getSettings( + @Query("version") String version, + @Query("channel") String channel, + @Query("manufacture") String manufacture, + @Query("model") String model, + @Query("android_sdk_version") int androidSdkVersion + ); /** * 新的设置接口 diff --git a/app/src/main/java/com/gh/gamecenter/vpn/VpnHelper.kt b/app/src/main/java/com/gh/gamecenter/vpn/VpnHelper.kt new file mode 100644 index 0000000000..2334785634 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/vpn/VpnHelper.kt @@ -0,0 +1,108 @@ +package com.gh.gamecenter.vpn + +import android.content.Context +import android.os.Build +import android.os.ParcelFileDescriptor +import androidx.appcompat.app.AppCompatActivity +import com.alibaba.android.arouter.launcher.ARouter +import com.gh.common.constant.Config +import com.gh.download.PackageObserver +import com.gh.gamecenter.common.constant.RouteConsts +import com.gh.gamecenter.core.provider.IVpnProvider +import com.gh.gamecenter.core.utils.SPUtils +import com.gh.gamecenter.eventbus.EBPackage +import com.lg.download.DownloadStatus +import com.lightgame.utils.Utils +import java.util.* + +object VpnHelper { + + private const val TAG = "VPN" + private const val KEY_IS_FIRST_TIME_TO_SHOW_VPN_HINT_DIALOG = "is_first_time_to_show_vpn_hint_dialog" + private const val KEY_IGNORED_VPN_HINT_DIALOG = "ignored_vpn_hint_dialog" + private const val STOP_VPN_COUNT_DOWN_INTERVAL = 20 * 1000L + + private var mCountDownTimer: Timer? = null + private var mPfd: ParcelFileDescriptor? = null + + private val mVpnProvider by lazy { + ARouter.getInstance().build(RouteConsts.provider.vpn).navigation() as? IVpnProvider + } + + private var mPackageChangedListener: PackageObserver.PackageChangeListener? = null + + /** + * 仅后台配置打开且手机系统大于 5.0 时才启用 VPN 功能 + */ + fun shouldUseVpn() = Config.isVpnOptionEnabled() + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && !SPUtils.getBoolean(KEY_IGNORED_VPN_HINT_DIALOG, false) + && mVpnProvider != null + + fun isTheFistTimeToShowVpnHintDialog() = SPUtils.getBoolean(KEY_IS_FIRST_TIME_TO_SHOW_VPN_HINT_DIALOG, true) + + fun setVpnHintDialogShowed() = SPUtils.setBoolean(KEY_IS_FIRST_TIME_TO_SHOW_VPN_HINT_DIALOG, false) + + fun ignoreVpnHintDialog() = SPUtils.setBoolean(KEY_IGNORED_VPN_HINT_DIALOG, true) + + fun isVpnPermissionGranted(activity: AppCompatActivity) = mVpnProvider?.isVpnPermissionGranted(activity) + + fun startVpn(activity: AppCompatActivity, onVpnStartedCallback: ((Boolean) -> Unit)) { + val applicationContext = activity.applicationContext + val packagesSet = Config.getNewApiSettingsEntity()?.install?.vpnRequired?.vpnMatchedPackagesName + + if (packagesSet.isNullOrEmpty()) { + Utils.log(TAG, "vpn matched packages is empty, vpn is disabled") + return + } + + startVpn(activity, packagesSet, onVpnStartedCallback) + + mPackageChangedListener = PackageObserver.PackageChangeListener { + if (it.type == EBPackage.TYPE_INSTALLED) { + stopVpnIfNeeded(applicationContext) + } + } + + + if (mCountDownTimer == null) { + mCountDownTimer = Timer() + } + + val task: TimerTask = object : TimerTask() { + override fun run() { + stopVpnIfNeeded(applicationContext) + } + } + + mCountDownTimer?.schedule(task, STOP_VPN_COUNT_DOWN_INTERVAL) + + PackageObserver.registerPackageChangeChangeListener(mPackageChangedListener!!) + } + + private fun startVpn( + activity: AppCompatActivity, + noInternetConnectionPackageNameSet: HashSet, + onVpnStartedCallback: ((Boolean) -> Unit) + ) { + mVpnProvider?.startVpn(activity, noInternetConnectionPackageNameSet, onVpnStartedCallback) { pfd -> + mPfd = pfd + } + } + + fun stopVpnIfNeeded(context: Context) { + if (mPfd != null) { + mVpnProvider?.stopVpn(context, mPfd) + mPfd = null + } + + mCountDownTimer?.cancel() + mCountDownTimer = null + + mPackageChangedListener?.let { + PackageObserver.unregisterPackageChangeChangeListener(it) + } + mPackageChangedListener = null + } + +} \ No newline at end of file diff --git a/module_common/src/debug/assets/assistant-android-mock b/module_common/src/debug/assets/assistant-android-mock index 344a436503..c595414dc4 160000 --- a/module_common/src/debug/assets/assistant-android-mock +++ b/module_common/src/debug/assets/assistant-android-mock @@ -1 +1 @@ -Subproject commit 344a4365031796b36b4511d42f8142830a54767b +Subproject commit c595414dc4889daf171597516e3e88f86873aa62 diff --git a/module_common/src/debug/java/com/gh/gamecenter/mock/Mocks.kt b/module_common/src/debug/java/com/gh/gamecenter/mock/Mocks.kt index 12fbf7d7e7..2434edc003 100644 --- a/module_common/src/debug/java/com/gh/gamecenter/mock/Mocks.kt +++ b/module_common/src/debug/java/com/gh/gamecenter/mock/Mocks.kt @@ -28,7 +28,7 @@ val mocks: Map = mapOf( RequestFilter("/settings") to MockResponse().apply { setResponseCode(200) setBody( - getJsonByAssets("new_simulator.json") + getJsonByAssets("new_settings.json") ) }, diff --git a/module_common/src/main/java/com/gh/gamecenter/common/activityresult/ActResultRequest.java b/module_common/src/main/java/com/gh/gamecenter/common/activityresult/ActResultRequest.java new file mode 100644 index 0000000000..54d43e3d9a --- /dev/null +++ b/module_common/src/main/java/com/gh/gamecenter/common/activityresult/ActResultRequest.java @@ -0,0 +1,43 @@ +package com.gh.gamecenter.common.activityresult; + +import android.content.Intent; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.FragmentManager; + +public class ActResultRequest { + + private final OnActResultEventDispatcherFragment fragment; + + public ActResultRequest(AppCompatActivity activity) { + fragment = getEventDispatchFragment(activity); + } + + private OnActResultEventDispatcherFragment getEventDispatchFragment(AppCompatActivity activity) { + final FragmentManager fragmentManager = activity.getSupportFragmentManager(); + + OnActResultEventDispatcherFragment fragment = findEventDispatchFragment(fragmentManager); + if (fragment == null) { + fragment = new OnActResultEventDispatcherFragment(); + fragmentManager + .beginTransaction() + .add(fragment, OnActResultEventDispatcherFragment.TAG) + .commitAllowingStateLoss(); + fragmentManager.executePendingTransactions(); + } + return fragment; + } + + private OnActResultEventDispatcherFragment findEventDispatchFragment(FragmentManager manager) { + return (OnActResultEventDispatcherFragment) manager.findFragmentByTag(OnActResultEventDispatcherFragment.TAG); + } + + public void startForResult(Intent intent, Callback callback) { + fragment.startForResult(intent, callback); + } + + public interface Callback { + void onActivityResult(int resultCode, Intent data); + } + +} \ No newline at end of file diff --git a/module_common/src/main/java/com/gh/gamecenter/common/activityresult/OnActResultEventDispatcherFragment.java b/module_common/src/main/java/com/gh/gamecenter/common/activityresult/OnActResultEventDispatcherFragment.java new file mode 100644 index 0000000000..7d78df9de2 --- /dev/null +++ b/module_common/src/main/java/com/gh/gamecenter/common/activityresult/OnActResultEventDispatcherFragment.java @@ -0,0 +1,38 @@ +package com.gh.gamecenter.common.activityresult; + +import android.content.Intent; +import android.os.Bundle; +import android.util.SparseArray; + +import androidx.fragment.app.Fragment; + +public class OnActResultEventDispatcherFragment extends Fragment { + + public static final String TAG = "on_act_result_event_dispatcher"; + + private SparseArray mCallbacks = new SparseArray<>(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + public void startForResult(Intent intent, ActResultRequest.Callback callback) { + mCallbacks.put(callback.hashCode(), callback); + startActivityForResult(intent, callback.hashCode()); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + ActResultRequest.Callback callback = mCallbacks.get(requestCode); + mCallbacks.remove(requestCode); + + if (callback != null) { + callback.onActivityResult(resultCode, data); + } + } + +} diff --git a/module_common/src/main/java/com/gh/gamecenter/common/utils/DialogHelper.kt b/module_common/src/main/java/com/gh/gamecenter/common/utils/DialogHelper.kt index e1312bcf3b..ebbcbc1ccf 100644 --- a/module_common/src/main/java/com/gh/gamecenter/common/utils/DialogHelper.kt +++ b/module_common/src/main/java/com/gh/gamecenter/common/utils/DialogHelper.kt @@ -210,6 +210,14 @@ object DialogHelper { binding.closeContainer.setOnClickListener { dialog.dismiss() } } + if (it.showAlternativeCancelStyle) { + binding.cancelTv.visibility = View.GONE + binding.alternativeCancelTv.visibility = View.VISIBLE + binding.alternativeCancelTv.setOnClickListener { + binding.cancelTv.performClick() + } + } + if (it.titleIcon != -1) { binding.titleTv.setDrawableStart(it.titleIcon) } @@ -629,6 +637,7 @@ object DialogHelper { val showCloseIcon: Boolean = false, val centerTitle: Boolean = false, val centerContent: Boolean = false, + val showAlternativeCancelStyle: Boolean = false, // 是否显示另类的取消样式 @DrawableRes val titleIcon: Int = -1 ) diff --git a/module_common/src/main/res/layout/dialog_guide.xml b/module_common/src/main/res/layout/dialog_guide.xml index 9779f949d3..d11b23aa03 100644 --- a/module_common/src/main/res/layout/dialog_guide.xml +++ b/module_common/src/main/res/layout/dialog_guide.xml @@ -37,14 +37,14 @@ android:layout_marginStart="24dp" android:layout_marginTop="24dp" android:layout_marginEnd="24dp" + android:drawablePadding="4dp" android:textColor="@color/text_title" android:textSize="16sp" android:textStyle="bold" - android:drawablePadding="4dp" app:layout_constrainedWidth="true" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="温馨提示" /> @@ -73,11 +73,11 @@ android:textColor="@color/text_subtitleDesc" android:textSize="14sp" android:visibility="gone" + app:layout_constrainedWidth="true" + app:layout_constraintHorizontal_bias="0" app:layout_constraintLeft_toLeftOf="@id/contentTv" app:layout_constraintRight_toRightOf="@id/contentTv" app:layout_constraintTop_toBottomOf="@id/contentTv" - app:layout_constrainedWidth="true" - app:layout_constraintHorizontal_bias="0" tools:text="查看权限应用场景" tools:visibility="visible" /> @@ -88,33 +88,53 @@ android:layout_marginStart="24dp" android:layout_marginTop="24dp" android:layout_marginBottom="24dp" - android:text="@string/cancel" - android:gravity="center" - android:textSize="14sp" - android:textColor="@color/text_subtitle" android:background="@drawable/bg_shape_space2_radius_999" - app:layout_constraintStart_toStartOf="parent" + android:gravity="center" + android:text="@string/cancel" + android:textColor="@color/text_subtitle" + android:textSize="14sp" + app:layout_constraintBottom_toBottomOf="@id/confirmTv" app:layout_constraintEnd_toStartOf="@+id/confirmTv" - app:layout_constraintTop_toBottomOf="@+id/hintTv" - app:layout_constraintBottom_toBottomOf="parent" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@id/confirmTv" /> + app:layout_goneMarginBottom="24dp" /> + + diff --git a/module_common/src/main/res/values/strings.xml b/module_common/src/main/res/values/strings.xml index 478742a87d..bf27986083 100644 --- a/module_common/src/main/res/values/strings.xml +++ b/module_common/src/main/res/values/strings.xml @@ -2,6 +2,7 @@ module_common 取消 确定 + 不再提醒 我知道了 不需要内存缓存 highResImage diff --git a/module_core/src/main/java/com/gh/gamecenter/core/provider/IVpnProvider.kt b/module_core/src/main/java/com/gh/gamecenter/core/provider/IVpnProvider.kt index d801e766c7..0a97b7463b 100644 --- a/module_core/src/main/java/com/gh/gamecenter/core/provider/IVpnProvider.kt +++ b/module_core/src/main/java/com/gh/gamecenter/core/provider/IVpnProvider.kt @@ -1,14 +1,21 @@ package com.gh.gamecenter.core.provider -import android.app.Activity import android.content.Context import android.os.ParcelFileDescriptor +import androidx.appcompat.app.AppCompatActivity import com.alibaba.android.arouter.facade.template.IProvider import java.util.HashSet interface IVpnProvider : IProvider { - fun startVpn(activity: Activity, noInternetConnectionPackageNameSet: HashSet): ParcelFileDescriptor? + fun isVpnPermissionGranted(activity: AppCompatActivity): Boolean + + fun startVpn( + activity: AppCompatActivity, + packageNameSet: HashSet, + vpnStartedCallback: ((Boolean) -> Unit), + pfdCreatedCallback: ((ParcelFileDescriptor?) -> Unit) + ) fun stopVpn(context: Context, pfd: ParcelFileDescriptor?) diff --git a/module_vpn/build.gradle b/module_vpn/build.gradle index ea3267f930..28a2390a81 100644 --- a/module_vpn/build.gradle +++ b/module_vpn/build.gradle @@ -40,10 +40,6 @@ android { } } - buildFeatures { - viewBinding true - } - kapt { arguments { arg("AROUTER_MODULE_NAME", project.name) @@ -67,7 +63,5 @@ dependencies { implementation "androidx.multidex:multidex:${multiDex}" } - implementation(project(path: ":module_common")) { - exclude group: 'androidx.swiperefreshlayout' - } + compileOnly(project(path: ":module_common")) } \ No newline at end of file diff --git a/module_vpn/src/main/java/com/gh/gamecenter/vpn/VpnProviderImpl.kt b/module_vpn/src/main/java/com/gh/gamecenter/vpn/VpnProviderImpl.kt index f2eaffcb62..9c1c4c7abf 100644 --- a/module_vpn/src/main/java/com/gh/gamecenter/vpn/VpnProviderImpl.kt +++ b/module_vpn/src/main/java/com/gh/gamecenter/vpn/VpnProviderImpl.kt @@ -6,37 +6,67 @@ import android.content.Intent import android.os.Build import android.os.ParcelFileDescriptor import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity import com.alibaba.android.arouter.facade.annotation.Route +import com.gh.gamecenter.common.activityresult.ActResultRequest import com.gh.gamecenter.common.constant.RouteConsts import com.gh.gamecenter.core.provider.IVpnProvider -import com.gh.gamecenter.core.utils.ToastUtils import java.util.HashSet @Route(path = RouteConsts.provider.vpn, name = "VPN 暴露服务") class VpnProviderImpl : IVpnProvider { @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + override fun isVpnPermissionGranted(activity: AppCompatActivity): Boolean { + return (HaloVpnService.prepare(activity) == null) + } + + /** + * @param packageNameSet 要应用 VPN 的包名列表 + * @param vpnStartedCallback 启动 VPN 的回调,值为 true 代表遇到错误 + */ + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun startVpn( - activity: Activity, - noInternetConnectionPackageNameSet: HashSet - ): ParcelFileDescriptor? { + activity: AppCompatActivity, + packageNameSet: HashSet, + vpnStartedCallback: ((containsError: Boolean) -> Unit), + pfdCreatedCallback: ((pfd: ParcelFileDescriptor?) -> Unit) + ) { val intent = HaloVpnService.prepare(activity) - var pfd: ParcelFileDescriptor? = null if (intent != null) { try { - activity.startActivityForResult(intent, 0) + ActResultRequest(activity).startForResult(intent) { resultCode, _ -> + if (resultCode == Activity.RESULT_OK) { + pfdCreatedCallback.invoke(startVpn(activity, packageNameSet)) + vpnStartedCallback.invoke(false) + } else if (resultCode == Activity.RESULT_CANCELED) { + vpnStartedCallback.invoke(false) + } + } } catch (e: Exception) { - ToastUtils.toast("请先关闭其它 VPN 服务再使用") + e.printStackTrace() + pfdCreatedCallback.invoke(null) + vpnStartedCallback.invoke(true) } } else { - pfd = HaloVpnService.startVPN(noInternetConnectionPackageNameSet) - val serviceIntent = Intent(activity, HaloVpnService::class.java) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - activity.startForegroundService(serviceIntent) - } else { - activity.startService(serviceIntent) - } + pfdCreatedCallback.invoke(startVpn(activity, packageNameSet)) + vpnStartedCallback.invoke(false) } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private fun startVpn( + activity: AppCompatActivity, + packageNameSet: HashSet, + ): ParcelFileDescriptor? { + val pfd: ParcelFileDescriptor? = HaloVpnService.startVPN(packageNameSet) + val serviceIntent = Intent(activity, HaloVpnService::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + activity.startForegroundService(serviceIntent) + } else { + activity.startService(serviceIntent) + } + return pfd }