Compare commits

...

8 Commits
appbase ... aes

6 changed files with 340 additions and 106 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed Nov 26 15:54:26 GMT 2025
stageCount=7
#Thu Nov 27 19:18:55 HKT 2025
stageCount=9
libraryProject=libaes
baseVersion=15.11
publishVersion=15.11.6
buildCount=32
baseBetaVersion=15.11.7
publishVersion=15.11.8
buildCount=0
baseBetaVersion=15.11.9

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed Nov 26 15:54:26 GMT 2025
stageCount=7
#Thu Nov 27 19:18:42 HKT 2025
stageCount=9
libraryProject=libaes
baseVersion=15.11
publishVersion=15.11.6
buildCount=32
baseBetaVersion=15.11.7
publishVersion=15.11.8
buildCount=0
baseBetaVersion=15.11.9

View File

@@ -0,0 +1,67 @@
package cc.winboll.studio.libaes.enums;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/27 12:35
* @Describe 隐私协议签约状态枚举
* 对应值0-拒绝1-赞同2-未签约(默认)
*/
public enum PrivacyAgreeStatus {
REJECTED(0, "拒绝"), // 0: 拒绝隐私协议
AGREED(1, "赞同"), // 1: 赞同隐私协议
UN_SIGNED(2, "未签约"); // 2: 未签约(初始默认状态)
private final int statusCode; // 对应存储的int值
private final String statusDesc; // 状态描述(可选,便于日志/UI显示
// Java 7 枚举构造方法必须private
private PrivacyAgreeStatus(int statusCode, String statusDesc) {
this.statusCode = statusCode;
this.statusDesc = statusDesc;
}
/**
* 根据int值获取枚举SP读取时使用兼容Java 7
* @param code 存储的int值0/1/2
* @return 对应枚举默认返回UN_SIGNED未签约
*/
public static PrivacyAgreeStatus fromCode(int code) {
// Java 7 不支持switch(String)用if-else兼容
if (code == REJECTED.statusCode) {
return REJECTED;
} else if (code == AGREED.statusCode) {
return AGREED;
} else {
return UN_SIGNED; // 默认未签约
}
}
/**
* 根据SP存储的字符串值获取枚举兼容原逻辑中String类型存储
* @param codeStr 存储的字符串值("0"/"1"/"2"
* @return 对应枚举默认返回UN_SIGNED未签约
*/
public static PrivacyAgreeStatus fromString(String codeStr) {
if (codeStr == null) {
return UN_SIGNED;
}
try {
int code = Integer.parseInt(codeStr);
return fromCode(code);
} catch (NumberFormatException e) {
// 字符串格式异常时,默认返回未签约
return UN_SIGNED;
}
}
// 获取状态码用于存储到SP
public int getStatusCode() {
return statusCode;
}
// 获取状态描述(用于日志/UI显示可选
public String getStatusDesc() {
return statusDesc;
}
}

View File

@@ -2,27 +2,19 @@ package cc.winboll.studio.libaes.views;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Display;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.enums.ADsMode;
import cc.winboll.studio.libaes.utils.MimoUtils;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import com.miui.zeus.mimo.sdk.ADParams;
import com.miui.zeus.mimo.sdk.BannerAd;
import com.miui.zeus.mimo.sdk.MimoCustomController;
@@ -30,8 +22,6 @@ import com.miui.zeus.mimo.sdk.MimoLocation;
import com.miui.zeus.mimo.sdk.MimoSdk;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import cc.winboll.studio.libaes.enums.ADsMode;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
@@ -84,9 +74,8 @@ public class ADsBannerView extends LinearLayout {
void initView(Context context) {
this.mContext = context;
initMimoSdk(this.mContext);
// 初始化主线程Handler关键确保广告操作在主线程执行
mMainHandler = new Handler(Looper.getMainLooper());
@@ -98,9 +87,13 @@ public class ADsBannerView extends LinearLayout {
public void resumeADs(final Activity activity) {
// 没有设置米盟广告支持就退出
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
// 2. 释放之前的广告资源
if (mBannerAd != null) {
mBannerAd.destroy();
}
return;
}
// 修复:优化广告请求逻辑(添加生命周期判断 + 主线程执行)
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
if (ADsControlView.getAdsModeFromStatic(this.mContext) == ADsMode.MIMO_SDK) {
@@ -126,7 +119,7 @@ public class ADsBannerView extends LinearLayout {
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
return;
}
LogUtils.d(TAG, "releaseAdResources()");
// 移除Handler回调
@@ -159,7 +152,7 @@ public class ADsBannerView extends LinearLayout {
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
return;
}
LogUtils.d(TAG, "showAd()");
// 1. 生命周期校验避免Activity已销毁时操作UI
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
@@ -227,7 +220,7 @@ public class ADsBannerView extends LinearLayout {
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
return;
}
LogUtils.d(TAG, "fetchAd()");
// 1. 双重校验Activity未销毁 + Context非空
if (activity == null || activity.isFinishing() || activity.isDestroyed() || activity.getApplicationContext() == null) {
@@ -325,7 +318,7 @@ public class ADsBannerView extends LinearLayout {
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
return;
}
// 修复:加载失败时移除当前广告实例
if (mAllBanners.contains(mBannerAd)) {
mAllBanners.remove(mBannerAd);

View File

@@ -2,31 +2,39 @@ package cc.winboll.studio.libaes.views;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.enums.ADsMode;
import cc.winboll.studio.libaes.enums.PrivacyAgreeStatus;
import cc.winboll.studio.libaes.views.ADsControlView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import com.miui.zeus.mimo.sdk.MimoCustomController;
import com.miui.zeus.mimo.sdk.MimoLocation;
import com.miui.zeus.mimo.sdk.MimoSdk;
import cc.winboll.studio.libappbase.ToastUtils;
import android.text.Html;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
@@ -37,7 +45,7 @@ import cc.winboll.studio.libappbase.ToastUtils;
public class ADsControlView extends LinearLayout {
public static final String TAG = "ADsControlView";
// SP存储配置
private static final String SP_NAME = "ads_control_config";
private static final String KEY_SELECTED_MODE = "selected_ads_mode";
@@ -45,15 +53,15 @@ public class ADsControlView extends LinearLayout {
ADsMode mADsMode;
private static final String PRIVACY_VALUE = "privacy_value";
// 隐私协议签约结果 0: 拒绝1赞同 2: 未签约
String privacyAgreeValue;
PrivacyAgreeStatus mPrivacyAgreeStatus;
// Handler消息标识
private static final int MSG_UPDATE_MODE = 1001;
// 控件引用
private RadioGroup rgAdsMode;
private RadioGroup rgADsMode;
private RadioButton rbStandalone;
private RadioButton rbMimoSdk;
private RadioButton rbMimoSDK;
// 外部监听、SP实例、Handler实例
private OnAdsModeSelectedListener listener;
@@ -81,13 +89,15 @@ public class ADsControlView extends LinearLayout {
initView(context);
}
public void setPrivacyAgreeValue(String privacyAgreeValue) {
this.privacyAgreeValue = privacyAgreeValue;
public void setPrivacyAgreeStatus(PrivacyAgreeStatus privacyAgreeStatus) {
this.mPrivacyAgreeStatus = privacyAgreeStatus;
sharedPreferences.edit().putString(PRIVACY_VALUE, this.mPrivacyAgreeStatus.name()).apply();
}
public String getPrivacyAgreeValue() {
String privacyAgreeValue = sharedPreferences.getString(PRIVACY_VALUE, "0");
return privacyAgreeValue;
public PrivacyAgreeStatus getPrivacyAgreeStatus() {
String privacyAgreeStatusStr = sharedPreferences.getString(PRIVACY_VALUE, PrivacyAgreeStatus.UN_SIGNED.name());
PrivacyAgreeStatus privacyAgreeStatus = PrivacyAgreeStatus.fromString(privacyAgreeStatusStr);
return privacyAgreeStatus;
}
public void setADsMode(ADsMode mADsMode) {
@@ -113,9 +123,9 @@ public class ADsControlView extends LinearLayout {
sharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
// 绑定控件
rgAdsMode = (RadioGroup) findViewById(R.id.rg_ads_mode);
rgADsMode = (RadioGroup) findViewById(R.id.rg_ads_mode);
rbStandalone = (RadioButton) findViewById(R.id.rb_standalone);
rbMimoSdk = (RadioButton) findViewById(R.id.rb_mimo_sdk);
rbMimoSDK = (RadioButton) findViewById(R.id.rb_mimo_sdk);
// 初始化Handler主线程Looper
mHandler = new InternalHandler(Looper.getMainLooper());
@@ -124,25 +134,27 @@ public class ADsControlView extends LinearLayout {
registerControlView(this);
// 从SP读取初始模式并设置
ToastUtils.show(String.format("savedMode : %s", getADsMode().name()));
//ToastUtils.show(String.format("savedMode : %s", getADsMode().name()));
setSelectedMode(getADsMode());
// 单选组选择事件监听
rgAdsMode.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
rgADsMode.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.rb_standalone) {
setADsMode(ADsMode.STANDALONE);
} else if (checkedId == R.id.rb_mimo_sdk) {
showPrivacy(context, new OnPrivacyChangeListener(){
handlePrivacyLogic((Activity)context, PrivacyAgreeStatus.UN_SIGNED , new OnPrivacyChangeListener(){
@Override
public void onAgreePrivacy() {
setADsMode(ADsMode.MIMO_SDK);
setSelectedMode(ADsMode.MIMO_SDK);
}
@Override
public void onDisagreePrivacy() {
setADsMode(ADsMode.STANDALONE);
setSelectedMode(ADsMode.STANDALONE);
}
});
}
@@ -154,31 +166,29 @@ public class ADsControlView extends LinearLayout {
* 【静态】显示隐私协议弹窗供外部调用带Context参数
* @param context 上下文需传入Activity Context用于弹窗显示
*/
public static void showPrivacy(Context context, OnPrivacyChangeListener onPrivacyChangeListener) {
if (context == null) {
LogUtils.e(TAG, "showPrivacy: Context is null, cannot show privacy dialog");
return;
}
// 校验是否为Activity Context弹窗必须依附Activity
Activity activity = null;
try {
activity = (Activity) context;
} catch (ClassCastException e) {
LogUtils.e(TAG, "showPrivacy: Context is not Activity Context", e);
Toast.makeText(context.getApplicationContext(), "请传入Activity上下文以显示隐私协议", Toast.LENGTH_SHORT).show();
return;
}
// 校验Activity状态
if (activity.isFinishing() || activity.isDestroyed()) {
LogUtils.e(TAG, "showPrivacy: Activity is finishing or destroyed");
return;
}
// 读取隐私协议状态并处理逻辑
SbhhharedPreferences sp = getPrivacySharedPreferences(context);
String privacyAgreeValue = sp.getString(PRIVACY_VALUE, null);
handlePrivacyLogic(activity, privacyAgreeValue, onPrivacyChangeListener);
}
// public void showPrivacy(OnPrivacyChangeListener onPrivacyChangeListener) {
// if (context == null) {
// LogUtils.e(TAG, "showPrivacy: Context is null, cannot show privacy dialog");
// return;
// }
// // 校验是否为Activity Context弹窗必须依附Activity
// Activity activity = null;
// try {
// activity = (Activity) context;
// } catch (ClassCastException e) {
// LogUtils.e(TAG, "showPrivacy: Context is not Activity Context", e);
// Toast.makeText(context.getApplicationContext(), "请传入Activity上下文以显示隐私协议", Toast.LENGTH_SHORT).show();
// return;
// }
// // 校验Activity状态
// if (activity.isFinishing() || activity.isDestroyed()) {
// LogUtils.e(TAG, "showPrivacy: Activity is finishing or destroyed");
// return;
// }
//
// // 读取隐私协议状态并处理逻辑
// handlePrivacyLogic(activity, getPrivacyAgreeStatus(), onPrivacyChangeListener);
// }
/**
* 【静态】清理SP中存储的隐私协议状态PRIVACY_VALUE
@@ -204,52 +214,30 @@ public class ADsControlView extends LinearLayout {
// 使用ApplicationContext获取SP避免内存泄漏
Context appContext = context.getApplicationContext();
if (appContext != null) {
return appContext.getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
return appContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
// 降级方案若ApplicationContext为空使用传入的Context
return context.getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
return context.getSharedPreferences(PRIVACY_VALUE, Context.MODE_PRIVATE);
}
// 【配套静态工具方法】隐私协议逻辑处理(供上述两个静态方法调用,需一并添加)
private static void handlePrivacyLogic(final Activity activity, String privacyAgreeValue, final OnPrivacyChangeListener onPrivacyChangeListener) {
if (TextUtils.equals(privacyAgreeValue, String.valueOf(0))) {
ADsControlView.updateAdsModeByStatic(activity.getApplicationContext(), ADsMode.STANDALONE);
private static void handlePrivacyLogic(final Activity activity, PrivacyAgreeStatus privacyAgreeStatus, final OnPrivacyChangeListener onPrivacyChangeListener) {
if (privacyAgreeStatus == PrivacyAgreeStatus.REJECTED) {
//ADsControlView.updateAdsModeByStatic(activity.getApplicationContext(), ADsMode.STANDALONE);
LogUtils.i(TAG, "已拒绝隐私协议,广告已处于不可用状态...");
Toast.makeText(activity.getApplicationContext(), "已拒绝隐私协议,广告已处于不可用状态", Toast.LENGTH_SHORT).show();
return;
} else if (TextUtils.equals(privacyAgreeValue, String.valueOf(1))) {
ADsControlView.updateAdsModeByStatic(activity.getApplicationContext(), ADsMode.MIMO_SDK);
} else if (privacyAgreeStatus == PrivacyAgreeStatus.AGREED) {
//ADsControlView.updateAdsModeByStatic(activity.getApplicationContext(), ADsMode.MIMO_SDK);
LogUtils.i(TAG, "已同意隐私协议开始初始化米盟SDK...");
initMimoSdkStatic(activity.getApplicationContext());
return;
} else {
LogUtils.i(TAG, "开始弹出隐私协议...");
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("用户须知");
builder.setMessage("小米广告SDK隐私政策: https://dev.mi.com/distribute/doc/details?pId=1688, 请复制到浏览器查看");
builder.setIcon(R.drawable.ic_launcher);
builder.setCancelable(false); // 点击对话框以外的区域不消失
builder.setPositiveButton("同意", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences sp = getPrivacySharedPreferences(activity);
sp.edit().putString(PRIVACY_VALUE, String.valueOf(1)).apply();
initMimoSdkStatic(activity.getApplicationContext());
dialog.dismiss();
onPrivacyChangeListener.onAgreePrivacy();
}
});
builder.setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences sp = getPrivacySharedPreferences(activity);
sp.edit().putString(PRIVACY_VALUE, String.valueOf(0)).apply();
ADsControlView.updateAdsModeByStatic(activity.getApplicationContext(), ADsMode.STANDALONE);
dialog.dismiss();
onPrivacyChangeListener.onDisagreePrivacy();
}
});
AlertDialog dialog = builder.create();
// 2. 替换后的XML布局对话框代码核心逻辑
AlertDialog dialog = createPrivacyDialog(activity, onPrivacyChangeListener);
dialog.show();
// 配置弹窗位置(底部全屏)
Window window = dialog.getWindow();
@@ -382,7 +370,7 @@ public class ADsControlView extends LinearLayout {
if (mode2 == ADsMode.STANDALONE) {
rbStandalone.setChecked(true);
} else if (mode2 == ADsMode.MIMO_SDK) {
rbMimoSdk.setChecked(true);
rbMimoSDK.setChecked(true);
}
} else {
mHandler.post(new Runnable() {
@@ -398,7 +386,7 @@ public class ADsControlView extends LinearLayout {
* 获取当前选中模式
*/
public ADsMode getSelectedMode() {
int checkedId = rgAdsMode.getCheckedRadioButtonId();
int checkedId = rgADsMode.getCheckedRadioButtonId();
return checkedId == R.id.rb_mimo_sdk ? ADsMode.MIMO_SDK : ADsMode.STANDALONE;
}
@@ -476,9 +464,116 @@ public class ADsControlView extends LinearLayout {
void onModeSelected(ADsMode selectedMode);
}
// 1. 先定义隐私协议变更监听接口(若已定义可忽略)
public interface OnPrivacyChangeListener {
void onAgreePrivacy();
void onDisagreePrivacy();
void onAgreePrivacy(); // 同意隐私协议回调
void onDisagreePrivacy();// 拒绝隐私协议回调
}
// 3. 修复创建隐私协议对话框XML布局版解决inflation崩溃
private static AlertDialog createPrivacyDialog(final Activity activity, final OnPrivacyChangeListener onPrivacyChangeListener) {
// 1. 加载自定义XML布局修复避免解析<a>标签崩溃)
View dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_privacy_agreement, null);
// 2. 初始化对话框(无标题,使用自定义布局标题)
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setView(dialogView)
.setCancelable(false); // 点击外部不消失
final AlertDialog dialog = builder.create();
// 3. 初始化控件修复绑定链接TextView替代<a>标签)
final TextView tvPrivacyUrl = (TextView) dialogView.findViewById(R.id.tv_privacy_url);
Button btnAgree = (Button) dialogView.findViewById(R.id.btn_agree);
Button btnDisagree = (Button) dialogView.findViewById(R.id.btn_disagree);
// 4. 修复设置链接TextView可点击 + 蓝色下划线样式(模拟网页链接)
// 4.1 设置下划线文本
tvPrivacyUrl.setText(Html.fromHtml("<u>" + tvPrivacyUrl.getText().toString() + "</u>"));
// 4.2 设置点击事件,调用系统浏览器
tvPrivacyUrl.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String url = tvPrivacyUrl.getText().toString().trim();
openUrlInBrowser(activity, url);
}
});
// 4.3 设置点击反馈(可选,提升交互)
tvPrivacyUrl.setClickable(true);
tvPrivacyUrl.setFocusable(true);
// 5. 同意按钮点击事件(不变)
btnAgree.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// // 存储隐私协议状态(枚举+String兼容
// SharedPreferences sp = getPrivacySharedPreferences(activity);
// sp.edit()
// .putString(PRIVACY_VALUE, String.valueOf(PrivacyAgreeStatus.AGREED.getStatusCode()))
// .apply();
// // 初始化米盟SDK
// initMimoSdkStatic(activity.getApplicationContext());
// 回调外部监听
if (onPrivacyChangeListener != null) {
onPrivacyChangeListener.onAgreePrivacy();
}
dialog.dismiss();
}
});
// 6. 拒绝按钮点击事件(不变)
btnDisagree.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// // 存储隐私协议状态
// SharedPreferences sp = getPrivacySharedPreferences(activity);
// sp.edit()
// .putString(PRIVACY_VALUE, String.valueOf(PrivacyAgreeStatus.REJECTED.getStatusCode()))
// .apply();
// // 更新广告模式为单机模式
// ADsControlView.updateAdsModeByStatic(activity.getApplicationContext(), AdsMode.STANDALONE);
// 回调外部监听
if (onPrivacyChangeListener != null) {
onPrivacyChangeListener.onDisagreePrivacy();
}
dialog.dismiss();
}
});
// 7. 配置对话框样式(底部全屏,与原逻辑一致)
Window window = dialog.getWindow();
if (window != null) {
window.setGravity(Gravity.BOTTOM);
WindowManager m = activity.getWindowManager();
Display d = m.getDefaultDisplay();
WindowManager.LayoutParams p = window.getAttributes();
p.width = d.getWidth(); // 宽度全屏
window.setAttributes(p);
}
return dialog;
}
// 4. 不变调用系统默认浏览器打开链接Java 7 兼容)
private static void openUrlInBrowser(Context context, String url) {
if (context == null || TextUtils.isEmpty(url)) {
LogUtils.e(TAG, "openUrlInBrowser: Context or Url is null");
return;
}
try {
// 构建浏览器意图
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
// 确保有应用可处理该意图(避免崩溃)
if (browserIntent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(browserIntent);
} else {
Toast.makeText(context.getApplicationContext(), "未找到可用的浏览器", Toast.LENGTH_SHORT).show();
LogUtils.e(TAG, "openUrlInBrowser: No browser app found");
}
} catch (Exception e) {
LogUtils.e(TAG, "openUrlInBrowser: Open url failed", e);
Toast.makeText(context.getApplicationContext(), "打开链接失败,请手动复制链接浏览", Toast.LENGTH_SHORT).show();
}
}
}

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp"
android:background="@android:color/white">
<!-- 标题 -->
<TextView
android:id="@+id/tv_dialog_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户须知"
android:textSize="18sp"
android:textColor="@android:color/black"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<!-- 内容用LinearLayout横向排列文本+链接替代HTML <a>标签) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小米广告SDK隐私政策: "
android:textSize="14sp"
android:textColor="@android:color/darker_gray" />
<!-- 可点击链接用TextView承载通过代码设置蓝色+下划线) -->
<TextView
android:id="@+id/tv_privacy_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="https://dev.mi.com/distribute/doc/details?pId=1688"
android:textSize="14sp"
android:textColor="@android:color/holo_blue_light" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=",点击链接查看完整政策"
android:textSize="14sp"
android:textColor="@android:color/darker_gray" />
</LinearLayout>
<!-- 按钮容器(水平排列) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end">
<Button
android:id="@+id/btn_disagree"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拒绝"
android:textSize="14sp"
android:layout_marginRight="12dp"
android:paddingLeft="24dp"
android:paddingRight="24dp" />
<Button
android:id="@+id/btn_agree"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="同意"
android:textSize="14sp"
android:paddingLeft="24dp"
android:paddingRight="24dp" />
</LinearLayout>
</LinearLayout>