diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 2a5bca8..4af4d13 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,18 +1,15 @@ + - - + android:resource="@xml/gdt_file_path" + tools:replace="android:resource" + /> + + + - + \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/DownloadApkConfirmDialog.java b/android/src/main/kotlin/com/example/union_ad_ssgf/DownloadApkConfirmDialog.java new file mode 100644 index 0000000..6df5e06 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/DownloadApkConfirmDialog.java @@ -0,0 +1,198 @@ +package com.example.union_ad_ssgf; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Configuration; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.webkit.WebResourceError; +import android.webkit.WebResourceRequest; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; + +import com.qq.e.comm.compliance.DownloadConfirmCallBack; + + +public class DownloadApkConfirmDialog extends Dialog implements View.OnClickListener { + private static final String TAG = "ConfirmDialogWebView"; + private Context context; + private int orientation; + private DownloadConfirmCallBack callBack; + private WebView webView; + private ImageView close; + private Button confirm; + + private ViewGroup contentHolder; + private ProgressBar loadingBar; + private Button reloadButton; + + private String url; + private boolean urlLoadError = false; + + private static final String RELOAD_TEXT = "重新加载"; + private static final String LOAD_ERROR_TEXT = "抱歉,应用信息获取失败"; + + public DownloadApkConfirmDialog(Context context, String infoUrl, + DownloadConfirmCallBack callBack) { + super(context, R.style.DownloadConfirmDialogFullScreen);//需要全屏显示,同时显示非窗口蒙版 + this.context = context; + this.callBack = callBack; + this.url = infoUrl; + orientation = context.getResources().getConfiguration().orientation; + requestWindowFeature(Window.FEATURE_NO_TITLE); + setCanceledOnTouchOutside(true); + initView(); + } + + private void initView() { + setContentView(R.layout.download_confirm_dialog); + View root = findViewById(R.id.download_confirm_root); + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + root.setBackgroundResource(R.drawable.download_confirm_background_portrait); + } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + root.setBackgroundResource(R.drawable.download_confirm_background_landscape); + } + close = findViewById(R.id.download_confirm_close); + close.setOnClickListener(this); + reloadButton = findViewById(R.id.download_confirm_reload_button); + reloadButton.setOnClickListener(this); + confirm = findViewById(R.id.download_confirm_confirm); + confirm.setOnClickListener(this); + loadingBar = findViewById(R.id.download_confirm_progress_bar); + contentHolder = findViewById(R.id.download_confirm_content); + createTextView(); + } + + private void createTextView(){ + FrameLayout layout = findViewById(R.id.download_confirm_holder); + webView = new WebView(context); + webView.getSettings().setJavaScriptEnabled(true); + webView.setWebViewClient(new Client()); + layout.addView(webView); + } + @Override + public void show() { + super.show(); + try { + loadUrl(url); + } catch (Exception e) { + Log.e(DownloadApkConfirmDialog.TAG, "load error url:" + url, e); + } + } + + + private void loadUrl(String url) { + if (TextUtils.isEmpty(url)) { + loadingBar.setVisibility(View.GONE); + contentHolder.setVisibility(View.GONE); + reloadButton.setVisibility(View.VISIBLE); + reloadButton.setText(LOAD_ERROR_TEXT); + reloadButton.setEnabled(false); + return; + } + urlLoadError = false; + Log.d(TAG, "download confirm load url:" + url); + webView.loadUrl(url); + } + + public void setInstallTip() { + confirm.setText("立即安装"); + } + + @Override + protected void onStart() { + int height = (int) UIUtils.INSTANCE.getScreenHeight(context); + int width =(int) UIUtils.INSTANCE.getScreenWidth(context); + Window window = getWindow(); + window.getDecorView().setPadding(0, 0, 0, 0); + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; + layoutParams.height = (int) (height * 0.6); + layoutParams.gravity = Gravity.BOTTOM; + layoutParams.windowAnimations = R.style.DownloadConfirmDialogAnimationUp; + } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + layoutParams.width = (int) (width * 0.5); + layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; + layoutParams.gravity = Gravity.RIGHT; + layoutParams.windowAnimations = R.style.DownloadConfirmDialogAnimationRight; + } + //弹窗外区域蒙版50%透明度 + layoutParams.dimAmount = 0.5f; + + //resume后动画会重复,在显示出来后重置无动画 + window.setAttributes(layoutParams); + setOnShowListener(new OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + try { + Window window = getWindow(); + window.setWindowAnimations(0); + } catch (Throwable t) { + } + } + }); + } + + @Override + public void onClick(View v) { + if (v == close) { + if (callBack != null) { + callBack.onCancel(); + } + dismiss(); + } else if (v == confirm) { + if (callBack != null) { + callBack.onConfirm(); + } + dismiss(); + } else if (v == reloadButton) { + loadUrl(url); + } + + } + + @Override + public void cancel() { + super.cancel(); + if (callBack != null) { + callBack.onCancel(); + } + } + + class Client extends WebViewClient { + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + if (!urlLoadError) { + loadingBar.setVisibility(View.GONE); + reloadButton.setVisibility(View.GONE); + contentHolder.setVisibility(View.VISIBLE); + } + } + + @Override + public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { + super.onReceivedError(view, request, error); + Log.d(TAG, "doConfirmWithInfo onReceivedError:" + error + " " + request); + urlLoadError = true; + loadingBar.setVisibility(View.GONE); + contentHolder.setVisibility(View.GONE); + reloadButton.setVisibility(View.VISIBLE); + reloadButton.setText(RELOAD_TEXT); + reloadButton.setEnabled(true); + } + } + + +} diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/DownloadConfirmHelper.java b/android/src/main/kotlin/com/example/union_ad_ssgf/DownloadConfirmHelper.java new file mode 100644 index 0000000..d334d19 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/DownloadConfirmHelper.java @@ -0,0 +1,818 @@ +package com.example.union_ad_ssgf; + +import android.app.Activity; +import android.text.TextUtils; +import android.util.Log; + +import com.qq.e.comm.compliance.ApkDownloadComplianceInterface; +import com.qq.e.comm.compliance.DownloadConfirmCallBack; +import com.qq.e.comm.compliance.DownloadConfirmListener; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +/* + * created by timfeng 2021/2/2 + */ +public class DownloadConfirmHelper { + public static final String TAG = "DownloadConfirmHelper"; + public static boolean USE_CUSTOM_DIALOG = false; + + private static final String JSON_INFO_PARAMETER = "&resType=api"; + + private static final String JSON_RESULT_KEY = "ret"; + private static final String JSON_DATA_KEY = "data"; + + private static final String ICON_URL_KEY = "iconUrl";//App Icon + private static final String APP_NAME_KEY = "appName";//App 名称 + private static final String VERSION_NAME_KEY = "versionName";//版本号 + private static final String AUTHOR_NAME_KEY = "authorName";//开发者 + private static final String PERMISSIONS_KEY = "permissions";//权限列表(具体权限信息,非URL) + private static final String PRIVACY_AGREEMENT_KEY = "privacyAgreement";//隐私政策(URL)这个url需要使用外部浏览器或者webview展示 + private static final String UPDATE_TIME_KEY = "apkPublishTime";//版本更新时间 + private static final String APK_FILE_SIZE_KEY = "fileSize";//apk文件大小,bytes + + private static boolean USE_RAW_PERMISSIONS = false; + private static JSONObject PERMISSIONS_MAP_JSON; + + public static class ApkInfo { + public String iconUrl; + public String appName; + public String versionName; + public String authorName; + public List permissions; + public String privacyAgreementUrl; + public long apkPublishTime; + public long fileSize; + } + + public static final DownloadConfirmListener DOWNLOAD_CONFIRM_LISTENER = + new DownloadConfirmListener() { + + @Override + public void onDownloadConfirm(Activity context, int scenes, String infoUrl, + DownloadConfirmCallBack callBack) { + Log.d(TAG, "scenes:" + scenes + " info url:" + infoUrl); + + //获取对应的json数据并自定义显示 +// DownloadApkConfirmDialog dialog = new DownloadApkConfirmDialog(context, getApkJsonInfoUrl(infoUrl), callBack); + DownloadApkConfirmDialog dialog = new DownloadApkConfirmDialog(context, infoUrl, callBack);//使用webview显示 + if((scenes & ApkDownloadComplianceInterface.INSTALL_BITS) != 0){ + dialog.setInstallTip(); + scenes &= ~ApkDownloadComplianceInterface.INSTALL_BITS; + Log.d(TAG, "real scenes:" + scenes ); + } + dialog.show(); + } + }; + + public static String getApkJsonInfoUrl(String infoUrl) { + return infoUrl + JSON_INFO_PARAMETER; + } + + public static ApkInfo getAppInfoFromJson(String jsonStr) { + ApkInfo result = null; + + if (TextUtils.isEmpty(jsonStr)) { + Log.d(TAG, "请求应用信息返回值为空"); + return null; + } + try { + JSONObject json = new JSONObject(jsonStr); + int retCode = json.optInt(JSON_RESULT_KEY, -1); + if (retCode != 0) { + Log.d(TAG, "请求应用信息返回值错误"); + return null; + } + JSONObject dataJson = json.optJSONObject(JSON_DATA_KEY); + if (dataJson == null) { + Log.d(TAG, "请求应用信息返回值错误" + JSON_DATA_KEY); + return null; + } + + if (dataJson != null) { + result = new ApkInfo(); + result.iconUrl = dataJson.optString(ICON_URL_KEY); + result.appName = dataJson.optString(APP_NAME_KEY); + result.versionName = dataJson.optString(VERSION_NAME_KEY); + result.authorName = dataJson.optString(AUTHOR_NAME_KEY); + //这里获得是原始的权限数组,android.permission.ACCESS_NETWORK_STATE这种 + JSONArray jsonPermissions = dataJson.optJSONArray(PERMISSIONS_KEY); + if (jsonPermissions != null) { + result.permissions = new ArrayList<>(); + for (int i = 0; i < jsonPermissions.length(); i++) { + String rawPermission = jsonPermissions.getString(i); + if (USE_RAW_PERMISSIONS) { + result.permissions.add(jsonPermissions.getString(i)); + continue; + } + //字面权限,不会显示所有,只会显示一部分常用权限 + JSONObject permissionContent = PERMISSIONS_MAP_JSON.optJSONObject(rawPermission); + if (permissionContent != null) { + String permissionTitle = permissionContent.optString("title"); + if (!TextUtils.isEmpty(permissionTitle)) { + result.permissions.add(permissionTitle); + } + } + } + } + result.privacyAgreementUrl = dataJson.optString(PRIVACY_AGREEMENT_KEY); + //后台返回的时间可能是秒也可能是毫秒,这里需要统一下为毫秒 + //2000年1月1日1时0分0秒对应的 秒是946688401 毫秒是 946688401000 + long publicTime = dataJson.optLong(UPDATE_TIME_KEY); + result.apkPublishTime = publicTime > 946688401000L ? publicTime : publicTime * 1000; + result.fileSize = dataJson.optLong(APK_FILE_SIZE_KEY);//单位是字节 + } + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + /** + * 1. 权限对照表作为开发者参考使用 + * 2. level = 1,代表敏感权限,0,代表普通权限,开发者可以按需使用 + * 3. 权限未穷举部分,需要开发者自行翻译 + */ + + public static final String PERMISSIONS_MAP_JSON_STR = "{\n" + + "\"android.permission.ACCESS_CHECKIN_PROPERTIES\": {\n" + + "\"desc\": \"允许应用对登记服务上传的属性拥有读/写权限。普通应用不应使用此权限。\",\n" + + "\"title\": \"访问检入属性\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.ACCESS_COARSE_LOCATION\": {\n" + + "\"desc\": \"允许该应用获取您的大致位置信息,这类位源信息来自于使用网络位置信息源(例如基站和WLAN)" + + "的位置信息服务。您必须在设备上开启这些位置信息服务,应用才能获得位置信息。应用会使用此类服务确定您的大概位置。\",\n" + + "\"title\": \"大致位置(基于网络)\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS\": {\n" + + "\"desc\": \"允许应用程序访问其他的位置信息提供程序命令。此权限使该应用可以干扰GPS或其他位置信息源的运作。\",\n" + + "\"title\": \"获取额外的位置信息提供程序命令。\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.ACCESS_NETWORK_STATE\": {\n" + + "\"desc\": \"允许该应用查看网络连接的相关信息,例如存在和连接的网络。\",\n" + + "\"title\": \"查看网络连接。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.ACCESS_NOTIFICATION_POLICY\": {\n" + + "\"desc\": \"允许访问通知策略的应用程序的标记权限。\",\n" + + "\"title\": \"访问通知策略\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.ACCESS_WIFI_STATE\": {\n" + + "\"desc\": \"允许该应用查看WLAN网络的相关信息。例如是否启用了WLAN以及连接的WLAN设备的名称。\",\n" + + "\"title\": \"查看WLAN连接\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.ACCOUNT_MANAGER\": {\n" + + "\"desc\": \"允许应用使用AccountManager的帐户身份验证程序功能,包括创建帐户以及获取和设置复密码。\",\n" + + "\"title\": \"创建帐户并设置密码\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.ACTIVITY_RECOGNITION\": {\n" + + "\"desc\": \"允许应用程序识别身体活动。\",\n" + + "\"title\": \"识别身体活动\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.ANSWER_PHONE_CALLS\": {\n" + + "\"desc\": \"允许该应用接听来电。\",\n" + + "\"title\": \"接听来电\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.BATTERY_STATS\": {\n" + + "\"desc\": \"允许应用读取当前电量使用情况的基础数据。此权限可让应用了解关于您使用了哪些应用的详细信息。\",\n" + + "\"title\": \"读取电池使用统计信息。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.BIND_CARRIER_SERVICES\": {\n" + + "\"desc\": \"允许应用告知系统哪些小部件可提供哪个应用使用。拥有此权限的应用可向其他应用授予对个人数据的访问权限。普通应用不应使用此权限。\",\n" + + "\"title\": \"选择小部件\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.BIND_CONTROLS\": {\n" + + "\"desc\": \"允许SystemUI请求第三方控件。\",\n" + + "\"title\": \"请求第三方控件\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.BIND_QUICK_SETTINGS_TILE\": {\n" + + "\"desc\": \"允许应用程序绑定到第三方快速设置磁贴。\",\n" + + "\"title\": \"绑定快速设置磁贴\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.BLUETOOTH\": {\n" + + "\"desc\": \"允许该应用查看手机上的蓝牙配置,以及建立和接受与配对设备的连接。\",\n" + + "\"title\": \"与蓝牙设备配对。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.BLUETOOTH_ADMIN\": {\n" + + "\"desc\": \"允许应用配置本地蓝牙手机,以及发现远程设备并进行配对。\",\n" + + "\"title\": \"访问蓝牙设置。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.BLUETOOTH_PRIVILEGED\": {\n" + + "\"desc\": \"允许应用程序与蓝牙设备配对,而无需用户交互。\",\n" + + "\"title\": \"运行应用进行蓝牙配对。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.BODY_SENSORS\": {\n" + + "\"desc\": \"允许应用访问您用于测量身体状况(如心跳速率)的传感器中的数据。\",\n" + + "\"title\": \"人体传感器\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.BROADCAST_PACKAGE_REMOVED\": {\n" + + "\"desc\": \"允许应用程序广播已删除应用程序的通知。\",\n" + + "\"title\": \"发送软件包被移除的广播通知。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.BROADCAST_SMS\": {\n" + + "\"desc\": \"允许应用广播一条有关已收到短信的通知。恶意应用可能借此伪造接到的短信。\",\n" + + "\"title\": \"发送短信收到的广播\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.BROADCAST_STICKY\": {\n" + + "\"desc\": \"允许应用程序广播粘性意图。\",\n" + + "\"title\": \"发送持久广播。\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.BROADCAST_WAP_PUSH\": {\n" + + "\"desc\": \"允许应用程序广播WAP PUSH接收通知。\",\n" + + "\"title\": \"发送WAP-PUSH 收到的广播。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.CALL_COMPANION_APP\": {\n" + + "\"desc\": \"允许应用作为呼叫伴随应用启用。\",\n" + + "\"title\": \"伴随呼叫启动\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.CALL_PHONE\": {\n" + + "\"desc\": \"允许应用程序在不通过拨号界面的情况下发起电话呼叫。\",\n" + + "\"title\": \"直接拨打电话号码\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.CALL_PRIVILEGED\": {\n" + + "\"desc\": \"允许应用程序拨打任何电话号码,包括紧急号码,而无需通过拨号界面来让用户确认正在拨打的电话。\",\n" + + "\"title\": \"直接呼叫任何电话号码\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.CAMERA\": {\n" + + "\"desc\": \"允许该应用使用相机拍摄照片和视频。此权限可让该应用随时使用相机,而无需您的确认。\",\n" + + "\"title\": \"拍摄照片和视频\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.CAPTURE_AUDIO_OUTPUT\": {\n" + + "\"desc\": \"允许该应用捕获和重定向音频输出。\",\n" + + "\"title\": \"捕获音频输出。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.CHANGE_COMPONENT_ENABLED_STATE\": {\n" + + "\"desc\": \"允许应用启用或停用其他应用的组件。\",\n" + + "\"title\": \"启用或停用应用组件\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.CHANGE_CONFIGURATION\": {\n" + + "\"desc\": \"允许应用程序修改当前设备配置。\",\n" + + "\"title\": \"更改系统显示设置。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.CHANGE_NETWORK_STATE\": {\n" + + "\"desc\": \"允许应用程序更改网络连接状态。\",\n" + + "\"title\": \"更改网络连接状态。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.CHANGE_WIFI_MULTICAST_STATE\": {\n" + + "\"desc\": \"允许该应用使用多播地址接收发送到WLAN网络上所有设备的数据包。该操作的耗电量比非多播模式更大。\",\n" + + "\"title\": \"允许接收WLAN多播。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.CHANGE_WIFI_STATE\": {\n" + + "\"desc\": \"允许该应用与 WLAN 接入点建立和断开连接,以及更改WLAN网络的设备配置。\",\n" + + "\"title\": \"连接WLAN网络和断开连接。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.CLEAR_APP_CACHE\": {\n" + + "\"desc\": \"允许应用程序清除设备上所有已安装应用程序的缓存。\",\n" + + "\"title\": \"删除所有应用缓存数据\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.CONTROL_LOCATION_UPDATES\": {\n" + + "\"desc\": \"允许应用启用/停止来自无线装置的位置更新通知。普通应用应该使用此权限。\",\n" + + "\"title\": \"控制位置更新通知。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.DELETE_CACHE_FILES\": {\n" + + "\"desc\": \"允许删除应用程序的缓存文件。\",\n" + + "\"title\": \"删除其他应用程序的缓存文件。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.DELETE_PACKAGES\": {\n" + + "\"desc\": \"允许应用程序删除其他应用。\",\n" + + "\"title\": \"删除应用\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.DIAGNOSTIC\": {\n" + + "\"desc\": \"允许应用读取/写入诊断拥有的所有资源(例如/dev中的文件)。这可能会影响系统的稳定性和安全性。\",\n" + + "\"title\": \"允许应用程序读写诊断资源。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.DISABLE_KEYGUARD\": {\n" + + "\"desc\": \"允许该应用停用键锁以及任何关联的密码安全措施。例如,让手机在接听来电时停用键锁,在通话结束后重新启用键。\",\n" + + "\"title\": \"停用屏幕锁定\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.DUMP\": {\n" + + "\"desc\": \"允许应用程序从系统服务中检索状态转储信息。\",\n" + + "\"title\": \"检索系统内部状态\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.EXPAND_STATUS_BAR\": {\n" + + "\"desc\": \"允许应用程序展开或折叠状态栏。\",\n" + + "\"title\": \"展开/收拢状态栏\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.FACTORY_TEST\": {\n" + + "\"desc\": \"允许应用以制造商测试应用程序的身份运行,以root用户身份运行。\",\n" + + "\"title\": \"在出厂测试模式下运行\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.FOREGROUND_SERVICE\": {\n" + + "\"desc\": \"允许应用程序使用前台服务。\",\n" + + "\"title\": \"使用前台服务\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.GET_ACCOUNTS\": {\n" + + "\"desc\": \"允许访问帐户服务中的帐户列表。\",\n" + + "\"title\": \"查找设备上的账户\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.GET_ACCOUNTS_PRIVILEGED\": {\n" + + "\"desc\": \"允许访问帐户服务中的帐户列表。\",\n" + + "\"title\": \"访问账户列表\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.GET_PACKAGE_SIZE\": {\n" + + "\"desc\": \"允许应用程序查看其他应用使用的空间大小。\",\n" + + "\"title\": \"计算应用存储空间\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.GET_TASKS\": {\n" + + "\"desc\": \"允许应用程序获取当前的信息或最近运行的任务。\",\n" + + "\"title\": \"检索正在运行的应用。\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.INSTALL_LOCATION_PROVIDER\": {\n" + + "\"desc\": \"创建用于测试的模拟位置源或安装新的位置提供程序。\",\n" + + "\"title\": \"允许安装位置信息提供程序\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.INSTALL_PACKAGES\": {\n" + + "\"desc\": \"允许应用程序安装其他应用。\",\n" + + "\"title\": \"直接安装应用\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.INSTANT_APP_FOREGROUND_SERVICE\": {\n" + + "\"desc\": \"允许即时应用创建前台服务。\",\n" + + "\"title\": \"创建前台服务\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.INTERNET\": {\n" + + "\"desc\": \"允许该应用创建网络套接字和使用自定义网络协议。浏览器和其他某些应用提供了向互联网发送数据的途径,因此应用无需该权限即可向互联网发送数据。\",\n" + + "\"title\": \"完全的网络访问权限\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.KILL_BACKGROUND_PROCESSES\": {\n" + + "\"desc\": \"允许应用清理后台应用进程。\",\n" + + "\"title\": \"关闭其他应用\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.LOADER_USAGE_STATS\": {\n" + + "\"desc\": \"允许数据加载器读取程序包的访问日志。\",\n" + + "\"title\": \"允许读取程序包日志\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.MANAGE_DOCUMENTS\": {\n" + + "\"desc\": \"允许应用程序管理档存储空间。\",\n" + + "\"title\": \"管理文档存储空间\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.MANAGE_EXTERNAL_STORAGE\": {\n" + + "\"desc\": \"允许应用程序访问作用域存储中的外部存储。\",\n" + + "\"title\": \"允许程序访问APP通知方式\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.MEDIA_CONTENT_CONTROL\": {\n" + + "\"desc\": \"允许应用控制媒体播放及使用媒体信息(标题、作者..)。\",\n" + + "\"title\": \"控制媒体播放和使用元数据。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.MODIFY_AUDIO_SETTINGS\": {\n" + + "\"desc\": \"允许该应用修改全局音频设置,例如音量和用于输出的扬声器。\",\n" + + "\"title\": \"更改您的音频设置\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.MODIFY_PHONE_STATE\": {\n" + + "\"desc\": \"允许应用控制设备的电话功能。拥有此权限的应用可在不通知您的情况下执行切换网络、开关手机无线装置等此类操。\",\n" + + "\"title\": \"修改手机状态\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.MOUNT_FORMAT_FILESYSTEMS\": {\n" + + "\"desc\": \"允许格式化可移动存储设备。\",\n" + + "\"title\": \"清除USB存储设备内容\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\": {\n" + + "\"desc\": \"允许挂载和卸载可移动存储的文件系统。\",\n" + + "\"title\": \"访问USB存储设备的文件系统\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.NFC\": {\n" + + "\"desc\": \"允许应用与近距离无线通信(NFC)标签、卡和读取器通信。\",\n" + + "\"title\": \"控制近距离通信\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.PACKAGE_USAGE_STATS\": {\n" + + "\"desc\": \"允许应用收集组件使用情况统计信息。\",\n" + + "\"title\": \"更新组件使用情况统计\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.PERSISTENT_ACTIVITY\": {\n" + + "\"desc\": \"允许应用程序在内存中持续保留其自身的某些组件。\",\n" + + "\"title\": \"让应用始终运行\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.PROCESS_OUTGOING_CALLS\": {\n" + + "\"desc\": \"允许应用在拨出电话时查看拨打的电话号码,并选择改为拨打其他号码或完全中止通话。\",\n" + + "\"title\": \"重新设置外拨电话的路径\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.QUERY_ALL_PACKAGES\": {\n" + + "\"desc\": \"允许查询设备上的任何应用程序。\",\n" + + "\"title\": \"查看设备上所有应用\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.READ_CALENDAR\": {\n" + + "\"desc\": \"允许应用读取用户的日历数据。\",\n" + + "\"title\": \"读取日历活动信息\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.READ_CALL_LOG\": {\n" + + "\"desc\": \"允许应用程序读取用户的通话记录。\",\n" + + "\"title\": \"读取通话记录\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.READ_CONTACTS\": {\n" + + "\"desc\": \"允许该应用读取您手机上存储的联系人的相关数据,包括您通过打电话、发送电子邮件或以其他方式与特定个人通信的频率。\",\n" + + "\"title\": \"读取您的通讯录\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.READ_EXTERNAL_STORAGE\": {\n" + + "\"desc\": \"允许应用程序从外部存储读取数据。\",\n" + + "\"title\": \"读取您的USB存储设备中的内容\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.READ_LOGS\": {\n" + + "\"desc\": \"允许应用程序读取系统日志文件。\",\n" + + "\"title\": \"查阅敏感日志数据\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.READ_PHONE_NUMBERS\": {\n" + + "\"desc\": \"允许读取访问设备的电话号码。\",\n" + + "\"title\": \"读取手机电话号码\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.READ_PHONE_STATE\": {\n" + + "\"desc\": \"允许该应用访问设备的电话功能,此权限可以让应用确定本机号码和设备ID、是否正处于通话状态以及拨打的号码。\",\n" + + "\"title\": \"读取手机状态和身份\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.READ_PRECISE_PHONE_STATE\": {\n" + + "\"desc\": \"允许访问精确的电话状态。\",\n" + + "\"title\": \"读取确切的手机状态\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.READ_SMS\": {\n" + + "\"desc\": \"允许该应用读取您手机或SIM卡上存储的短信。\",\n" + + "\"title\": \"读取您的讯息(短信或彩信)\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.READ_SYNC_SETTINGS\": {\n" + + "\"desc\": \"许该应用读取某个账户的同步设置。\",\n" + + "\"title\": \"读取同步设置。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.READ_SYNC_STATS\": {\n" + + "\"desc\": \"允许该应用读取某个账户的同步统计信息,包括同步活动历史记录和同步数据量。\",\n" + + "\"title\": \"读取同步统计信息。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.REBOOT\": {\n" + + "\"desc\": \"允许应用强行重新启动设备。\",\n" + + "\"title\": \"强行重新启动手机。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.RECEIVE_BOOT_COMPLETED\": {\n" + + "\"desc\": \"允许应用在系统完成引导后立即自动启动。\",\n" + + "\"title\": \"开机启动\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.RECEIVE_MMS\": {\n" + + "\"desc\": \"允许应用程序监听彩信消息。\",\n" + + "\"title\": \"接收讯息(彩信)\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.RECEIVE_SMS\": {\n" + + "\"desc\": \"允许应用程序接收短信消息。\",\n" + + "\"title\": \"接收讯息(短信)\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.RECEIVE_WAP_PUSH\": {\n" + + "\"desc\": \"允许应用程序接收WAP推送消息。\",\n" + + "\"title\": \"接收讯息(WAP)\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.RECORD_AUDIO\": {\n" + + "\"desc\": \"允许应用程序录制音频。\",\n" + + "\"title\": \"录音\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.REORDER_TASKS\": {\n" + + "\"desc\": \"允允许该应用将任务移动到前台和后台。\",\n" + + "\"title\": \"对正在运行的应用重新排序\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND\": {\n" + + "\"desc\": \"允许合作应用在后台运行。\",\n" + + "\"title\": \"合作应用后台运行\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND\": {\n" + + "\"desc\": \"允许合作应用在后台使用数据。\",\n" + + "\"title\": \"合作应用后台使用数据\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.REQUEST_DELETE_PACKAGES\": {\n" + + "\"desc\": \"允许应用程序请求卸载其他应用。\",\n" + + "\"title\": \"卸载其他应用\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\": {\n" + + "\"desc\": \"允许用户请求应用忽略电池优化。\",\n" + + "\"title\": \"忽略电池优化\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.REQUEST_INSTALL_PACKAGES\": {\n" + + "\"desc\": \"允许应用程序请求安装软件包。\",\n" + + "\"title\": \"安装软件\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.REQUEST_PASSWORD_COMPLEXITY\": {\n" + + "\"desc\": \"允许应用程序请求屏幕锁定复杂性,并提示用户将屏幕锁定更新到特定的复杂性级别。\",\n" + + "\"title\": \"获取屏幕锁定复杂性\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.RESTART_PACKAGES\": {\n" + + "\"desc\": \"允许应用程序结束其他应用的后台进程。\",\n" + + "\"title\": \"关闭其他应用\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SEND_RESPOND_VIA_MESSAGE\": {\n" + + "\"desc\": \"允许应用程序将请求发送到其他应用程序,以便处理来电的“通过信息回复”事件。\",\n" + + "\"title\": \"发送“通过信息回复”事件\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SEND_SMS\": {\n" + + "\"desc\": \"允许应用程序发送短信消息。\",\n" + + "\"title\": \"发送短信\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.SET_ANIMATION_SCALE\": {\n" + + "\"desc\": \"允许修改全局动画缩放比例。\",\n" + + "\"title\": \"修改全局动画\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SET_DEBUG_APP\": {\n" + + "\"desc\": \"允许该应用对其他应用程序进行调试。\",\n" + + "\"title\": \"启动应用调试\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SET_PREFERRED_APPLICATIONS\": {\n" + + "\"desc\": \"允许程序修改您的首选应用。\",\n" + + "\"title\": \"设置首选应用。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SET_PROCESS_LIMIT\": {\n" + + "\"desc\": \"允许应用程序设置可以运行的应用程序进程的最大数量。\",\n" + + "\"title\": \"限制运行的进程数量。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SET_TIME\": {\n" + + "\"desc\": \"允许应用程序直接设置系统时间。\",\n" + + "\"title\": \"设置时间。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SET_TIME_ZONE\": {\n" + + "\"desc\": \"允许应用程序直接设置系统时区。\",\n" + + "\"title\": \"设置时区\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SET_WALLPAPER\": {\n" + + "\"desc\": \"允许应用程序设置墙纸。\",\n" + + "\"title\": \"设置壁纸\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SET_WALLPAPER_HINTS\": {\n" + + "\"desc\": \" 允许应用程序设置墙纸大小的提示。\",\n" + + "\"title\": \"调整您的壁纸大小\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SIGNAL_PERSISTENT_PROCESSES\": {\n" + + "\"desc\": \"允许应用程序请求将信号发送到所有持久性进程。\",\n" + + "\"title\": \"向应用发送 Linux 信号\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SMS_FINANCIAL_TRANSACTIONS\": {\n" + + "\"desc\": \"允许金融应用读取过滤的短信。\",\n" + + "\"title\": \"读取过滤短信\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.START_VIEW_PERMISSION_USAGE\": {\n" + + "\"desc\": \"允许启动应用程序的权限使用屏幕。\",\n" + + "\"title\": \"使用手机屏幕\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.STATUS_BAR\": {\n" + + "\"desc\": \"允许应用程序打开,关闭或禁用状态栏及其图标。\",\n" + + "\"title\": \"停用或修改状态栏\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.SYSTEM_ALERT_WINDOW\": {\n" + + "\"desc\": \"允许应用显示在所有其他应用的顶部。\",\n" + + "\"title\": \"在其他应用之上显示内容\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.TRANSMIT_IR\": {\n" + + "\"desc\": \"允许使用设备的红外发射器。\",\n" + + "\"title\": \"使用红外线发射器\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.UPDATE_DEVICE_STATS\": {\n" + + "\"desc\": \"允许应用程序更新设备统计信息。\",\n" + + "\"title\": \"修改使用统计信息。\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.USE_BIOMETRIC\": {\n" + + "\"desc\": \"允许应用使用设备支持的生物特征识别方式。\",\n" + + "\"title\": \"允许使用指纹等识别方式\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.USE_FINGERPRINT\": {\n" + + "\"desc\": \"允许应用使用指纹硬件。\",\n" + + "\"title\": \"允许应用使用指纹硬件\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.USE_FULL_SCREEN_INTENT\": {\n" + + "\"desc\": \"允许应用显示全屏通知。\",\n" + + "\"title\": \"允许应用显示全屏通知\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.USE_SIP\": {\n" + + "\"desc\": \"允许应用程序使用SIP服务。\",\n" + + "\"title\": \"拨打/接听SIP电话\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.VIBRATE\": {\n" + + "\"desc\": \"允许使用振动器。\",\n" + + "\"title\": \"控制振动\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.WAKE_LOCK\": {\n" + + "\"desc\": \"允许应用阻止手机进入休眠状态。\",\n" + + "\"title\": \"防止手机休眠\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.WRITE_APN_SETTINGS\": {\n" + + "\"desc\": \"允许应用更改网络设置,并拦截和检查所有网络流量。\",\n" + + "\"title\": \"更改/拦截网络设置和流量\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.WRITE_CALENDAR\": {\n" + + "\"desc\": \"允许应用程序写入、删除、更改用户的日历数据。\",\n" + + "\"title\": \"添加或修改日历活动,并在所有者不知情的情况下向邀请对象发送电子邮件\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.WRITE_CALL_LOG\": {\n" + + "\"desc\": \"允许应用程序添加/修改用户的通话记录。\",\n" + + "\"title\": \"写入通话记录\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.WRITE_CONTACTS\": {\n" + + "\"desc\": \"允许应用程序写入用户的联系人数据。\",\n" + + "\"title\": \"修改您的通讯录\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.WRITE_EXTERNAL_STORAGE\": {\n" + + "\"desc\": \"允许应用程序读取/写入外部存储。\",\n" + + "\"title\": \"修改或删除您的USB存储设备中的内容\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.WRITE_SECURE_SETTINGS\": {\n" + + "\"desc\": \"允许应用读取或写入安全系统设置。\",\n" + + "\"title\": \"修改安全系统设置\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.WRITE_SETTINGS\": {\n" + + "\"desc\": \"允许应用读取或写入系统设置。\",\n" + + "\"title\": \"修改系统设置\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.WRITE_SYNC_SETTINGS\": {\n" + + "\"desc\": \"允许应用程序修改某个账户的同步设置。\",\n" + + "\"title\": \"启用和停用同步\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.WRITE_VOICEMAIL\": {\n" + + "\"desc\": \"允许应用程序修改和删除系统中现有的语音邮件。\",\n" + + "\"title\": \"读取修改语音邮件\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.DOWNLOAD_WITHOUT_NOTIFICATION\": {\n" + + "\"desc\": \"允许该应用通过下载管理器下载文件,而不会向用户显示任何通知。\",\n" + + "\"title\": \"直接下载文件而不显示通知\",\n" + + "\"level\": 1\n" + + "},\n" + + "\"android.permission.ACCESS_ALL_EXTERNAL_STORAGE\": {\n" + + "\"desc\": \"允许该应用访问所有用户的外部存储。\",\n" + + "\"title\": \"访问所有用户的外部存储设备\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.ASEC_ACCESS\": {\n" + + "\"desc\": \"允许该应用获取有关内部存储的信息。\",\n" + + "\"title\": \"获取有关内部存储设备的信息\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.ACCESS_MOCK_LOCATION\": {\n" + + "\"desc\": \"允许模拟位置源进行测试。\",\n" + + "\"title\": \"停用模拟地点来源进行测试\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"android.permission.AUTHENTICATE_ACCOUNTS\": {\n" + + "\"desc\": \"允许创建帐户并设置密码。\",\n" + + "\"title\": \"创建账户并设置密码\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"com.android.alarm.permission.SET_ALARM\": {\n" + + "\"desc\": \"允许应用程序为用户设置闹钟。\",\n" + + "\"title\": \"设置闹钟\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"com.android.voicemail.permission.READ_VOICEMAIL\": {\n" + + "\"desc\": \"允许应用程序读取系统中的语音邮件。\",\n" + + "\"title\": \"读取语言邮件\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"com.android.launcher.permission.INSTALL_SHORTCUT\": {\n" + + "\"desc\": \"允许应用程序在桌面中创建快捷方式。\",\n" + + "\"title\": \"安装快捷方式\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"com.android.launcher.permission.UNINSTALL_SHORTCUT\": {\n" + + "\"desc\": \"允许删除快捷方式。\",\n" + + "\"title\": \"卸载快捷方式\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"com.android.voicemail.permission.ADD_VOICEMAIL\": {\n" + + "\"desc\": \"允许该应用向您的语音信箱中添加邮件。\",\n" + + "\"title\": \"添加语音邮件\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"com.android.email.permission.READ_ATTACHMENT\": {\n" + + "\"desc\": \"允许该应用读取您的电子邮件附件。\",\n" + + "\"title\": \"读取电子邮件附件\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"com.android.browser.permission.WRITE_HISTORY_BOOKMARKS\": {\n" + + "\"desc\": \"允许该应用修改浏览器的历史记录或手机上存储的书签。\",\n" + + "\"title\": \"写入网络书签和历史记录\",\n" + + "\"level\": 0\n" + + "},\n" + + "\"com.android.browser.permission.READ_HISTORY_BOOKMARKS\": {\n" + + "\"desc\": \"允许该应用读取浏览器访问过的所有URL的历史记录,以及浏览器的所有书签的历史记录。\",\n" + + "\"title\": \"读取您的网络书签和历史记录\",\n" + + "\"level\": 0\n" + + "}\n" + + "}"; + + static { + try { + PERMISSIONS_MAP_JSON = new JSONObject(PERMISSIONS_MAP_JSON_STR); + } catch (JSONException e) { + e.printStackTrace(); + } + } +} diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/LogUtil.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/LogUtil.kt new file mode 100644 index 0000000..12ace42 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/LogUtil.kt @@ -0,0 +1,136 @@ +package com.example.union_ad_ssgf; + +import android.text.TextUtils +import android.util.Log + + +/** + * @Description: 打印工具类 + * 可自动把调用位置所在的类名和方法名作为tag,并可设置打印级别Ï + */ +object LogUtil { + // 以下为打印级别,级别从低到高 + const val LOG_LEVEL_VERBOSE = 1 + const val LOG_LEVEL_DEBUG = 2 + const val LOG_LEVEL_INFO = 3 + const val LOG_LEVEL_WARN = 4 + const val LOG_LEVEL_ERROR = 5 + const val LOG_LEVEL_NOLOG = 6 + private var AppName = "" + private var PrintLine = false + private var LogLevel = LOG_LEVEL_VERBOSE + private var isShow = false + + /** + * 可在打印的TAG前添加应用名标识,不设置则不输出 + */ + fun setAppName(appName: String) { + AppName = appName + } + + /** + * 是否输出打印所在的行数,默认不输出 + */ + fun setPrintLine(enable: Boolean) { + PrintLine = enable + } + + /** + * 可在打印的TAG前添加应用名标识,不设置则不输出 + */ + fun setShow(show: Boolean) { + isShow = show + } + + + /** + * 设置打印级别,且只有等于或高于该级别的打印才会输出 + */ + fun setLogLevel(logLevel: Int) { + LogLevel = logLevel + } + + fun v() { + log(LOG_LEVEL_VERBOSE, "") + } + + fun d() { + log(LOG_LEVEL_DEBUG, "") + } + + fun i() { + log(LOG_LEVEL_INFO, "") + } + + fun w() { + log(LOG_LEVEL_WARN, "") + } + + fun e() { + log(LOG_LEVEL_ERROR, "") + } + + fun v(msg: String) { + if (LogLevel <= LOG_LEVEL_VERBOSE) { + log(LOG_LEVEL_VERBOSE, msg) + } + } + + fun d(msg: String) { + if (LogLevel <= LOG_LEVEL_DEBUG) { + log(LOG_LEVEL_DEBUG, msg) + } + } + + fun i(msg: String) { + if (LogLevel <= LOG_LEVEL_INFO) { + log(LOG_LEVEL_INFO, msg) + } + } + + fun w(msg: String) { + if (LogLevel <= LOG_LEVEL_WARN) { + log(LOG_LEVEL_WARN, msg) + } + } + + fun e(msg: String) { + if (LogLevel <= LOG_LEVEL_ERROR) { + log(LOG_LEVEL_ERROR, msg) + } + } + + private fun log(logLevel: Int, msg: String) { + if(!isShow){ + return + } + val caller = Thread.currentThread().stackTrace[4] + var callerClazzName = caller.className + if (callerClazzName.contains(".")) { + callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1) + } + if (callerClazzName.contains("$")) { + callerClazzName = callerClazzName.substring(0, callerClazzName.indexOf("$")) + } + var tag = callerClazzName + if (!TextUtils.isEmpty(AppName)) { + tag = AppName + "_" + tag + } + if (PrintLine) { + tag += "(Line:%d)" + tag = String.format(tag, caller.lineNumber) + } + tag = String.format(tag, callerClazzName) + val message = "---" + caller.methodName + "---" + msg + when (logLevel) { + LOG_LEVEL_VERBOSE -> Log.v(tag, message) + LOG_LEVEL_DEBUG -> Log.d(tag, message) + LOG_LEVEL_INFO -> Log.i(tag, message) + LOG_LEVEL_WARN -> Log.w(tag, message) + LOG_LEVEL_ERROR -> Log.e(tag, message) + LOG_LEVEL_NOLOG -> { + } + } + } +} + diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/PluginDelegate.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/PluginDelegate.kt deleted file mode 100644 index 460abb4..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/PluginDelegate.kt +++ /dev/null @@ -1,183 +0,0 @@ -package com.example.union_ad_ssgf - -import android.annotation.SuppressLint -import android.app.Activity -import android.content.Intent -import android.util.Log -import com.example.union_ad_ssgf.config.UnionADConfig -import com.example.union_ad_ssgf.page.AdSplashActivity -import com.example.union_ad_ssgf.page.RewardVideoPage -import com.qq.e.comm.managers.GDTAdSdk -import com.qq.e.comm.managers.setting.GlobalSetting.setPersonalizedState -import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.EventChannel.EventSink -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel - - -/** - * 插件代理中心 - * - * @param activity 当前 Activity - * @param bind Flutter 插件绑定对象 - */ -class PluginDelegate(var activity: Activity, bind: FlutterPluginBinding) : - MethodChannel.MethodCallHandler, EventChannel.StreamHandler { - private val s = PluginDelegate::class.java.getSimpleName(); - - // 事件通道 - private var eventSink: EventSink? = null - - /** - * 初始化代码块 - */ - init { - _instance = this - } - - companion object { - /** - * 插件代理对象 - */ - @SuppressLint("StaticFieldLeak") - private lateinit var _instance: PluginDelegate - fun getInstance(): PluginDelegate { - return _instance - } - } - - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - val method = call.method - Log.d(s, "MethodChannel onMethodCall method:" + method + " arguments:" + call.arguments) - when (method) { - /** - * 获取当前版本 - */ - "getPlatformVersion" -> { - getPlatformVersion(call, result) - } - /** - * 初始化 - */ - "initAd" -> { - initAd(call, result) - } - /** - * 设置广告个性化 - */ - "setPersonalizedState" -> { - setPersonalizedState(call, result) - } - /** - * 开屏广告 - */ - "showSplashAd" -> { - showSplashAd(call, result) - } - /** - * 激励广告 - */ - "showRewardVideoAd" -> { - showRewardVideoAd(call, result); - } - /** - * 错误桥接 - */ - else -> { - result.notImplemented() - } - } - } - - override fun onListen(arguments: Any, events: EventChannel.EventSink) { - Log.d(s, "EventChannel onListen arguments:$arguments") - eventSink = events - } - - /** - * 取消事件通道监听 - * @param arguments 参数 - */ - override fun onCancel(arguments: Any?) { - Log.d(s, "EventChannel onCancel") - eventSink = null - } - - /** - * 获取当前版本 - * @param call MethodCall - * @param result Result - */ - private fun getPlatformVersion(call: MethodCall, result: MethodChannel.Result) { - result.success(" Union AD Android " + android.os.Build.VERSION.RELEASE); - } - - /** - * 初始化广告 - * @param call MethodCall - * @param result Result - */ - private fun initAd(call: MethodCall, result: MethodChannel.Result) { - val appId = call.argument("appId") - /** - * 该接口不会采集用户信息 - */ - GDTAdSdk.initWithoutStart(activity.applicationContext, appId) - /** - * 调用initWithoutStart后请尽快调用start,否则可能影响广告填充,造成收入下降 - */ - GDTAdSdk.start(object : GDTAdSdk.OnStartListener { - /** - * 推荐开发者在 onStartSuccess 回调后开始拉广告 - */ - override fun onStartSuccess() { - Log.e("gdt onStartSuccess", "加载成功") - } - override fun onStartFailed(e: Exception) { - Log.e("gdt onStartFailed:", e.toString()) - } - }) - result.success(true) - } - - /** - * 设置广告个性化 - * @param call MethodCall - * @param result Result - */ - private fun setPersonalizedState(call: MethodCall, result: MethodChannel.Result) { - val state = call.argument("state")!! - setPersonalizedState(state) - result.success(true); - } - - /** - * 开屏广告 - * @param call MethodCall - * @param result Result - */ - private fun showSplashAd(call: MethodCall, result: MethodChannel.Result) { - val posId: String? = call.argument(UnionADConfig.KEY_POSID) - val logo: String? = call.argument(UnionADConfig.KEY_LOGO) - val fetchDelay: Double = call.argument(UnionADConfig.KEY_FETCH_DELAY)!! - val intent = Intent(activity, AdSplashActivity::class.java) - intent.putExtra(UnionADConfig.KEY_POSID, posId) - intent.putExtra(UnionADConfig.KEY_LOGO, logo) - intent.putExtra(UnionADConfig.KEY_FETCH_DELAY, fetchDelay) - activity.startActivity(intent) - result.success(true) - } - - /** - * 显示激励视频广告 - * @param call MethodCall - * @param result Result - */ - private fun showRewardVideoAd(call: MethodCall, result: MethodChannel.Result) { - val iad = RewardVideoPage() - iad.showAd(activity, 1, call) - result.success(true) - } - -} diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/UIUtils.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/UIUtils.kt new file mode 100644 index 0000000..aa699f4 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/UIUtils.kt @@ -0,0 +1,249 @@ +package com.example.union_ad_ssgf; + +import android.app.Activity +import android.content.Context +import android.os.Build +import android.util.DisplayMetrics +import android.view.View +import android.view.WindowManager +import java.lang.reflect.InvocationTargetException + +object UIUtils { + + fun getScreenWidth(context: Context): Int { + return context.resources.displayMetrics.widthPixels + } + + fun getScreenHeight(context: Context): Int { + return getRealHeight(context) + } + + fun getScreenWidthDp(context: Context): Float { + val scale = context.resources.displayMetrics.density.toFloat() + val width = context.resources.displayMetrics.widthPixels.toFloat() + val num = if (scale <= 0) 1f else scale + return width / num + 0.5f + } + + //全面屏、刘海屏适配 + fun getHeightDp(activity: Activity): Float { + hideBottomUIMenu(activity) + val height: Float + val realHeight = getRealHeight(activity) + height = if (hasNotchScreen(activity)) { + px2dip(activity, realHeight - getStatusBarHeight(activity)) + } else { + px2dip(activity, realHeight.toFloat()) + } + return height + } + + fun hideBottomUIMenu(activity: Activity?) { + if (activity == null) { + return + } + try { + //隐藏虚拟按键,并且全屏 + if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api + val v = activity.window.decorView + v.systemUiVisibility = View.GONE + } else if (Build.VERSION.SDK_INT >= 19) { + //for new api versions. + val decorView = activity.window.decorView + val uiOptions = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE + or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar + // | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar + or View.SYSTEM_UI_FLAG_IMMERSIVE) + decorView.systemUiVisibility = uiOptions + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + //获取屏幕真实高度,不包含下方虚拟导航栏 + fun getRealHeight(context: Context): Int { + val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + val display = windowManager.defaultDisplay + val dm = DisplayMetrics() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + display.getRealMetrics(dm) + } else { + display.getMetrics(dm) + } + return dm.heightPixels + } + + //获取状态栏高度 + fun getStatusBarHeight(context: Context): Float { + var height = 0f + val resourceId = context.applicationContext.resources.getIdentifier( + "status_bar_height", + "dimen", + "android" + ) + if (resourceId > 0) { + height = + context.applicationContext.resources.getDimensionPixelSize(resourceId).toFloat() + } + return height + } + + /** + * 根据手机的分辨率从 px(像素) 的单位 转成为 dp + */ + fun px2dip(context: Context, pxValue: Float): Float { + val scale = context.resources.displayMetrics.density + var num = if (scale <= 0) 1f else scale + return pxValue / num + 0.5f + } + + /** + * 根据手机的分辨率从 dp 的单位 转成为 px(像素) + */ + fun dip2px(context: Context, dpValue: Float): Float { + val scale = context.resources.displayMetrics.density + return dpValue * scale + 0.5f + } + + /** + * 判断是否是刘海屏 + * @return + */ + fun hasNotchScreen(activity: Activity): Boolean { + return if (getInt( + "ro.miui.notch", + activity + ) == 1 || hasNotchAtHuawei(activity) || hasNotchAtOPPO(activity) + || hasNotchAtVivo(activity) || isAndroidPHasNotch(activity) + ) { + true + } else false + } + + /** + * Android P 刘海屏判断 + * @param activity + * @return + */ + fun isAndroidPHasNotch(activity: Activity?): Boolean { + var ret = false + if (Build.VERSION.SDK_INT >= 28) { + try { + val windowInsets = Class.forName("android.view.WindowInsets") + val method = windowInsets.getMethod("getDisplayCutout") + val displayCutout = method.invoke(windowInsets) + if (displayCutout != null) { + ret = true + } + } catch (e: Exception) { + e.printStackTrace() + } + } + return ret + } + + /** + * 小米刘海屏判断. + * @return 0 if it is not notch ; return 1 means notch + * @throws IllegalArgumentException if the key exceeds 32 characters + */ + fun getInt(key: String?, activity: Activity): Int { + var result = 0 + if (isMiui()) { + try { + val classLoader = activity.classLoader + val SystemProperties = classLoader.loadClass("android.os.SystemProperties") + //参数类型 + val paramTypes = arrayOfNulls?>(2) + paramTypes[0] = String::class.java + paramTypes[1] = Int::class.javaPrimitiveType + val getInt = SystemProperties.getMethod("getInt", *paramTypes) + //参数 + val params = arrayOfNulls(2) + params[0] = String + params[1] = Int + result = getInt.invoke(SystemProperties, *params) as Int + } catch (e: ClassNotFoundException) { + e.printStackTrace() + } catch (e: NoSuchMethodException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } catch (e: IllegalArgumentException) { + e.printStackTrace() + } catch (e: InvocationTargetException) { + e.printStackTrace() + } + } + return result + } + + /** + * 华为刘海屏判断 + * @return + */ + fun hasNotchAtHuawei(context: Context): Boolean { + var ret = false + try { + val classLoader = context.classLoader + val HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil") + val get = HwNotchSizeUtil.getMethod("hasNotchInScreen") + ret = get.invoke(HwNotchSizeUtil) as Boolean + } catch (e: ClassNotFoundException) { + } catch (e: NoSuchMethodException) { + } catch (e: Exception) { + } finally { + return ret + } + } + + val VIVO_NOTCH = 0x00000020 //是否有刘海 + + val VIVO_FILLET = 0x00000008 //是否有圆角 + + + /** + * VIVO刘海屏判断 + * @return + */ + fun hasNotchAtVivo(context: Context): Boolean { + var ret = false + try { + val classLoader = context.classLoader + val FtFeature = classLoader.loadClass("android.util.FtFeature") + val method = FtFeature.getMethod("isFeatureSupport", Int::class.javaPrimitiveType) + ret = method.invoke(FtFeature, VIVO_NOTCH) as Boolean + } catch (e: ClassNotFoundException) { + } catch (e: NoSuchMethodException) { + } catch (e: Exception) { + } finally { + return ret + } + } + + /** + * OPPO刘海屏判断 + * @return + */ + fun hasNotchAtOPPO(context: Context): Boolean { + return context.packageManager.hasSystemFeature("com.oppo.feature.screen.heteromorphism") + } + + fun isMiui(): Boolean { + var sIsMiui = false + try { + val clz = Class.forName("miui.os.Build") + if (clz != null) { + sIsMiui = true + return sIsMiui + } + } catch (e: Exception) { + // ignore + } + return sIsMiui + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfConfig.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfConfig.kt new file mode 100644 index 0000000..9cbe529 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfConfig.kt @@ -0,0 +1,17 @@ +package com.example.union_ad_ssgf; + +class UnionAdSsgfConfig { + companion object { + // event事件 + const val adevent = "com.example.union_ad_ssgf/adevent" + + // BannerAdView + const val bannerAdView = "com.example.union_ad_ssgf/BannerAdView" + + // SplashAdView + const val splashAdView = "com.example.union_ad_ssgf/SplashAdView" + + // NativeExpressADView + const val nativeAdView = "com.example.union_ad_ssgf/NativeExpressAdView" + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfEventPlugin.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfEventPlugin.kt new file mode 100644 index 0000000..a6388a9 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfEventPlugin.kt @@ -0,0 +1,38 @@ +package com.example.union_ad_ssgf; + +import android.content.Context +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.EventChannel + +class UnionAdSsgfEventPlugin : FlutterPlugin, EventChannel.StreamHandler { + companion object { + private var eventChannel: EventChannel? = null + + private var eventSink: EventChannel.EventSink? = null + + private var context: Context? = null + + fun sendContent(content: MutableMap) { + eventSink?.success(content); + } + } + + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + eventSink = events + } + + override fun onCancel(arguments: Any?) { + eventSink = null + } + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + eventChannel = EventChannel(binding.binaryMessenger, UnionAdSsgfConfig.adevent) + eventChannel!!.setStreamHandler(this) + context = binding.applicationContext + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + eventChannel = null + eventChannel!!.setStreamHandler(null) + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfPlugin.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfPlugin.kt index a40bd9a..c1bb2b9 100644 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfPlugin.kt +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfPlugin.kt @@ -1,87 +1,117 @@ -package com.example.union_ad_ssgf +package com.example.union_ad_ssgf; - -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.MethodChannel; +import android.app.Activity +import android.content.Context +import android.util.Log +import androidx.annotation.NonNull +import com.example.union_ad_ssgf.interstitialad.InterstitialAd +import com.example.union_ad_ssgf.rewardvideoad.RewardVideoAd +import com.qq.e.comm.DownloadService +import com.qq.e.comm.managers.GDTAdSdk +import com.qq.e.comm.managers.setting.GlobalSetting +import com.qq.e.comm.managers.status.SDKStatus +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import java.lang.Exception /** - * 三商共富 - 三方插件 + * 腾讯优量汇插件 */ -class UnionAdSsgfPlugin : FlutterPlugin, ActivityAware { - /** - * 方法通道: - * - * 用于Flutter与原生Android之间通信的 MethodChannel - * 此本地引用用于向Flutter引擎注册插件,并在Flutter引擎与Activity分离时注销它 - */ - private lateinit var methodChannel: MethodChannel +class UnionAdSsgfPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { - /** - * 事件通道 - * - * 用于Flutter与原生Android之间通信的 EventChannel - * 此本地引用用于向Flutter引擎注册插件,并在Flutter引擎与Activity分离时注销它 - */ - private lateinit var eventChannel: EventChannel + private lateinit var channel: MethodChannel + private var applicationContext: Context? = null + private var mActivity: Activity? = null + private var mFlutterPluginBinding: FlutterPlugin.FlutterPluginBinding? = null - - /** - * 插件代理 - */ - var delegate: PluginDelegate? = null - - - /** - * 插件连接器 - */ - private lateinit var bind: FlutterPlugin.FlutterPluginBinding - - /** - * 初始化方法通道和事件通道 - */ - override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - bind = flutterPluginBinding - - // 注册 - 方法通道 - methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "union_ad_ssgf_method") - - // 注册 - 监听通道 - eventChannel = EventChannel(flutterPluginBinding.getBinaryMessenger(), "union_ad_ssgf_event") - } - - /** - * 注销通道 - */ - override fun onDetachedFromEngine(p0: FlutterPlugin.FlutterPluginBinding) { - // 解除 - 方法通道 - methodChannel.setMethodCallHandler(null) - - // 解除 - 事件通道 - eventChannel.setStreamHandler(null) - } - - /** - * 绑定activity - */ override fun onAttachedToActivity(binding: ActivityPluginBinding) { - delegate = PluginDelegate(binding.activity, bind) - methodChannel.setMethodCallHandler(delegate) - eventChannel.setStreamHandler(delegate) - } - - - override fun onDetachedFromActivityForConfigChanges() { - onDetachedFromActivity() + mActivity = binding.activity + Log.e("UnionAdSsgfPlugin->", "onAttachedToActivity"); + UnionAdSsgfViewPlugin.registerWith(mFlutterPluginBinding!!, mActivity!!) } override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - onAttachedToActivity(binding) + mActivity = binding.activity + Log.e("UnionAdSsgfPlugin->", "onReattachedToActivityForConfigChanges") + } + + override fun onDetachedFromActivityForConfigChanges() { + mActivity = null + Log.e("UnionAdSsgfPlugin->", "onDetachedFromActivityForConfigChanges") } override fun onDetachedFromActivity() { - this.delegate = null + mActivity = null + Log.e("UnionAdSsgfPlugin->", "onDetachedFromActivity") + } + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_union_ad") + channel.setMethodCallHandler(this) + applicationContext = flutterPluginBinding.applicationContext + mFlutterPluginBinding = flutterPluginBinding + UnionAdSsgfEventPlugin().onAttachedToEngine(flutterPluginBinding) + } + + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + //注册初始化 + if (call.method == "register") { + val appId = call.argument("androidId") + val debug = call.argument("debug") + val channelId = call.argument("channelId") + val personalized = call.argument("personalized") + // 日志 + LogUtil.setAppName("flutter_union_ad") + LogUtil.setShow(debug!!) + // 是否开启个性化 + GlobalSetting.setPersonalizedState(personalized!!) + // 设置渠道id + GlobalSetting.setChannel(channelId!!) + GDTAdSdk.initWithoutStart(applicationContext, appId) + GDTAdSdk.start(object : GDTAdSdk.OnStartListener { + override fun onStartSuccess() { + result.success(true) + } + + override fun onStartFailed(p0: Exception?) { + result.success(false) + } + + }) + // 获取sdk版本 + } else if (call.method == "getSDKVersion") { + result.success(SDKStatus.getIntegrationSDKVersion()) + // 预加载激励广告 + } else if (call.method == "loadRewardVideoAd") { + RewardVideoAd.init(applicationContext!!, call.arguments as Map<*, *>) + result.success(true) + // 展示激励广告 + } else if (call.method == "showRewardVideoAd") { + RewardVideoAd.showAd(call.arguments as Map<*, *>) + result.success(true) + // 预加载插屏广告 + } else if (call.method == "loadInterstitialAD") { + InterstitialAd.init(mActivity!!, call.arguments as Map<*, *>) + result.success(true) + // 展示插屏广告 + } else if (call.method == "showInterstitialAD") { + InterstitialAd.showAd(call.arguments as Map<*, *>) + result.success(true) + // 进入下载列表 + } else if (call.method == "enterAPPDownloadListPage") { + DownloadService.enterAPPDownloadListPage(mActivity) + result.success(true) + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) } } diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfViewPlugin.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfViewPlugin.kt new file mode 100644 index 0000000..ec096f0 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/UnionAdSsgfViewPlugin.kt @@ -0,0 +1,27 @@ +package com.example.union_ad_ssgf; + +import android.app.Activity +import com.example.union_ad_ssgf.bannerad.BannerAdViewFactory +import com.example.union_ad_ssgf.expressad.NativeExpressAdViewFactory +import com.example.union_ad_ssgf.splashad.SplashAdViewFactory +import io.flutter.embedding.engine.plugins.FlutterPlugin + +object UnionAdSsgfViewPlugin { + fun registerWith(binding: FlutterPlugin.FlutterPluginBinding, activity: Activity) { + // 注册banner广告 + binding.platformViewRegistry.registerViewFactory( + UnionAdSsgfConfig.bannerAdView, + BannerAdViewFactory(binding.binaryMessenger, activity) + ) + // 注册splash广告 + binding.platformViewRegistry.registerViewFactory( + UnionAdSsgfConfig.splashAdView, + SplashAdViewFactory(binding.binaryMessenger, activity) + ) + // 注册Express广告 + binding.platformViewRegistry.registerViewFactory( + UnionAdSsgfConfig.nativeAdView, + NativeExpressAdViewFactory(binding.binaryMessenger, activity) + ) + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/bannerad/BannerAdView.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/bannerad/BannerAdView.kt new file mode 100644 index 0000000..73a8985 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/bannerad/BannerAdView.kt @@ -0,0 +1,175 @@ +package com.example.union_ad_ssgf.bannerad + +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import com.example.union_ad_ssgf.* +import com.qq.e.ads.banner2.UnifiedBannerADListener +import com.qq.e.ads.banner2.UnifiedBannerView +import com.qq.e.comm.pi.IBidding +import com.qq.e.comm.util.AdError +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.platform.PlatformView + +/** + * @Description: 平台模板Banner广告 + **/ + +internal class BannerAdView( + var activity: Activity, + messenger: BinaryMessenger, + id: Int, + params: Map +) : + PlatformView, UnifiedBannerADListener, MethodChannel.MethodCallHandler { + + private val TAG = "BannerAdView" + + private var mContainer: FrameLayout? = null + + //广告所需参数 + private var codeId: String + private var viewWidth: Float + private var viewHeight: Float + + private var unifiedBannerView: UnifiedBannerView? = null + + private var channel: MethodChannel? + + private var downloadConfirm: Boolean + //是否开启竞价 + private var isBidding: Boolean = params["isBidding"] as Boolean + + + init { + codeId = params["androidId"] as String + var width = params["viewWidth"] as Double + var height = params["viewHeight"] as Double + downloadConfirm = params["downloadConfirm"] as Boolean + viewWidth = width.toFloat() + viewHeight = height.toFloat() + mContainer = FrameLayout(activity) + mContainer?.layoutParams?.width = ViewGroup.LayoutParams.WRAP_CONTENT + mContainer?.layoutParams?.height = ViewGroup.LayoutParams.WRAP_CONTENT + channel = MethodChannel(messenger, UnionAdSsgfConfig.bannerAdView + "_" + id) + channel?.setMethodCallHandler(this) + loadBannerAd() + } + + private fun loadBannerAd() { + unifiedBannerView = UnifiedBannerView(activity, codeId, this) + unifiedBannerView?.loadAD() + } + + override fun getView(): View { + return mContainer!! + } + + + //广告加载失败,error 对象包含了错误码和错误信息 + override fun onNoAD(p0: AdError?) { + LogUtil.e("$TAG Banner广告加载失败 ${p0?.errorCode} ${p0?.errorMsg}") + var map: MutableMap = + mutableMapOf("code" to p0?.errorCode, "message" to p0?.errorMsg) + channel?.invokeMethod("onFail", map) + } + + //广告加载成功回调,表示广告相关的资源已经加载完毕,Ready To Show + override fun onADReceive() { + mContainer?.removeAllViews() + if (unifiedBannerView == null) { + LogUtil.e("$TAG Banner广告加载失败 unifiedBannerView不存在或已销毁") + var map: MutableMap = + mutableMapOf("code" to 0, "message" to "BannerView不存在或已销毁") + channel?.invokeMethod("onFail", map) + return + } + if (downloadConfirm) { + unifiedBannerView?.setDownloadConfirmListener(DownloadConfirmHelper.DOWNLOAD_CONFIRM_LISTENER) + } + //竞价 则返回价格 不直接加载 + if (isBidding) { + channel?.invokeMethod("onECPM", mutableMapOf( + "ecpmLevel" to unifiedBannerView?.ecpmLevel, + "ecpm" to unifiedBannerView?.ecpm + )) + } else { + LogUtil.e("$TAG Banner广告加载成功回调") + mContainer?.addView(unifiedBannerView) + val map: MutableMap = mutableMapOf( + "width" to UIUtils.px2dip(activity, unifiedBannerView?.width!!.toFloat()), + "height" to UIUtils.px2dip(activity, unifiedBannerView?.height!!.toFloat()) + ) + channel?.invokeMethod("onShow", map) + } + } + + //当广告曝光时发起的回调 + override fun onADExposure() { + LogUtil.e("$TAG Banner广告曝光") + channel?.invokeMethod("onExpose", "") + } + + //当广告关闭时调用 + override fun onADClosed() { + LogUtil.e("$TAG Banner广告关闭") + channel?.invokeMethod("onClose", "") + } + + //当广告点击时发起的回调,由于点击去重等原因可能和平台最终的统计数据有差异 + override fun onADClicked() { + LogUtil.e("$TAG Banner广告点击") + channel?.invokeMethod("onClick", "") + } + + //由于广告点击离开 APP 时调用 + override fun onADLeftApplication() { + LogUtil.e("$TAG Banner广告点击离开 APP") + } + + override fun dispose() { + unifiedBannerView?.destroy() + unifiedBannerView = null + } + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + //竞价成功 + "biddingSucceeded" -> { + var arguments = call.arguments as Map<*, *> + val map: MutableMap = mutableMapOf( + //对应值为竞胜出价,类型为Integer + IBidding.EXPECT_COST_PRICE to arguments["expectCostPrice"], + //对应值为最大竞败方出价,类型为Integer + IBidding.HIGHEST_LOSS_PRICE to arguments["highestLossPrice"], + ) + unifiedBannerView?.sendWinNotification(map) + //展示banner + mContainer?.addView(unifiedBannerView) + val map2: MutableMap = mutableMapOf( + "width" to UIUtils.px2dip(activity, unifiedBannerView?.width!!.toFloat()), + "height" to UIUtils.px2dip(activity, unifiedBannerView?.height!!.toFloat()) + ) + channel?.invokeMethod("onShow", map2) + } + //竞价失败 + "biddingFail" -> { + var arguments = call.arguments as Map<*, *> + val map: MutableMap = mutableMapOf( + //值为本次竞胜方出价(单位:分),类型为Integer。选填 + IBidding.WIN_PRICE to arguments["winPrice"], + //值为优量汇广告竞败原因,类型为Integer。必填 + IBidding.LOSS_REASON to arguments["lossReason"], + //值为本次竞胜方渠道ID,类型为Integer。必填。 + IBidding.ADN_ID to arguments["adnId"], + ) + unifiedBannerView?.sendLossNotification(map) + } + + } + } + +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/bannerad/BannerAdViewFactory.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/bannerad/BannerAdViewFactory.kt new file mode 100644 index 0000000..a872302 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/bannerad/BannerAdViewFactory.kt @@ -0,0 +1,17 @@ +package com.example.union_ad_ssgf.bannerad + +import android.app.Activity +import android.content.Context +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory + +internal class BannerAdViewFactory(private val messenger: BinaryMessenger, private val activity: Activity) : PlatformViewFactory( + StandardMessageCodec.INSTANCE) { + + override fun create(context: Context?, viewId: Int, args: Any?): PlatformView { + val params = args as Map + return BannerAdView(activity,messenger, viewId, params) + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/config/UnionADConfig.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/config/UnionADConfig.kt deleted file mode 100644 index 69cd85d..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/config/UnionADConfig.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.example.union_ad_ssgf.config - -import android.annotation.SuppressLint - -class UnionADConfig { - - /*初始化代码块*/ - init { - _instance = this - } - - companion object { - // 插件代理对象 - @SuppressLint("StaticFieldLeak") - private lateinit var _instance: UnionADConfig - - fun getInstance(): UnionADConfig { - return _instance - } - - // Banner View - const val KEY_BANNER_VIEW = "flutter_qq_ads_banner" - - // Feed View - const val KEY_FEED_VIEW = "flutter_qq_ads_feed" - - // 广告参数 - const val KEY_POSID = "posId" - - // 广告参数 - const val KEY_ADID = "adId" - - // logo 参数 - const val KEY_LOGO = "logo" - - // fetchDelay 参数 - const val KEY_FETCH_DELAY = "fetchDelay" - } - -} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdErrorEvent.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdErrorEvent.kt deleted file mode 100644 index ccacf37..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdErrorEvent.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.union_ad_ssgf.event - - -class AdErrorEvent(posId: String?, adId: String?, viewId: Int?, errCode: Int, errMsg: String?) : - AdEvent(posId, adId, viewId, AdEventAction.onAdError) { - // 错误码 - private var errCode = 0 - - // 错误信息 - private var errMsg: String? = null - - // 错误事件实体 - init { - this.errMsg = errMsg - this.errCode = errCode - } - - /** - * 重写 toMap 方法 - * @return 返回错误事件的map - */ - override fun toMap(): HashMap { - val newMap = super.toMap() - newMap["errCode"] = errCode - newMap["errMsg"] = errMsg - return newMap - } -} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdEvent.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdEvent.kt deleted file mode 100644 index 4382717..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdEvent.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.example.union_ad_ssgf.event - -import java.util.HashMap - -open class AdEvent(posId: String?, adId: String?, viewId: Int?, action: String?) { - // 广告位 id - private var posId: String? = null - - // 广告 id - private var adId: String? = null - - private var viewId: Int? = null - - // 操作 - private var action: String? = null - - init { - this.adId = adId - this.posId = posId - this.action = action - this.viewId = viewId - } - - /** - * 转换为 Map 方面传输 - * @return 转换后的 Map 对象 - */ - open fun toMap(): HashMap { - val map = HashMap() - map["adId"] = adId - map["posId"] = posId - map["action"] = action - map["viewId"] = viewId - return map - } -} diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdEventAction.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdEventAction.kt deleted file mode 100644 index 1f92f89..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdEventAction.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.union_ad_ssgf.event - -class AdEventAction { - companion object { - // 广告错误 - val onAdError = "onAdError" - - // 广告加载成功 - val onAdLoaded = "onAdLoaded" - - // 广告填充 - val onAdPresent = "onAdPresent" - - // 广告曝光 - val onAdExposure = "onAdExposure" - - // 广告关闭 - val onAdClosed = "onAdClosed" - - // 广告点击 - val onAdClicked = "onAdClicked" - - // 广告跳过 - val onAdSkip = "onAdSkip" - - // 广告播放或计时完毕 - val onAdComplete = "onAdComplete" - - // 获得广告激励 - val onAdReward = "onAdReward" - } -} diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdEventHandler.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdEventHandler.kt deleted file mode 100644 index 21dccc8..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdEventHandler.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.union_ad_ssgf.event - - -class AdEventHandler { - companion object { - - // 广告事件处理对象 - @Volatile private var _instance: AdEventHandler? = null - - /** - * 获取广告事件处理类 - * @return 广告事件处理对象 - */ - fun getInstance(): AdEventHandler? { - if (_instance == null) { - synchronized(AdEventHandler::class.java) { _instance = AdEventHandler() } - } - return _instance - } - } - - /** - * 添加广告事件 - * @param event 广告事件 - */ - fun sendEvent(event: AdEvent?) { - if (event != null) { -// PluginDelegate.getInstance().addEvent(event.toMap()) - } - } -} diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdRewardEvent.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdRewardEvent.kt deleted file mode 100644 index 62f1515..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/event/AdRewardEvent.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.union_ad_ssgf.event - - -/** - * @param transId 服务端验证唯一id - * @param customData 服务端验证的自定义信息 - * @param userId 服务端验证的用户信息 - */ -class AdRewardEvent( - posId: String?, - adId: String?, - viewId: Int?, - action: String?, - var transId: String, - var customData: String, - var userId: String, -) : AdEvent(posId, adId, viewId, action) { - override fun toMap(): HashMap { - val newMap = super.toMap() - newMap["transId"] = transId - newMap["customData"] = customData - newMap["userId"] = userId - return newMap - } -} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/expressad/NativeExpressAdView.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/expressad/NativeExpressAdView.kt new file mode 100644 index 0000000..a126a57 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/expressad/NativeExpressAdView.kt @@ -0,0 +1,267 @@ +package com.example.union_ad_ssgf.expressad + +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import com.example.union_ad_ssgf.* +import com.qq.e.ads.cfg.VideoOption +import com.qq.e.ads.nativ.ADSize +import com.qq.e.ads.nativ.NativeExpressAD +import com.qq.e.ads.nativ.NativeExpressADView +import com.qq.e.ads.nativ.NativeExpressMediaListener +import com.qq.e.comm.constants.AdPatternType +import com.qq.e.comm.pi.IBidding +import com.qq.e.comm.util.AdError +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.platform.PlatformView + +/** + * @Description: 平台模板NativeExpressAdView广告 + **/ +internal class NativeExpressAdView( + var activity: Activity, + messenger: BinaryMessenger, + id: Int, + params: Map +) : + PlatformView, NativeExpressAD.NativeExpressADListener, NativeExpressMediaListener , + MethodChannel.MethodCallHandler { + + private var mContainer: FrameLayout? = null + + //广告所需参数 + private var codeId: String = params["androidId"] as String + private var viewWidth: Int = params["viewWidth"] as Int + private var viewHeight: Int = params["viewHeight"] as Int + private var downloadConfirm: Boolean = params["downloadConfirm"] as Boolean + + //是否开启竞价 + private var isBidding: Boolean = params["isBidding"] as Boolean + + private var nativeExpressAD: NativeExpressAD? = null + private var nativeExpressAdView: NativeExpressADView? = null + + private var channel: MethodChannel? + + + init { + mContainer = FrameLayout(activity) + mContainer?.layoutParams?.width = ViewGroup.LayoutParams.WRAP_CONTENT + mContainer?.layoutParams?.height = ViewGroup.LayoutParams.WRAP_CONTENT + channel = MethodChannel(messenger, UnionAdSsgfConfig.nativeAdView + "_" + id) + channel?.setMethodCallHandler(this) + loadExpressAd() + } + + private fun loadExpressAd() { + val adSize: ADSize = when { + viewWidth == 0 -> { + ADSize(ADSize.FULL_WIDTH, viewHeight) + } + viewHeight == 0 -> { + ADSize(viewWidth, ADSize.AUTO_HEIGHT) + } + else -> { + ADSize(viewWidth, viewHeight) + } + } + nativeExpressAD = NativeExpressAD(activity, adSize, codeId, this) + nativeExpressAD?.setVideoOption( + VideoOption.Builder() + .setAutoPlayPolicy(VideoOption.AutoPlayPolicy.ALWAYS) + .setAutoPlayMuted(true) + .build() + ) + nativeExpressAD?.loadAD(1) + } + + override fun getView(): View { + return mContainer!! + } + + /**************平台模板广告加载、渲染、点击状态的回调。****************/ + + //无广告填充 + override fun onNoAD(p0: AdError?) { + LogUtil.e("广告加载失败 ${p0?.errorCode} ${p0?.errorMsg}") + val map: MutableMap = + mutableMapOf("code" to p0?.errorCode, "message" to p0?.errorMsg) + channel?.invokeMethod("onFail", map) + } + + //广告数据加载成功,返回了可以用来展示广告的 NativeExpressADView, + // 但是想让广告曝光还需要调用 NativeExpressADView 的 render 方法 + override fun onADLoaded(p0: MutableList?) { + // 释放前一个 NativeExpressADView 的资源 + if (nativeExpressAdView != null) { + nativeExpressAdView?.destroy() + nativeExpressAdView = null + } + if (p0?.size == 0) { + LogUtil.e("未拉取到广告") + val map: MutableMap = mutableMapOf("code" to 0, "message" to "未拉取到广告") + channel?.invokeMethod("onFail", map) + } + LogUtil.e("广告数据加载成功") + nativeExpressAdView = p0!![0] + if (nativeExpressAdView?.boundData?.adPatternType == AdPatternType.NATIVE_VIDEO) { + nativeExpressAdView?.setMediaListener(this) + } + if (downloadConfirm) { + nativeExpressAdView?.setDownloadConfirmListener(DownloadConfirmHelper.DOWNLOAD_CONFIRM_LISTENER) + } + //竞价 则返回价格 不直接加载 + if (isBidding) { + channel?.invokeMethod("onECPM", mutableMapOf( + "ecpmLevel" to nativeExpressAdView?.ecpmLevel, + "ecpm" to nativeExpressAdView?.ecpm + )) + } else { + if (nativeExpressAdView != null) { + if (mContainer?.childCount!! > 0) { + mContainer?.removeAllViews() + } + mContainer?.addView(nativeExpressAdView!!.rootView) + nativeExpressAdView!!.render() + } + } + } + + //NativeExpressADView 渲染广告失败 + override fun onRenderFail(p0: NativeExpressADView?) { + LogUtil.e("渲染广告失败") + val map: MutableMap = mutableMapOf("code" to 0, "message" to "渲染广告失败") + channel?.invokeMethod("onFail", map) + nativeExpressAdView?.destroy() + } + + //NativeExpressADView 渲染广告成功 + override fun onRenderSuccess(p0: NativeExpressADView?) { + val map: MutableMap = mutableMapOf( + "width" to UIUtils.px2dip(activity, p0?.width!!.toFloat()), + "height" to UIUtils.px2dip(activity, p0?.height!!.toFloat()) + ) + channel?.invokeMethod("onShow", map) + } + + //广告曝光 + override fun onADExposure(p0: NativeExpressADView?) { + LogUtil.e("广告曝光") + channel?.invokeMethod("onExpose", "") + } + + //广告点击 + override fun onADClicked(p0: NativeExpressADView?) { + LogUtil.e("广告点击") + channel?.invokeMethod("onClick", "") + } + + //广告被关闭,将不再显示广告,此时广告对象已经释放资源,不可以再次用来展示了 + override fun onADClosed(p0: NativeExpressADView?) { + LogUtil.e("广告被关闭") + channel?.invokeMethod("onClose", "") + p0?.destroy() + } + + //因为广告点击等原因离开当前 app 时调用 + override fun onADLeftApplication(p0: NativeExpressADView?) { + LogUtil.e("因为广告点击等原因离开当前 app") + } + + /*****************平台模板视频广告播放状态回调接口,专用于带有视频素材的广告对象**************/ + + //视频播放 View 初始化完成 + override fun onVideoInit(p0: NativeExpressADView?) { + LogUtil.e("视频播放 View 初始化完成") + } + + //视频下载中 + override fun onVideoLoading(p0: NativeExpressADView?) { + LogUtil.e("视频下载中") + } + + //视频下载完成 + override fun onVideoCached(p0: NativeExpressADView?) { + LogUtil.e("视频下载完成") +// nativeExpressAdView?. + } + + //视频播放器初始化完成,准备好可以播放了,videoDuration 是视频素材的时间长度,单位为 ms + override fun onVideoReady(p0: NativeExpressADView?, p1: Long) { + LogUtil.e("视频播放器初始化完成") +// nativeExpressAdView? + } + + //视频开始播放 + override fun onVideoStart(p0: NativeExpressADView?) { + LogUtil.e("视频开始播放") + } + + //视频暂停 + override fun onVideoPause(p0: NativeExpressADView?) { + LogUtil.e("视频暂停") + } + + //视频播放结束,手动调用 stop 或者自然播放到达最后一帧时都会触发 + override fun onVideoComplete(p0: NativeExpressADView?) { + LogUtil.e("视频播放结束") + } + + //视频播放时出现错误,error 对象包含了错误码和错误信息,错误码的详细内容可以参考文档第5章 + override fun onVideoError(p0: NativeExpressADView?, p1: AdError?) { + LogUtil.e("视频播放时出现错误 ${p1?.errorCode} ${p1?.errorMsg}") + } + + //进入视频落地页 + override fun onVideoPageOpen(p0: NativeExpressADView?) { + LogUtil.e("进入视频落地页") + } + + //退出视频落地页 + override fun onVideoPageClose(p0: NativeExpressADView?) { + LogUtil.e("退出视频落地页") + + } + + override fun dispose() { + nativeExpressAdView?.destroy() + mContainer?.removeAllViews() + } + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method){ + //竞价成功 + "biddingSucceeded" -> { + var arguments = call.arguments as Map<*, *> + val map: MutableMap = mutableMapOf( + //对应值为竞胜出价,类型为Integer + IBidding.EXPECT_COST_PRICE to arguments["expectCostPrice"], + //对应值为最大竞败方出价,类型为Integer + IBidding.HIGHEST_LOSS_PRICE to arguments["highestLossPrice"], + ) + nativeExpressAdView?.sendWinNotification(map) + //展示信息流 + mContainer?.addView(nativeExpressAdView!!.rootView) + nativeExpressAdView!!.render() + } + //竞价失败 + "biddingFail" -> { + var arguments = call.arguments as Map<*, *> + val map: MutableMap = mutableMapOf( + //值为本次竞胜方出价(单位:分),类型为Integer。选填 + IBidding.WIN_PRICE to arguments["winPrice"], + //值为优量汇广告竞败原因,类型为Integer。必填 + IBidding.LOSS_REASON to arguments["lossReason"], + //值为本次竞胜方渠道ID,类型为Integer。必填。 + IBidding.ADN_ID to arguments["adnId"], + ) + nativeExpressAdView?.sendLossNotification(map) + } + + } + } + +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/expressad/NativeExpressAdViewFactory.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/expressad/NativeExpressAdViewFactory.kt new file mode 100644 index 0000000..c0f5290 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/expressad/NativeExpressAdViewFactory.kt @@ -0,0 +1,17 @@ +package com.example.union_ad_ssgf.expressad + +import android.app.Activity +import android.content.Context +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory + +internal class NativeExpressAdViewFactory(private val messenger: BinaryMessenger, private val activity: Activity) : PlatformViewFactory( + StandardMessageCodec.INSTANCE) { + + override fun create(context: Context?, viewId: Int, args: Any?): PlatformView { + val params = args as Map + return NativeExpressAdView(activity,messenger, viewId, params) + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/interstitialad/InterstitialAd.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/interstitialad/InterstitialAd.kt new file mode 100644 index 0000000..b344d5f --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/interstitialad/InterstitialAd.kt @@ -0,0 +1,227 @@ +package com.example.union_ad_ssgf.interstitialad + +import android.annotation.SuppressLint +import android.app.Activity +import com.example.union_ad_ssgf.DownloadConfirmHelper +import com.example.union_ad_ssgf.LogUtil +import com.qq.e.ads.interstitial2.UnifiedInterstitialAD +import com.qq.e.ads.interstitial2.UnifiedInterstitialADListener +import com.example.union_ad_ssgf.UnionAdSsgfEventPlugin +import com.qq.e.ads.interstitial2.ADRewardListener +import com.qq.e.comm.pi.IBidding +import com.qq.e.comm.util.AdError + +@SuppressLint("StaticFieldLeak") +object InterstitialAd { + private val TAG = "InterstitialAd" + + private lateinit var context: Activity + private var unifiedInterstitialAD: UnifiedInterstitialAD? = null + + private var codeId: String? = null + private var isFullScreen: Boolean? = false + private var downloadConfirm: Boolean = false + + //是否开启竞价 + private var isBidding: Boolean = false + + fun init(context: Activity, params: Map<*, *>) { + this.context = context + this.codeId = params["androidId"] as String + this.isFullScreen = params["isFullScreen"] as Boolean + this.downloadConfirm = params["downloadConfirm"] as Boolean + this.isBidding = params["isBidding"] as Boolean + loadInterstitialAD() + } + + private fun loadInterstitialAD() { + unifiedInterstitialAD = UnifiedInterstitialAD( + context, + codeId, + interstitialADListener + ) + if (isFullScreen!!) { + unifiedInterstitialAD?.setRewardListener(adRewardListener) + unifiedInterstitialAD?.loadFullScreenAD() + } else { + unifiedInterstitialAD?.loadAD() + } + } + + fun showAd(params: Map<*, *>) { + if (unifiedInterstitialAD == null) { + val map: MutableMap = + mutableMapOf("adType" to "interactAd", "onAdMethod" to "onUnReady") + UnionAdSsgfEventPlugin.sendContent(map) + LogUtil.e("$TAG 插屏全屏视频广告显示失败,无广告") + return + } + // 广告是否有效,无效广告将无法展示 + if (!unifiedInterstitialAD?.isValid!!) { + val map: MutableMap = mutableMapOf( + "adType" to "interactAd", + "onAdMethod" to "onFail", + "code" to 1, + "message" to "插屏全屏视频视频广告显示失败,无效广告" + ) + UnionAdSsgfEventPlugin.sendContent(map) + LogUtil.e("$TAG 插屏全屏视频广告显示失败,无效广告") + unifiedInterstitialAD?.close() + unifiedInterstitialAD?.destroy() + unifiedInterstitialAD = null + return + } + //是否为竞价模式 + if (isBidding) { + val isSuccess: Boolean = params["isSuccess"] as Boolean + //是否成功 + if (isSuccess) { + unifiedInterstitialAD?.sendWinNotification( + mutableMapOf( + //对应值为竞胜出价,类型为Integer + IBidding.EXPECT_COST_PRICE to params["expectCostPrice"], + //对应值为最大竞败方出价,类型为Integer + IBidding.HIGHEST_LOSS_PRICE to params["highestLossPrice"], + ) + ) + if (isFullScreen!!) { + unifiedInterstitialAD?.showFullScreenAD(context) + } else { + unifiedInterstitialAD?.show() + } + } else { + unifiedInterstitialAD?.sendLossNotification( + mutableMapOf( + //值为本次竞胜方出价(单位:分),类型为Integer。选填 + IBidding.WIN_PRICE to params["winPrice"], + //值为优量汇广告竞败原因,类型为Integer。必填 + IBidding.LOSS_REASON to params["lossReason"], + //值为本次竞胜方渠道ID,类型为Integer。必填。 + IBidding.ADN_ID to params["adnId"], + ) + ) + } + } else { + if (isFullScreen!!) { + unifiedInterstitialAD?.showFullScreenAD(context) + } else { + unifiedInterstitialAD?.show() + } + } + } + + private var interstitialADListener = object : UnifiedInterstitialADListener { + //插屏全屏视频广告加载完毕,此回调后才可以调用 show 方法 + override fun onADReceive() { + LogUtil.e("$TAG 插屏全屏视频广告加载完毕 $downloadConfirm") + if (downloadConfirm) { + unifiedInterstitialAD?.setDownloadConfirmListener(DownloadConfirmHelper.DOWNLOAD_CONFIRM_LISTENER) + } + } + + //插屏全屏视频视频广告,视频素材下载完成 + override fun onVideoCached() { + LogUtil.e("$TAG 插屏全屏视频视频广告,视频素材下载完成") + } + + //广告加载失败,error 对象包含了错误码和错误信息 + override fun onNoAD(p0: AdError?) { + LogUtil.e("$TAG 插屏全屏视频视频广告,加载失败 ${p0?.errorCode} ${p0?.errorMsg}") + val map: MutableMap = mutableMapOf( + "adType" to "interactAd", + "onAdMethod" to "onFail", + "code" to p0?.errorCode, + "message" to p0?.errorMsg + ) + UnionAdSsgfEventPlugin.sendContent(map) + } + + //插屏全屏视频广告展开时回调 + override fun onADOpened() { + LogUtil.e("$TAG 插屏全屏视频广告展开时回调") + val map: MutableMap = + mutableMapOf("adType" to "interactAd", "onAdMethod" to "onShow") + UnionAdSsgfEventPlugin.sendContent(map) + } + + //插屏全屏视频广告曝光时回调 + override fun onADExposure() { + LogUtil.e("$TAG 插屏全屏视频广告曝光时回调") + val map: MutableMap = + mutableMapOf("adType" to "interactAd", "onAdMethod" to "onExpose") + UnionAdSsgfEventPlugin.sendContent(map) + } + + //插屏全屏视频广告点击时回调 + override fun onADClicked() { + LogUtil.e("$TAG 插屏全屏视频广告点击时回调") + val map: MutableMap = + mutableMapOf("adType" to "interactAd", "onAdMethod" to "onClick") + UnionAdSsgfEventPlugin.sendContent(map) + } + + override fun onADLeftApplication() { + LogUtil.e("$TAG 插屏全屏视频视频广告,渲染成功") + } + + //插屏全屏视频广告关闭时回调 + override fun onADClosed() { + LogUtil.e("$TAG 插屏全屏视频广告关闭时回调") + val map: MutableMap = + mutableMapOf("adType" to "interactAd", "onAdMethod" to "onClose") + UnionAdSsgfEventPlugin.sendContent(map) + unifiedInterstitialAD?.close() + unifiedInterstitialAD?.destroy() + unifiedInterstitialAD = null + } + + // 插屏全屏视频视频广告,渲染成功 + override fun onRenderSuccess() { + LogUtil.e("$TAG 插屏全屏视频视频广告,渲染成功") + if (isBidding) { + val map: MutableMap = + mutableMapOf( + "adType" to "interactAd", + "onAdMethod" to "onECPM", + "ecpmLevel" to unifiedInterstitialAD?.ecpmLevel, + "ecpm" to unifiedInterstitialAD?.ecpm + ) + UnionAdSsgfEventPlugin.sendContent(map) + } else { + val map: MutableMap = + mutableMapOf("adType" to "interactAd", "onAdMethod" to "onReady") + UnionAdSsgfEventPlugin.sendContent(map) + } + } + + //插屏全屏视频视频广告,渲染失败 + override fun onRenderFail() { + LogUtil.e("$TAG 插屏全屏视频视频广告,渲染失败") + val map: MutableMap = mutableMapOf( + "adType" to "interactAd", + "onAdMethod" to "onFail", + "code" to 0, + "message" to "插屏全屏视频视频广告渲染失败" + ) + UnionAdSsgfEventPlugin.sendContent(map) + unifiedInterstitialAD?.close() + unifiedInterstitialAD?.destroy() + unifiedInterstitialAD = null + } + + } + + + private var adRewardListener = object : ADRewardListener { + override fun onReward(p0: MutableMap?) { + LogUtil.e("$TAG 激励奖励 $p0") + val map: MutableMap = mutableMapOf( + "adType" to "interactAd", + "onAdMethod" to "onVerify", + "transId" to p0!!["transId"] + ) + UnionAdSsgfEventPlugin.sendContent(map) + } + + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/load/FeedAdLoad.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/load/FeedAdLoad.kt deleted file mode 100644 index 80e271d..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/load/FeedAdLoad.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.example.union_ad_ssgf.load - -import android.app.Activity -import android.content.Intent -import android.util.Log -import androidx.localbroadcastmanager.content.LocalBroadcastManager -import com.example.union_ad_ssgf.config.UnionADConfig -import com.example.union_ad_ssgf.event.AdEventAction -import com.example.union_ad_ssgf.page.BaseAdPage -import com.qq.e.ads.nativ.ADSize -import com.qq.e.ads.nativ.NativeExpressAD -import com.qq.e.ads.nativ.NativeExpressADView -import com.qq.e.comm.util.AdError -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import java.util.Locale - -/** 信息流加载对象 */ -class FeedAdLoad : BaseAdPage(), NativeExpressAD.NativeExpressADListener { - private val TAG = FeedAdLoad::class.java.getSimpleName() - private var result: MethodChannel.Result? = null - - /** - * 加载信息流广告列表 - * @param call - * @param result - */ - fun loadFeedAdList(activity: Activity?, call: MethodCall, result: MethodChannel.Result) { - this.result = result - showAd(activity, null, call) - } - - override fun onNoAD(error: AdError) { - val msg = - String.format( - Locale.getDefault(), - "onError, error code: %d, error msg: %s", - error.getErrorCode(), - error.getErrorMsg() - ) - Log.i(TAG, "onError, adError=$msg") - sendErrorEvent(error.getErrorCode(), error.getErrorMsg()) - this.result!!.success(ArrayList()) - } - - override fun onADLoaded(list: MutableList) { - Log.i(TAG, "onADLoaded") - val adResultList: MutableList = ArrayList() - - if (list.isEmpty()) { - result!!.success(adResultList) - return - } - for (adItem in list) { - val key = adItem.hashCode() - adResultList.add(key) - FeedAdManager.getInstance()?.putAd(key, adItem) - } - // 添加广告事件 - sendEvent(AdEventAction.onAdLoaded) - result!!.success(adResultList) - } - - override fun onRenderFail(nativeExpressADView: NativeExpressADView) { - Log.i(TAG, "onRenderFail") - sendErrorEvent(-100, "onRenderFail") - sendBroadcastEvent(nativeExpressADView, AdEventAction.onAdError) - } - - private fun sendBroadcastEvent(adView: NativeExpressADView, event: String) { - val intent = Intent() - intent.setAction(UnionADConfig.KEY_FEED_VIEW + "_" + adView.hashCode()) - intent.putExtra("event", event) - LocalBroadcastManager.getInstance(activity!!).sendBroadcast(intent) - } - - override fun onRenderSuccess(nativeExpressADView: NativeExpressADView) { - Log.i(TAG, "onRenderSuccess") - sendEvent(AdEventAction.onAdPresent) - sendBroadcastEvent(nativeExpressADView, AdEventAction.onAdPresent) - } - - override fun onADExposure(nativeExpressADView: NativeExpressADView?) { - Log.i(TAG, "onADExposure") - sendEvent(AdEventAction.onAdExposure) - } - - override fun onADClicked(nativeExpressADView: NativeExpressADView?) { - Log.i(TAG, "onADClicked") - sendEvent(AdEventAction.onAdClicked) - } - - override fun onADClosed(nativeExpressADView: NativeExpressADView) { - Log.i(TAG, "onADClosed") - sendEvent(AdEventAction.onAdClosed) - sendBroadcastEvent(nativeExpressADView, AdEventAction.onAdClosed) - } - - override fun onADLeftApplication(p0: NativeExpressADView?) {} - - override fun loadAd(call: MethodCall?) { - // 获取请求模板广告素材的尺寸 - val width: Int = call!!.argument("width")!! - val height: Int = call.argument("height")!! - val count: Int = call.argument("count")!! - val ad = NativeExpressAD(activity, ADSize(width, height), posId, this) - ad.loadAD(count) - } -} diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/load/FeedAdManager.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/load/FeedAdManager.kt deleted file mode 100644 index d4d0e9c..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/load/FeedAdManager.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.example.union_ad_ssgf.load - -import com.qq.e.ads.nativ.NativeExpressADView - - -/** - * 信息流广告管理 - */ -class FeedAdManager { - private val TAG = FeedAdManager::class.java.getSimpleName() - - companion object { - // 信息流广告管理类对象 - private var _instance: FeedAdManager? = null - - @Synchronized - fun getInstance(): FeedAdManager? { - if (_instance == null) { - synchronized(FeedAdManager::class.java) { _instance = FeedAdManager() } - } - return _instance - } - } - - // 信息流广告列表 - private val feedAdList: MutableMap = HashMap() - - /** - * 添加广告渲染对象 - * - * @param key 广告缓存id - * @param ad 广告渲染对象 - */ - fun putAd(key: Int, ad: NativeExpressADView) { - feedAdList[key] = ad - } - - /** - * 获取广告渲染对象 - * - * @param key 广告缓存 id - * @return 广告渲染对象 - */ - fun getAd(key: Int): NativeExpressADView? { - return feedAdList[key] - } - - /** - * 删除广告渲染对象 - * - * @param key 广告缓存id - * @return 广告渲染对象 - */ - fun removeAd(key: Int): NativeExpressADView? { - return feedAdList.remove(key) - } -} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/page/AdBannerView.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/page/AdBannerView.kt deleted file mode 100644 index 8d71c96..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/page/AdBannerView.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.example.union_ad_ssgf.page - -import android.content.Context -import android.view.View -import android.widget.FrameLayout -import com.qq.e.ads.banner2.UnifiedBannerADListener -import com.qq.e.ads.banner2.UnifiedBannerView -import com.qq.e.comm.util.AdError -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.platform.PlatformView -import java.util.Locale - -import android.util.Log; -import com.example.union_ad_ssgf.PluginDelegate -import com.example.union_ad_ssgf.event.AdEventAction - -class AdBannerView( - context: Context, - private var id: Int, - creationParams: Map?, - private var pluginDelegate: PluginDelegate? -) : BaseAdPage(), PlatformView, UnifiedBannerADListener { - private val TAG = AdBannerView::class.java.getSimpleName() - private var frameLayout = FrameLayout(context) - private var bv: UnifiedBannerView? = null - private var params = creationParams - - init { - val call = MethodCall("AdBannerView", creationParams) - showAd(this.pluginDelegate!!.activity, id, call); - } - - override fun loadAd(call: MethodCall?) { - // 获取轮播时间间隔参数 - val interval = params!!["interval"] as Int - // 加载广告 Banner - bv = UnifiedBannerView(activity, posId, this) - frameLayout!!.addView(bv) - // 设置轮播时间间隔 - bv!!.setRefresh(interval) - bv!!.loadAD() - } - - override fun getView(): View? { - return frameLayout; - } - - override fun dispose() { - disposeAd(); - } - - override fun onNoAD(error: AdError) { - val msg = java.lang.String.format( - Locale.getDefault(), "onNoAD, error code: %d, error msg: %s", - error.getErrorCode(), error.getErrorMsg() - ) - Log.e(TAG, msg) - sendErrorEvent(error.getErrorCode(), error.getErrorMsg()) - disposeAd() - } - - override fun onADReceive() { - Log.i(TAG, "onADReceive"); - sendEvent(AdEventAction.onAdLoaded); - } - - override fun onADExposure() { - Log.i(TAG, "onADExposure"); - sendEvent(AdEventAction.onAdExposure); - } - - override fun onADClosed() { - Log.i(TAG, "onADClosed"); - sendEvent(AdEventAction.onAdClosed); - disposeAd(); - } - - override fun onADClicked() { - Log.i(TAG, "onADClicked"); - sendEvent(AdEventAction.onAdClicked); - } - - override fun onADLeftApplication() { - Log.i(TAG, "onADLeftApplication"); - } - - /** - * 销毁广告 - */ - private fun disposeAd() { - frameLayout!!.removeAllViews() - if (bv != null) { - bv!!.destroy() - } - } - -} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/page/AdFeedView.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/page/AdFeedView.kt deleted file mode 100644 index b0b139f..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/page/AdFeedView.kt +++ /dev/null @@ -1,91 +0,0 @@ -package com.example.union_ad_ssgf.page - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.view.View -import android.widget.FrameLayout -import androidx.localbroadcastmanager.content.LocalBroadcastManager -import com.example.union_ad_ssgf.PluginDelegate -import com.example.union_ad_ssgf.config.UnionADConfig -import com.example.union_ad_ssgf.event.AdEventAction -import com.example.union_ad_ssgf.load.FeedAdManager -import com.qq.e.ads.nativ.NativeExpressADView -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.platform.PlatformView - -class AdFeedView( - context: Context, - private var id: Int, - private var creationParams: Map?, - private var pluginDelegate: PluginDelegate? -) : BaseAdPage(), PlatformView { - private var frameLayout = FrameLayout(context) - private var fad: NativeExpressADView? = null - private var receiver: BroadcastReceiver? = null - - init { - val call = MethodCall("AdFeedView", creationParams) - showAd(this.pluginDelegate!!.activity, id, call) - } - - override fun loadAd(call: MethodCall?) { - val key = adId!!.toInt() - regReceiver(key) - fad = FeedAdManager.getInstance()!!.getAd(key) - if (fad != null) { - if (frameLayout.childCount > 0) { - frameLayout.removeAllViews() - } - fad!!.render() - frameLayout.addView(fad) - } - } - - override fun getView(): View? { - return frameLayout - } - - override fun dispose() { - removeAd() - } - - /** - * 注册广播 - * @param key key - */ - private fun regReceiver(key: Int) { - // 注册广播 - receiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val event = intent.getStringExtra("event") - sendEvent(event) - if (AdEventAction.onAdClosed == event || AdEventAction.onAdError == event) { - this@AdFeedView.disposeAd() - } - } - } - val intentFilter = IntentFilter(UnionADConfig.KEY_FEED_VIEW + "_" + key) - LocalBroadcastManager.getInstance(activity!!).registerReceiver(receiver!!, intentFilter) - } - - /** 移除广告 */ - private fun removeAd() { - frameLayout!!.removeAllViews() - // 注销广播 - if (receiver != null) { - LocalBroadcastManager.getInstance(activity!!).unregisterReceiver(receiver!!) - } - } - - /** 销毁广告 */ - private fun disposeAd() { - removeAd() - FeedAdManager.getInstance()!!.removeAd(adId!!.toInt()) - if (fad != null) { - fad!!.destroy() - } - } -} diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/page/AdSplashActivity.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/page/AdSplashActivity.kt deleted file mode 100644 index 7ab1858..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/page/AdSplashActivity.kt +++ /dev/null @@ -1,187 +0,0 @@ -package com.example.union_ad_ssgf.page - -import android.annotation.SuppressLint -import android.os.Bundle -import android.text.TextUtils -import android.util.Log -import android.view.KeyEvent -import android.view.View -import android.widget.FrameLayout -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.AppCompatImageView -import com.example.union_ad_ssgf.R -import com.example.union_ad_ssgf.config.UnionADConfig -import com.example.union_ad_ssgf.event.AdErrorEvent -import com.example.union_ad_ssgf.event.AdEvent -import com.example.union_ad_ssgf.event.AdEventAction -import com.example.union_ad_ssgf.event.AdEventHandler -import com.qq.e.ads.splash.SplashAD -import com.qq.e.ads.splash.SplashADListener -import com.qq.e.comm.util.AdError - - -/** - * 开屏广告 - */ -class AdSplashActivity() : AppCompatActivity(), SplashADListener { - private val TAG = AdSplashActivity::class.java.getSimpleName() - /** - * 广告容器 - */ - private var ad_container: FrameLayout? = null - - /** - * 自定义品牌 logo - */ - private var ad_logo: AppCompatImageView? = null - - /** - * 广告位 id - */ - private var posId: String? = null - - /** - * 是否全屏 - */ - private var isFullScreen = false - - /** - * 开屏广告 - */ - private var splashAD: SplashAD? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_ad_splash); - initView(); - initData(); - } - - /** - * 初始化View - */ - private fun initView() { - ad_container = findViewById(R.id.splash_ad_container) - ad_logo = findViewById(R.id.splash_ad_logo) - } - - /** - * 初始化数据 - */ - private fun initData() { - // 获取参数 - posId = intent.getStringExtra(UnionADConfig.KEY_POSID) - val logo = intent.getStringExtra(UnionADConfig.KEY_LOGO) - val fetchDelay = intent.getDoubleExtra(UnionADConfig.KEY_FETCH_DELAY, 0.0) - val absFetchDelay = (fetchDelay * 1000).toInt() - isFullScreen = TextUtils.isEmpty(logo) - // 创建开屏广告 - splashAD = SplashAD(this, posId, this, absFetchDelay) - if (isFullScreen) { - // logo 为空则加载全屏广告 - ad_logo!!.setVisibility(View.GONE) - splashAD!!.fetchFullScreenAdOnly() - } else { - // 加载 logo - val resId: Int = getMipmapId(logo!!) - if (resId > 0) { - ad_logo!!.setVisibility(View.VISIBLE) - ad_logo!!.setImageResource(resId) - } - // 加载广告 - splashAD!!.fetchAdOnly() - } - } - - /** - * 广告关闭时调用,可能是用户关闭或者展示时间到。此时一般需要跳过开屏的 Activity,进入应用内容页面 - */ - override fun onADDismissed() { - Log.d(TAG, "onADDismissed") - finishPage() - AdEventHandler.getInstance()?.sendEvent(AdEvent(posId, "", 1, AdEventAction.onAdClosed)); - } - - /** - * 广告加载或展示过程中出错,AdError中包含了错误码和错误描述,具体错误码内容可参考错误码部分 - */ - override fun onNoAD(adError: AdError) { - Log.d(TAG, "onNoAD adError:" + adError.errorMsg); - finishPage() - AdEventHandler.getInstance()!! - .sendEvent(AdErrorEvent(posId, "", 1, adError.errorCode, adError.errorMsg)); - } - - /** - * 广告成功展示时调用,成功展示不等于有效展示(比如广告容器高度不够) - */ - override fun onADPresent() { - Log.d(TAG, "onADPresent") - AdEventHandler.getInstance()!!.sendEvent(AdEvent(posId, "", 1, AdEventAction.onAdPresent)) - } - - /** - * 广告被点击时调用,不代表满足计费条件(如点击时网络异常) - */ - override fun onADClicked() { - Log.d(TAG, "onADClicked") - AdEventHandler.getInstance()!!.sendEvent(AdEvent(posId, "", 1, AdEventAction.onAdClicked)) - } - - /** - * 倒计时回调,返回广告还将被展示的剩余时间,单位是 ms - */ - override fun onADTick(millisUntilFinished: Long) { - Log.d(TAG, "onADTick millisUntilFinished:$millisUntilFinished"); - } - - /** - * 广告曝光时调用 - */ - override fun onADExposure() { - Log.d(TAG, "onADExposure") - AdEventHandler.getInstance()!!.sendEvent(AdEvent(posId, "", 1, AdEventAction.onAdExposure)) - } - - /** - * 广告加载成功的回调,在fetchAdOnly的情况下,表示广告拉取成功可以显示了。 - * 广告需要在SystemClock.elapsedRealtime (UnionADConfig.KEY_POSID) - this.adId = call.argument(UnionADConfig.KEY_ADID) - loadAd(call) - } - - /** - * 加载广告 - * @param call 方法调用 - */ - abstract fun loadAd(call: MethodCall?) - - /** - * 发送广告事件 - * @param event 广告事件 - */ - protected fun sendEvent(event: AdEvent?) { - AdEventHandler.getInstance()?.sendEvent(event) - } - - /** - * 发送广告事件 - * - * @param action 操作 - */ - protected fun sendEvent(action: String?) { - sendEvent(AdEvent(posId, adId, viewId, action)) - } - - /** - * 发送错误事件 - * - * @param errCode 错误码 - * @param errMsg 错误事件 - */ - protected fun sendErrorEvent(errCode: Int, errMsg: String?) { - sendEvent(AdErrorEvent(posId, adId, viewId, errCode, errMsg)) - } -} diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/page/NativeViewFactory.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/page/NativeViewFactory.kt deleted file mode 100644 index dcb9a4d..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/page/NativeViewFactory.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.example.union_ad_ssgf.page - -import android.content.Context -import com.example.union_ad_ssgf.PluginDelegate -import com.example.union_ad_ssgf.config.UnionADConfig -import io.flutter.plugin.common.StandardMessageCodec -import io.flutter.plugin.platform.PlatformView -import io.flutter.plugin.platform.PlatformViewFactory - -/** - * View 名字 - * @param viewName: 插件代理类 - */ -class NativeViewFactory( - private val viewName: String, - private val pluginDelegate: PluginDelegate -) : - PlatformViewFactory(StandardMessageCodec.INSTANCE) { - - override fun create(context: Context, viewId: Int, args: Any?): PlatformView { - val creationParams = args as Map - return if (viewName == UnionADConfig.KEY_BANNER_VIEW) { - AdBannerView(context, viewId, creationParams, pluginDelegate) - } else { - AdFeedView(context, viewId, creationParams, pluginDelegate) - } - } - -} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/page/RewardVideoPage.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/page/RewardVideoPage.kt deleted file mode 100644 index 6f6aeb1..0000000 --- a/android/src/main/kotlin/com/example/union_ad_ssgf/page/RewardVideoPage.kt +++ /dev/null @@ -1,138 +0,0 @@ -package com.example.union_ad_ssgf.page - -import android.util.Log -import com.example.union_ad_ssgf.event.AdEventAction -import com.example.union_ad_ssgf.event.AdRewardEvent -import com.qq.e.ads.rewardvideo.RewardVideoAD -import com.qq.e.ads.rewardvideo.RewardVideoADListener -import com.qq.e.ads.rewardvideo.ServerSideVerificationOptions -import com.qq.e.comm.util.AdError -import io.flutter.plugin.common.MethodCall -import java.util.Locale - -/** - * 激励视频页面 - */ -class RewardVideoPage() : BaseAdPage(), RewardVideoADListener { - private val TAG = RewardVideoPage::class.java.getSimpleName() - /** - * 激励广告对象 - */ - private lateinit var rewardVideoAD: RewardVideoAD - - /** - * 设置激励视频服务端验证的自定义信息 - */ - private lateinit var customData: String - - /** - * 设置服务端验证的用户信息 - */ - private lateinit var userId: String - - /** - * 加载激励视频广告,加载成功则调用回调RewardVideoADListener.onADLoad(), - * 加载失败则会调用RewardVideoADListener.onError(AdError error) - */ - override fun loadAd(call: MethodCall?) { - /** - * 获取对应参数 - */ - val playMuted: Boolean = call!!.argument("playMuted")!! - customData = call.argument("customData")!! - userId = call.argument("userId")!! - - // 1. 初始化激励视频广告 - rewardVideoAD = RewardVideoAD(activity, posId, this, !playMuted) - - // 2. 设置服务端验证信息 - val options = ServerSideVerificationOptions.Builder() - .setCustomData(customData) - .setUserId(userId) // 设置服务端验证的用户信息 - .build() - - // 3. 服务端验证信息 - rewardVideoAD.setServerSideVerificationOptions(options) - - // 4. 加载激励视频广告 - rewardVideoAD.loadAD() - } - - /** - * 广告加载成功,可在此回调后进行广告展示 - **/ - override fun onADLoad() { - Log.i(TAG, "onADLoad") - rewardVideoAD.showAD() - sendEvent(AdEventAction.onAdLoaded) - } - - /** - * 视频素材缓存成功,可在此回调后进行广告展示 - */ - override fun onVideoCached() { - Log.i(TAG, "onVideoCached") - } - - /** - * 激励视频广告页面展示 - */ - override fun onADShow() { - Log.i(TAG, "onADShow") - sendEvent(AdEventAction.onAdPresent) - } - - /** - * 激励视频广告曝光 - */ - override fun onADExpose() { - Log.i(TAG, "onADExpose") - sendEvent(AdEventAction.onAdExposure) - } - - /** - * 激励视频触发激励(观看视频大于一定时长或者视频播放完毕) - * @param map 若选择了服务端验证,可以通过 ServerSideVerificationOptions#TRANS_ID 键从 map 中获取此次交易的 id;若未选择服务端验证,则不需关注 map 参数。 - */ - override fun onReward(map: MutableMap?) { - val transId = map!![ServerSideVerificationOptions.TRANS_ID] as String? - Log.i(TAG, "onReward $transId") // 获取服务端验证的唯一 ID - sendEvent(AdRewardEvent(posId,"",1,AdEventAction.onAdReward, transId!!, customData, userId)) - } - - /** - * 激励视频广告被点击 - */ - override fun onADClick() { - Log.i(TAG, "onADClick") - sendEvent(AdEventAction.onAdClicked) - } - - /** - * 激励视频广告被关闭 - */ - override fun onVideoComplete() { - Log.i(TAG, "onVideoComplete") - } - - /** - * 激励视频广告被关闭 - */ - override fun onADClose() { - Log.i(TAG, "onADClose") - sendEvent(AdEventAction.onAdClosed) - } - - /** - * 广告流程出错 - */ - override fun onError(error: AdError) { - val msg = java.lang.String.format( - Locale.getDefault(), - "onError, error code: %d, error msg: %s", - error.errorCode, error.errorMsg, - ) - Log.i(TAG, "onError, adError=$msg") - sendErrorEvent(error.errorCode, error.errorMsg) - } -} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/rewardvideoad/RewardVideoAd.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/rewardvideoad/RewardVideoAd.kt new file mode 100644 index 0000000..5b044b2 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/rewardvideoad/RewardVideoAd.kt @@ -0,0 +1,182 @@ +package com.example.union_ad_ssgf.rewardvideoad + +import android.annotation.SuppressLint +import android.content.Context +import com.example.union_ad_ssgf.DownloadConfirmHelper +import com.example.union_ad_ssgf.LogUtil +import com.example.union_ad_ssgf.UnionAdSsgfEventPlugin +import com.qq.e.ads.rewardvideo.RewardVideoAD +import com.qq.e.ads.rewardvideo.RewardVideoADListener +import com.qq.e.ads.rewardvideo.ServerSideVerificationOptions +import com.qq.e.comm.pi.IBidding +import com.qq.e.comm.util.AdError + + +@SuppressLint("StaticFieldLeak") +object RewardVideoAd { + private val TAG = "RewardVideoAd" + + private lateinit var context: Context + private var rewardVideoAD: RewardVideoAD? = null + + private var codeId: String? = null + private var userID: String = "" + private var rewardName: String = "" + private var rewardAmount: Int = 0 + private var customData: String = "" + private var downloadConfirm: Boolean = false + private var videoMuted: Boolean = false + + //是否开启竞价 + private var isBidding: Boolean = false + + fun init(context: Context, params: Map<*, *>) { + this.context = context + this.codeId = params["androidId"] as String + this.userID = params["userID"] as String + this.rewardName = params["rewardName"] as String + this.rewardAmount = params["rewardAmount"] as Int + this.customData = params["customData"] as String + this.downloadConfirm = params["downloadConfirm"] as Boolean + this.isBidding = params["isBidding"] as Boolean + this.videoMuted = params["videoMuted"] as Boolean + loadRewardVideoAd() + } + + private fun loadRewardVideoAd() { + rewardVideoAD = RewardVideoAD(context, codeId, rewardVideoADListener,videoMuted) + val options = ServerSideVerificationOptions.Builder() + .setUserId(userID) + .setCustomData(customData) + .build() + rewardVideoAD?.setServerSideVerificationOptions(options) + rewardVideoAD?.loadAD() + } + + fun showAd(params: Map<*, *>) { + if (rewardVideoAD == null) { + val map: MutableMap = + mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onUnReady") + UnionAdSsgfEventPlugin.sendContent(map) + return + } + //是否为竞价模式 + if (isBidding) { + val isSuccess: Boolean = params["isSuccess"] as Boolean + //是否成功 + if (isSuccess) { + rewardVideoAD?.sendWinNotification( + mutableMapOf( + //对应值为竞胜出价,类型为Integer + IBidding.EXPECT_COST_PRICE to params["expectCostPrice"], + //对应值为最大竞败方出价,类型为Integer + IBidding.HIGHEST_LOSS_PRICE to params["highestLossPrice"], + ) + ) + rewardVideoAD?.showAD() + } else { + rewardVideoAD?.sendLossNotification( + mutableMapOf( + //值为本次竞胜方出价(单位:分),类型为Integer。选填 + IBidding.WIN_PRICE to params["winPrice"], + //值为优量汇广告竞败原因,类型为Integer。必填 + IBidding.LOSS_REASON to params["lossReason"], + //值为本次竞胜方渠道ID,类型为Integer。必填。 + IBidding.ADN_ID to params["adnId"], + ) + ) + } + } else { + rewardVideoAD?.showAD() + } + + } + + private var rewardVideoADListener = object : RewardVideoADListener { + override fun onADLoad() { + LogUtil.e("$TAG 激励广告加载成功") + if (downloadConfirm) { + rewardVideoAD?.setDownloadConfirmListener(DownloadConfirmHelper.DOWNLOAD_CONFIRM_LISTENER) + } + } + + override fun onVideoCached() { + LogUtil.e("$TAG 激励广告视频素材缓存成功") + if (isBidding) { + val map: MutableMap = + mutableMapOf( + "adType" to "rewardAd", + "onAdMethod" to "onECPM", + "ecpmLevel" to rewardVideoAD?.ecpmLevel, + "ecpm" to rewardVideoAD?.ecpm + ) + UnionAdSsgfEventPlugin.sendContent(map) + } else { + val map: MutableMap = + mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onReady") + UnionAdSsgfEventPlugin.sendContent(map) + } + } + + override fun onADShow() { + LogUtil.e("$TAG 激励视频广告页面展示") + val map: MutableMap = + mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onShow") + UnionAdSsgfEventPlugin.sendContent(map) + } + + override fun onADExpose() { + LogUtil.e("$TAG 激励视频广告曝光") + val map: MutableMap = + mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onExpose") + UnionAdSsgfEventPlugin.sendContent(map) + } + + override fun onReward(p0: MutableMap?) { + LogUtil.e("$TAG 激励视频广告激励发放 $p0") + + val map: MutableMap = mutableMapOf( + "adType" to "rewardAd", + "onAdMethod" to "onVerify", + "transId" to p0!!["transId"], + "rewardName" to rewardName, + "rewardAmount" to rewardAmount + ) + UnionAdSsgfEventPlugin.sendContent(map) + } + + override fun onADClick() { + LogUtil.e("$TAG 激励视频广告被点击") + val map: MutableMap = + mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onClick") + UnionAdSsgfEventPlugin.sendContent(map) + } + + override fun onVideoComplete() { + LogUtil.e("$TAG 激励视频广告视频素材播放完毕") + val map: MutableMap = + mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onFinish") + UnionAdSsgfEventPlugin.sendContent(map) + } + + override fun onADClose() { + LogUtil.e("$TAG 激励视频广告被关闭") + val map: MutableMap = + mutableMapOf("adType" to "rewardAd", "onAdMethod" to "onClose") + UnionAdSsgfEventPlugin.sendContent(map) + rewardVideoAD = null + } + + override fun onError(p0: AdError?) { + LogUtil.e("$TAG 广告流程出错 ${p0?.errorCode} ${p0?.errorMsg}") + val map: MutableMap = mutableMapOf( + "adType" to "rewardAd", + "onAdMethod" to "onFail", + "code" to p0?.errorCode, + "message" to p0?.errorMsg + ) + UnionAdSsgfEventPlugin.sendContent(map) + } + + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/splashad/SplashAdView.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/splashad/SplashAdView.kt new file mode 100644 index 0000000..cf29500 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/splashad/SplashAdView.kt @@ -0,0 +1,151 @@ +package com.example.union_ad_ssgf.splashad + +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import com.example.union_ad_ssgf.DownloadConfirmHelper +import com.example.union_ad_ssgf.UnionAdSsgfConfig +import com.example.union_ad_ssgf.LogUtil +import com.qq.e.ads.splash.SplashAD +import com.qq.e.ads.splash.SplashADListener +import com.qq.e.comm.pi.IBidding +import com.qq.e.comm.util.AdError +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.platform.PlatformView + + +internal class SplashAdView( + var activity: Activity, + messenger: BinaryMessenger, + id: Int, + params: Map +) : + PlatformView, SplashADListener , MethodChannel.MethodCallHandler { + + private var mContainer: FrameLayout? = null + private var channel: MethodChannel? + + private var splashAD: SplashAD? = null + + //广告所需参数 + private var codeId: String = params["androidId"] as String + private var fetchDelay: Int = params["fetchDelay"] as Int + private var downloadConfirm: Boolean = params["downloadConfirm"] as Boolean + //是否开启竞价 + private var isBidding: Boolean = params["isBidding"] as Boolean + + init { + mContainer = FrameLayout(activity) + mContainer?.layoutParams?.width = ViewGroup.LayoutParams.WRAP_CONTENT + mContainer?.layoutParams?.height = ViewGroup.LayoutParams.WRAP_CONTENT + channel = MethodChannel(messenger, UnionAdSsgfConfig.splashAdView + "_" + id) + channel?.setMethodCallHandler(this) + loadSplashAd() + } + + private fun loadSplashAd() { + splashAD = SplashAD(activity, codeId, this, fetchDelay) + mContainer?.removeAllViews() + splashAD?.fetchAdOnly() + } + + + override fun getView(): View { + return mContainer!! + } + + /*************开屏广告回调******************/ + //广告关闭时调用,可能是用户关闭或者展示时间到。此时一般需要跳过开屏的 Activity,进入应用内容页面 + override fun onADDismissed() { + LogUtil.e("开屏广告关闭") + channel?.invokeMethod("onClose", "") + } + + //广告加载失败,error 对象包含了错误码和错误信息,错误码的详细内容可以参考文档第5章 + override fun onNoAD(p0: AdError?) { + LogUtil.e("开屏广告加载失败 ${p0?.errorCode} ${p0?.errorMsg}") + var map: MutableMap = mutableMapOf("code" to p0?.errorCode, "message" to p0?.errorMsg) + channel?.invokeMethod("onFail", map) + } + + //广告成功展示时调用,成功展示不等于有效展示(比如广告容器高度不够) + override fun onADPresent() { + LogUtil.e("开屏广告成功展示") + channel?.invokeMethod("onShow", "") + } + + //广告被点击时调用,不代表满足计费条件(如点击时网络异常) + override fun onADClicked() { + LogUtil.e("开屏广告被点击") + channel?.invokeMethod("onClick", "") + } + + //倒计时回调,返回广告还将被展示的剩余时间,单位是 ms + override fun onADTick(p0: Long) { + LogUtil.e("开屏广告倒计时回调 $p0") + channel?.invokeMethod("onADTick", p0) + } + + //广告曝光时调用 + override fun onADExposure() { + LogUtil.e("开屏广告曝光") + channel?.invokeMethod("onExpose", "") + } + + //广告加载成功的回调,在fetchAdOnly的情况下, + // 表示广告拉取成功可以显示了。广告需要在SystemClock.elapsedRealtime { + var arguments = call.arguments as Map<*, *> + splashAD?.sendWinNotification(mutableMapOf( + //对应值为竞胜出价,类型为Integer + IBidding.EXPECT_COST_PRICE to arguments["expectCostPrice"], + //对应值为最大竞败方出价,类型为Integer + IBidding.HIGHEST_LOSS_PRICE to arguments["highestLossPrice"], + )) + //展示banner + splashAD?.showAd(mContainer) + } + //竞价失败 + "biddingFail" -> { + var arguments = call.arguments as Map<*, *> + splashAD?.sendLossNotification(mutableMapOf( + //值为本次竞胜方出价(单位:分),类型为Integer。选填 + IBidding.WIN_PRICE to arguments["winPrice"], + //值为优量汇广告竞败原因,类型为Integer。必填 + IBidding.LOSS_REASON to arguments["lossReason"], + //值为本次竞胜方渠道ID,类型为Integer。必填。 + IBidding.ADN_ID to arguments["adnId"], + )) + } + + } + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/example/union_ad_ssgf/splashad/SplashAdViewFactory.kt b/android/src/main/kotlin/com/example/union_ad_ssgf/splashad/SplashAdViewFactory.kt new file mode 100644 index 0000000..13b0625 --- /dev/null +++ b/android/src/main/kotlin/com/example/union_ad_ssgf/splashad/SplashAdViewFactory.kt @@ -0,0 +1,18 @@ +package com.example.union_ad_ssgf.splashad + +import android.app.Activity +import android.content.Context +import com.example.union_ad_ssgf.bannerad.BannerAdView +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory + +class SplashAdViewFactory (private val messenger: BinaryMessenger, private val activity: Activity) : PlatformViewFactory( + StandardMessageCodec.INSTANCE) { + + override fun create(context: Context?, viewId: Int, args: Any?): PlatformView { + val params = args as Map + return SplashAdView(activity,messenger, viewId, params) + } +} \ No newline at end of file diff --git a/android/src/main/res/anim/download_confirm_dialog_slide_right_in.xml b/android/src/main/res/anim/download_confirm_dialog_slide_right_in.xml new file mode 100644 index 0000000..7d5eb9c --- /dev/null +++ b/android/src/main/res/anim/download_confirm_dialog_slide_right_in.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/android/src/main/res/anim/download_confirm_dialog_slide_up.xml b/android/src/main/res/anim/download_confirm_dialog_slide_up.xml new file mode 100644 index 0000000..914107a --- /dev/null +++ b/android/src/main/res/anim/download_confirm_dialog_slide_up.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/android/src/main/res/drawable/download_confirm_background_confirm.xml b/android/src/main/res/drawable/download_confirm_background_confirm.xml new file mode 100644 index 0000000..eb2bdf9 --- /dev/null +++ b/android/src/main/res/drawable/download_confirm_background_confirm.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/android/src/main/res/drawable/download_confirm_background_landscape.xml b/android/src/main/res/drawable/download_confirm_background_landscape.xml new file mode 100644 index 0000000..7a3bee2 --- /dev/null +++ b/android/src/main/res/drawable/download_confirm_background_landscape.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/android/src/main/res/drawable/download_confirm_background_portrait.xml b/android/src/main/res/drawable/download_confirm_background_portrait.xml new file mode 100644 index 0000000..148b739 --- /dev/null +++ b/android/src/main/res/drawable/download_confirm_background_portrait.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/android/src/main/res/drawable/ic_download_confirm_close.xml b/android/src/main/res/drawable/ic_download_confirm_close.xml new file mode 100644 index 0000000..f37d2a6 --- /dev/null +++ b/android/src/main/res/drawable/ic_download_confirm_close.xml @@ -0,0 +1,12 @@ + + + diff --git a/android/src/main/res/layout/download_confirm_dialog.xml b/android/src/main/res/layout/download_confirm_dialog.xml new file mode 100644 index 0000000..1cd1832 --- /dev/null +++ b/android/src/main/res/layout/download_confirm_dialog.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + +