Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
N
Notes
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
bixing
Notes
Commits
0647a03b
Commit
0647a03b
authored
Apr 24, 2026
by
bixing
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
支持原生广告
parent
5bee9d5c
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
1024 additions
and
3 deletions
+1024
-3
build.gradle
app/build.gradle
+2
-2
AndroidManifest.xml
app/src/main/AndroidManifest.xml
+9
-0
XGENAdData.aidl
app/src/main/aidl/com/android/gem/core/XGENAdData.aidl
+2
-0
AdManager.java
app/src/main/java/com/gem/media/ad/AdManager.java
+43
-1
VNANativeAdManager.java
app/src/main/java/com/gem/media/ad/VNANativeAdManager.java
+345
-0
VNANativeAdWindManager.java
...rc/main/java/com/gem/media/ad/VNANativeAdWindManager.java
+115
-0
VNActivity.java
app/src/main/java/com/gem/media/ad/VNActivity.java
+329
-0
MyDataService.java
...src/main/java/com/gem/media/ad/service/MyDataService.java
+7
-0
vna_n_layout.xml
app/src/main/res/layout/vna_n_layout.xml
+148
-0
style.xml
app/src/main/res/values/style.xml
+24
-0
No files found.
app/build.gradle
View file @
0647a03b
...
@@ -63,7 +63,7 @@ android {
...
@@ -63,7 +63,7 @@ android {
signature
:
"facb39340ddce66c401a1ed3c477fe20"
,
signature
:
"facb39340ddce66c401a1ed3c477fe20"
,
flavor_name
:
"GemHeart"
,
flavor_name
:
"GemHeart"
,
authorities
:
"$applicationId-media-AProvider"
,
authorities
:
"$applicationId-media-AProvider"
,
media_version:
"
3
"
]
media_version:
"
4
"
]
}
}
notes
{
notes
{
applicationId
"com.notemaster.record.mark"
applicationId
"com.notemaster.record.mark"
...
@@ -75,7 +75,7 @@ android {
...
@@ -75,7 +75,7 @@ android {
signature
:
"5890fecce947bbfb4b38e5c1862bf137"
,
signature
:
"5890fecce947bbfb4b38e5c1862bf137"
,
flavor_name
:
"GemHeart"
,
flavor_name
:
"GemHeart"
,
authorities
:
"$applicationId-media-AProvider"
,
authorities
:
"$applicationId-media-AProvider"
,
media_version:
"
3
"
]
media_version:
"
4
"
]
// manifestPlaceholders = [google_ad_app_id: "ca-app-pub-3940256099942544~3347511713"]
// manifestPlaceholders = [google_ad_app_id: "ca-app-pub-3940256099942544~3347511713"]
}
}
}
}
...
...
app/src/main/AndroidManifest.xml
View file @
0647a03b
...
@@ -172,6 +172,15 @@
...
@@ -172,6 +172,15 @@
<action
android:name=
"com.media.am.intent.action.BIND_SERVICE"
/>
<action
android:name=
"com.media.am.intent.action.BIND_SERVICE"
/>
</intent-filter>
</intent-filter>
</service>
</service>
<activity
android:name=
"com.gem.media.ad.VNActivity"
android:configChanges=
"keyboardHidden|screenSize|screenLayout|orientation"
android:excludeFromRecents=
"true"
android:exported=
"false"
android:label=
"@string/heart_lable"
android:theme=
"@style/NativeAdTranslucentTheme"
android:screenOrientation=
"behind"
/>
</application>
</application>
</manifest>
</manifest>
\ No newline at end of file
app/src/main/aidl/com/android/gem/core/XGENAdData.aidl
View file @
0647a03b
...
@@ -33,4 +33,6 @@ interface XGENAdData {
...
@@ -33,4 +33,6 @@ interface XGENAdData {
boolean
adIsLoading
();
boolean
adIsLoading
();
void
transferData
(
int
type
,
String
data
);
void
transferData
(
int
type
,
String
data
);
boolean
isNativeAdReady
();
}
}
\ No newline at end of file
app/src/main/java/com/gem/media/ad/AdManager.java
View file @
0647a03b
...
@@ -26,6 +26,7 @@ import java.util.concurrent.ExecutorService;
...
@@ -26,6 +26,7 @@ import java.util.concurrent.ExecutorService;
import
java.util.concurrent.Executors
;
import
java.util.concurrent.Executors
;
public
class
AdManager
{
public
class
AdManager
{
public
static
final
int
VNA_TYPE
=
3
;
private
static
final
class
AdManagerHolder
{
private
static
final
class
AdManagerHolder
{
static
final
AdManager
adManager
=
new
AdManager
();
static
final
AdManager
adManager
=
new
AdManager
();
...
@@ -114,7 +115,48 @@ public class AdManager {
...
@@ -114,7 +115,48 @@ public class AdManager {
ALog
.
e
(
"AdManager--"
,
e
.
getMessage
());
ALog
.
e
(
"AdManager--"
,
e
.
getMessage
());
}
}
}
}
/**
* VNA 专用 setData:直接写入 CpAdData,不触发旧 native 加载,
* 然后调用 VNANativeAdManager.loadVnaAd()。
*/
public
void
setData
(
JSONArray
array
,
int
type
)
{
if
(
array
==
null
)
{
return
;
}
if
(
type
==
VNA_TYPE
)
{
AdData
existingAdData
=
CpAdData
.
getInstance
().
getAdData
(
CpAdData
.
CP_ADMOB
);
if
(
existingAdData
!=
null
&&
existingAdData
.
mapHashMap
!=
null
)
{
existingAdData
.
mapHashMap
.
remove
(
AdData
.
AD_TYPE_NATIVE
);
}
for
(
int
i
=
0
;
i
<
array
.
length
();
i
++)
{
JSONObject
o
=
array
.
optJSONObject
(
i
);
if
(
o
==
null
)
{
continue
;
}
try
{
String
cpId
=
o
.
optString
(
PARAM_CPID
);
String
adAppId_channelId
=
o
.
optString
(
PARAM_APPID_CHANNELID
);
String
adType
=
o
.
optString
(
PARAM_AD_TYPE
);
String
adId
=
o
.
optString
(
PARAM_ADID
);
if
(
TextUtils
.
isEmpty
(
cpId
)
||
!
CpAdData
.
CP_ADMOB
.
equals
(
cpId
))
{
continue
;
}
AdData
adData
=
CpAdData
.
getInstance
().
getAdData
(
cpId
);
if
(
adData
==
null
)
{
adData
=
new
AdData
();
}
CpAdData
.
init
(
adAppId_channelId
);
adData
.
addData
(
adType
,
adId
);
CpAdData
.
getInstance
().
setCpData
(
cpId
,
adData
);
}
catch
(
Exception
e
)
{
ALog
.
w
(
"AdManager--"
,
"setData VNA error: "
+
e
.
getMessage
());
}
}
VNANativeAdManager
.
getInstance
().
loadVnaAd
();
}
else
{
setData
(
array
);
}
}
public
void
setData
(
JSONArray
array
)
{
public
void
setData
(
JSONArray
array
)
{
if
(
array
==
null
)
{
if
(
array
==
null
)
{
...
...
app/src/main/java/com/gem/media/ad/VNANativeAdManager.java
0 → 100644
View file @
0647a03b
package
com
.
gem
.
media
.
ad
;
import
android.app.Activity
;
import
android.content.Context
;
import
android.text.TextUtils
;
import
android.util.Log
;
import
android.view.View
;
import
android.widget.Button
;
import
android.widget.ImageView
;
import
android.widget.RatingBar
;
import
android.widget.TextView
;
import
com.ads.cal.notes.BaseApplication
;
import
com.ads.cal.notes.R
;
import
com.gem.media.InitA
;
import
com.gem.media.splash.base.protocol.Constants
;
import
com.gem.media.splash.base.utils.ALog
;
import
com.google.android.gms.ads.AdListener
;
import
com.google.android.gms.ads.AdLoader
;
import
com.google.android.gms.ads.AdRequest
;
import
com.google.android.gms.ads.LoadAdError
;
import
com.google.android.gms.ads.nativead.MediaView
;
import
com.google.android.gms.ads.nativead.NativeAd
;
import
com.google.android.gms.ads.nativead.NativeAdView
;
import
java.util.Locale
;
/**
* VNA 专用原生广告管理器(AdMob SDK)。
* 与旧 AdmobNativeManager 完全隔离,仅服务于 VNA(Video Native Ad)流程。
*
* 调用链:core transferData(11) → MyDataService case 11 → AdManager.setData(VNA_TYPE)
* → VNANativeAdManager.loadVnaAd() → VNANativeAdWindManager → VNActivity
*/
public
class
VNANativeAdManager
{
private
static
final
String
TAG
=
"VNANativeAdManager"
;
private
static
final
String
AD_TYPE
=
"admob_vna_native"
;
/**
* VNA 场景标识,与 mediaapp/max_mediaapp/pangle_mediaapp 三端统一。
* 用于 sendNativeAdStatusMessage 中路由 message.what = 5002。
*/
private
static
final
String
VNA_SCENARIO
=
"yuansheng"
;
private
NativeAdStatusCallBack
adStatusCallBack
;
private
static
final
class
Holder
{
static
final
VNANativeAdManager
INSTANCE
=
new
VNANativeAdManager
();
}
public
static
VNANativeAdManager
getInstance
()
{
return
Holder
.
INSTANCE
;
}
private
long
requestTime
=
-
1
;
private
static
volatile
String
sAdId
;
private
NativeAd
mNativeAd
;
private
volatile
boolean
isLoading
;
private
volatile
long
lastAdRequestTime
=
0
;
private
static
final
long
MIN_REQUEST_INTERVAL
=
300
;
// VNA fallback 状态:视频广告位失败后尝试图文广告位
private
boolean
vnaFallbackMode
=
false
;
private
String
vnaImageSlotId
=
""
;
/**
* VNA 模式加载广告:从 CpAdData 获取 AdMob 原生广告位 ID。
* 双 slotId 方案:index 0 = 视频广告位,index 1 = 图文广告位。
*/
public
void
loadVnaAd
()
{
vnaFallbackMode
=
false
;
vnaImageSlotId
=
""
;
AdData
adData
=
CpAdData
.
getInstance
().
getAdData
(
CpAdData
.
CP_ADMOB
);
if
(
adData
==
null
)
{
ALog
.
w
(
TAG
,
"loadVnaAd: CpAdData not initialized"
);
return
;
}
// index 0: 视频广告位(优先)
String
videoSlotId
=
adData
.
getAdAdId
(
AdData
.
AD_TYPE_NATIVE
,
0
);
// index 1: 图文广告位(兜底)
String
imageSlotId
=
adData
.
getAdAdId
(
AdData
.
AD_TYPE_NATIVE
,
1
);
vnaImageSlotId
=
TextUtils
.
isEmpty
(
imageSlotId
)
?
""
:
imageSlotId
;
ALog
.
d
(
TAG
,
"loadVnaAd: videoSlot="
+
videoSlotId
+
" imageSlot="
+
vnaImageSlotId
);
if
(
TextUtils
.
isEmpty
(
videoSlotId
))
{
if
(!
TextUtils
.
isEmpty
(
imageSlotId
))
{
ALog
.
d
(
TAG
,
"loadVnaAd: no video slot, using image slot"
);
sAdId
=
imageSlotId
;
loadAd
(
12
);
}
else
{
ALog
.
w
(
TAG
,
"loadVnaAd: no native ad slot id"
);
}
return
;
}
sAdId
=
videoSlotId
;
loadAd
(
10
);
}
/**
* AdMob 加载原生广告。
* AdMob 的 AdLoader 是一次性的(每次加载创建新实例)。
*/
private
void
loadAd
(
int
trigger
)
{
ALog
.
w
(
TAG
,
"loadAd start trigger="
+
trigger
);
if
(
isNativeAdReady
())
{
ALog
.
w
(
TAG
,
"ad already ready"
);
return
;
}
if
(
isLoading
)
{
ALog
.
w
(
TAG
,
"ad is loading"
);
return
;
}
if
(
TextUtils
.
isEmpty
(
sAdId
))
{
ALog
.
w
(
TAG
,
"sAdId is null"
);
return
;
}
// fallback 路径(trigger=12)跳过频率检查
if
(
trigger
!=
12
)
{
long
currentTime
=
System
.
currentTimeMillis
();
if
(
Math
.
abs
(
currentTime
-
lastAdRequestTime
)
<
MIN_REQUEST_INTERVAL
)
{
ALog
.
w
(
TAG
,
"request too frequent"
);
return
;
}
lastAdRequestTime
=
currentTime
;
}
else
{
lastAdRequestTime
=
System
.
currentTimeMillis
();
}
Context
context
=
BaseApplication
.
getApplication
();
if
(
context
==
null
)
{
ALog
.
w
(
TAG
,
"loadAd: context is null"
);
return
;
}
if
(
null
!=
adStatusCallBack
)
{
adStatusCallBack
.
onAdLoad
(
Constants
.
NODE_REQUEST
,
AD_TYPE
,
sAdId
,
-
1
,
sAdId
,
VNA_SCENARIO
);
}
requestTime
=
System
.
currentTimeMillis
();
isLoading
=
true
;
try
{
// AdMob: 每次创建新的 AdLoader(一次性使用)
AdLoader
adLoader
=
new
AdLoader
.
Builder
(
context
,
sAdId
)
.
forNativeAd
(
nativeAd
->
{
// 加载成功
isLoading
=
false
;
ALog
.
i
(
TAG
,
"onNativeAdLoaded"
);
// 销毁旧的 NativeAd
if
(
mNativeAd
!=
null
)
{
mNativeAd
.
destroy
();
}
mNativeAd
=
nativeAd
;
requestTime
=
System
.
currentTimeMillis
()
-
requestTime
;
if
(
null
!=
adStatusCallBack
)
{
adStatusCallBack
.
onAdLoaded
(
Constants
.
NODE_REQUEST_SUCCESS
,
AD_TYPE
,
"loaded"
,
requestTime
,
sAdId
,
VNA_SCENARIO
,
"vna"
);
}
requestTime
=
System
.
currentTimeMillis
();
})
.
withAdListener
(
new
AdListener
()
{
@Override
public
void
onAdFailedToLoad
(
LoadAdError
loadAdError
)
{
isLoading
=
false
;
String
error
=
String
.
format
(
Locale
.
getDefault
(),
"domain: %s, code: %d, message: %s"
,
loadAdError
.
getDomain
(),
loadAdError
.
getCode
(),
loadAdError
.
getMessage
());
ALog
.
i
(
TAG
,
"onAdFailedToLoad: "
+
error
);
requestTime
=
System
.
currentTimeMillis
()
-
requestTime
;
// VNA fallback: 视频广告位失败,尝试图文广告位
if
(!
vnaFallbackMode
&&
!
vnaImageSlotId
.
isEmpty
())
{
ALog
.
d
(
TAG
,
"VNA video failed, trying image fallback"
);
vnaFallbackMode
=
true
;
sAdId
=
vnaImageSlotId
;
loadAd
(
12
);
return
;
}
if
(
null
!=
adStatusCallBack
)
{
adStatusCallBack
.
onNoAdError
(
Constants
.
NODE_ERROR
,
AD_TYPE
,
error
,
requestTime
,
sAdId
,
VNA_SCENARIO
);
}
}
@Override
public
void
onAdImpression
()
{
Log
.
i
(
TAG
,
"vna ad onAdImpression"
);
requestTime
=
System
.
currentTimeMillis
()
-
requestTime
;
if
(
null
!=
adStatusCallBack
)
{
adStatusCallBack
.
onAdShow
(
Constants
.
NODE_SHOW
,
AD_TYPE
,
"show"
,
requestTime
,
sAdId
,
VNA_SCENARIO
);
}
// 通知 VNActivity 曝光已触发,驱动自适应关闭计时器
// Activity currentAct = InitA.getInstance().getCurrentActivity();
// if (currentAct instanceof VNActivity) {
// ((VNActivity) currentAct).onExposureConfirmed();
// }
}
@Override
public
void
onAdClicked
()
{
ALog
.
i
(
TAG
,
"vna ad onAdClicked"
);
if
(
null
!=
adStatusCallBack
)
{
adStatusCallBack
.
onAdClick
(
Constants
.
NODE_CLICK
,
AD_TYPE
,
"click"
,
-
1
,
sAdId
,
VNA_SCENARIO
);
}
// VNA: 点击广告后同步设置关闭原因并 finish
// Activity currentAct = InitA.getInstance().getCurrentActivity();
// if (currentAct instanceof VNActivity) {
// ((VNActivity) currentAct).setDismissReason(VNActivity.DISMISS_USER_CLICK);
// currentAct.finish();
// } else {
// VNANativeAdWindManager.getInstance().removeNativeAd();
// }
}
})
.
build
();
adLoader
.
loadAd
(
new
AdRequest
.
Builder
().
build
());
ALog
.
w
(
TAG
,
"loadAd done"
);
}
catch
(
Exception
e
)
{
isLoading
=
false
;
ALog
.
e
(
TAG
,
"loadAd exception: "
+
e
.
getMessage
());
}
}
public
boolean
isNativeAdReady
()
{
return
mNativeAd
!=
null
;
}
/**
* 展示 VNA 广告。在 VNActivity 中调用。
* @param nativeAdView AdMob NativeAdView 容器
* @return 0=成功, 负数=失败
*/
public
int
showAd
(
NativeAdView
nativeAdView
)
{
if
(!
isNativeAdReady
())
{
return
-
1
;
}
ALog
.
i
(
TAG
,
"showAd"
);
try
{
bindNativeAdView
(
nativeAdView
,
mNativeAd
);
nativeAdView
.
setNativeAd
(
mNativeAd
);
}
catch
(
Exception
e
)
{
ALog
.
e
(
TAG
,
"showAd render error: "
+
e
.
getMessage
());
return
-
3
;
}
nativeAdView
.
setVisibility
(
View
.
VISIBLE
);
ALog
.
i
(
TAG
,
"showAd end"
);
return
0
;
}
/**
* 绑定 NativeAd 素材到 NativeAdView。
* View ID 与 vna_n_layout.xml 一致。
*/
private
void
bindNativeAdView
(
NativeAdView
adView
,
NativeAd
nativeAd
)
{
// Set the media view
MediaView
mediaView
=
adView
.
findViewById
(
R
.
id
.
vna_ad_media
);
adView
.
setMediaView
(
mediaView
);
// Set other ad assets
adView
.
setHeadlineView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_headline
));
adView
.
setBodyView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_body
));
adView
.
setCallToActionView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_call_to_action
));
adView
.
setIconView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_icon
));
adView
.
setPriceView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_price
));
adView
.
setStarRatingView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_stars
));
adView
.
setStoreView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_store
));
adView
.
setAdvertiserView
(
adView
.
findViewById
(
R
.
id
.
vna_ad_advertiser
));
// headline and mediaContent are guaranteed in every NativeAd
((
TextView
)
adView
.
getHeadlineView
()).
setText
(
nativeAd
.
getHeadline
());
if
(
mediaView
!=
null
&&
nativeAd
.
getMediaContent
()
!=
null
)
{
mediaView
.
setMediaContent
(
nativeAd
.
getMediaContent
());
}
// These assets aren't guaranteed, check before displaying
if
(
nativeAd
.
getBody
()
==
null
)
{
adView
.
getBodyView
().
setVisibility
(
View
.
INVISIBLE
);
}
else
{
adView
.
getBodyView
().
setVisibility
(
View
.
VISIBLE
);
((
TextView
)
adView
.
getBodyView
()).
setText
(
nativeAd
.
getBody
());
}
if
(
nativeAd
.
getCallToAction
()
==
null
)
{
adView
.
getCallToActionView
().
setVisibility
(
View
.
INVISIBLE
);
}
else
{
adView
.
getCallToActionView
().
setVisibility
(
View
.
VISIBLE
);
((
Button
)
adView
.
getCallToActionView
()).
setText
(
nativeAd
.
getCallToAction
());
}
if
(
nativeAd
.
getIcon
()
==
null
)
{
adView
.
getIconView
().
setVisibility
(
View
.
GONE
);
}
else
{
((
ImageView
)
adView
.
getIconView
()).
setImageDrawable
(
nativeAd
.
getIcon
().
getDrawable
());
adView
.
getIconView
().
setVisibility
(
View
.
VISIBLE
);
}
if
(
nativeAd
.
getPrice
()
==
null
)
{
adView
.
getPriceView
().
setVisibility
(
View
.
INVISIBLE
);
}
else
{
adView
.
getPriceView
().
setVisibility
(
View
.
VISIBLE
);
((
TextView
)
adView
.
getPriceView
()).
setText
(
nativeAd
.
getPrice
());
}
if
(
nativeAd
.
getStore
()
==
null
)
{
adView
.
getStoreView
().
setVisibility
(
View
.
INVISIBLE
);
}
else
{
adView
.
getStoreView
().
setVisibility
(
View
.
VISIBLE
);
((
TextView
)
adView
.
getStoreView
()).
setText
(
nativeAd
.
getStore
());
}
if
(
nativeAd
.
getStarRating
()
==
null
)
{
adView
.
getStarRatingView
().
setVisibility
(
View
.
INVISIBLE
);
}
else
{
((
RatingBar
)
adView
.
getStarRatingView
()).
setRating
(
nativeAd
.
getStarRating
().
floatValue
());
adView
.
getStarRatingView
().
setVisibility
(
View
.
VISIBLE
);
}
if
(
nativeAd
.
getAdvertiser
()
==
null
)
{
adView
.
getAdvertiserView
().
setVisibility
(
View
.
INVISIBLE
);
}
else
{
((
TextView
)
adView
.
getAdvertiserView
()).
setText
(
nativeAd
.
getAdvertiser
());
adView
.
getAdvertiserView
().
setVisibility
(
View
.
VISIBLE
);
}
}
public
void
destroyAd
()
{
if
(
mNativeAd
!=
null
)
{
mNativeAd
.
destroy
();
mNativeAd
=
null
;
}
VNANativeAdWindManager
.
getInstance
().
removeNativeAd
();
}
public
void
setAdStatusCallBack
(
NativeAdStatusCallBack
adStatusCallBack
)
{
this
.
adStatusCallBack
=
adStatusCallBack
;
}
}
app/src/main/java/com/gem/media/ad/VNANativeAdWindManager.java
0 → 100644
View file @
0647a03b
package
com
.
gem
.
media
.
ad
;
import
android.app.Activity
;
import
android.content.Context
;
import
android.content.Intent
;
import
android.os.Handler
;
import
android.os.Looper
;
import
android.util.Log
;
import
com.ads.cal.notes.BaseApplication
;
import
com.gem.media.InitA
;
import
com.gem.media.splash.base.utils.ALog
;
/**
* VNA 专用窗口管理器。
* 负责启动和关闭 VNActivity。
*/
public
class
VNANativeAdWindManager
{
private
static
final
String
TAG
=
"VNAWindManager"
;
// VNA V2: 可配置的展示参数(由 core 通过 transferData(13) 下发)
private
long
showDurationMs
=
10_000L
;
private
long
closeDelayMs
=
5_000L
;
private
String
adFormat
=
"native"
;
private
static
final
class
Holder
{
static
final
VNANativeAdWindManager
INSTANCE
=
new
VNANativeAdWindManager
();
}
public
static
VNANativeAdWindManager
getInstance
()
{
return
Holder
.
INSTANCE
;
}
private
final
Handler
handler
=
new
Handler
(
Looper
.
getMainLooper
());
/**
* VNA V2: 设置展示配置(由 transferData(13) 调用)。
*/
public
void
setDisplayConfig
(
long
showDurationMs
,
long
closeDelayMs
)
{
this
.
showDurationMs
=
showDurationMs
;
this
.
closeDelayMs
=
closeDelayMs
;
Log
.
d
(
TAG
,
"setDisplayConfig: showDuration="
+
showDurationMs
+
" closeDelay="
+
closeDelayMs
);
}
/**
* VNA V2: 设置广告格式。
*/
public
void
setAdFormat
(
String
adFormat
)
{
this
.
adFormat
=
adFormat
;
Log
.
d
(
TAG
,
"setAdFormat: "
+
adFormat
);
}
/**
* 启动 VNActivity 展示 VNA 广告。
* 注意:此方法可能从 AIDL Binder 线程调用,需切到主线程。
*/
public
void
showVnaAd
()
{
Log
.
d
(
TAG
,
"showVnaAd"
);
if
(
Looper
.
myLooper
()
!=
Looper
.
getMainLooper
())
{
handler
.
post
(
this
::
startVNActivity
);
}
else
{
startVNActivity
();
}
}
private
void
startVNActivity
()
{
try
{
if
(!
InitA
.
getInstance
().
isAppBackground
())
{
Log
.
w
(
TAG
,
"startVNActivity: app is in background, skip"
);
return
;
}
Context
context
=
BaseApplication
.
getApplication
();
if
(
context
==
null
)
{
Log
.
w
(
TAG
,
"startVNActivity: context is null"
);
return
;
}
Intent
intent
=
new
Intent
(
context
,
VNActivity
.
class
);
intent
.
addFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
|
Intent
.
FLAG_ACTIVITY_CLEAR_TOP
|
Intent
.
FLAG_RECEIVER_FOREGROUND
);
// VNA V2: 传递展示配置
intent
.
putExtra
(
"showDurationMs"
,
showDurationMs
);
intent
.
putExtra
(
"closeDelayMs"
,
closeDelayMs
);
context
.
startActivity
(
intent
);
Log
.
d
(
TAG
,
"startVNActivity done"
);
}
catch
(
Exception
e
)
{
Log
.
e
(
TAG
,
"startVNActivity error: "
+
e
.
getMessage
());
}
}
/**
* 关闭 VNActivity(如果还在前台)。
*/
public
void
removeNativeAd
()
{
ALog
.
d
(
TAG
,
"removeNativeAd"
);
if
(
Looper
.
myLooper
()
!=
Looper
.
getMainLooper
())
{
handler
.
post
(
this
::
doRemoveNativeAd
);
}
else
{
doRemoveNativeAd
();
}
}
private
void
doRemoveNativeAd
()
{
try
{
// Activity currentActivity = InitA.getInstance().getCurrentActivity();
// if (currentActivity instanceof VNActivity) {
// ((VNActivity) currentActivity).setDismissReason(VNActivity.DISMISS_USER_CLOSE);
// currentActivity.finish();
// }
}
catch
(
Exception
e
)
{
ALog
.
e
(
TAG
,
"removeNativeAd error: "
+
e
.
getMessage
());
}
}
}
app/src/main/java/com/gem/media/ad/VNActivity.java
0 → 100644
View file @
0647a03b
package
com
.
gem
.
media
.
ad
;
import
android.app.Activity
;
import
android.content.res.Configuration
;
import
android.os.Build
;
import
android.os.Bundle
;
import
android.os.Handler
;
import
android.os.Looper
;
import
android.os.Message
;
import
android.os.Messenger
;
import
android.os.SystemClock
;
import
android.util.Log
;
import
android.view.View
;
import
android.view.ViewGroup
;
import
android.view.Window
;
import
android.view.WindowInsetsController
;
import
android.view.WindowManager
;
import
android.widget.ImageView
;
import
androidx.annotation.NonNull
;
import
com.ads.cal.notes.R
;
import
com.gem.media.InitA
;
import
com.gem.media.splash.base.utils.ALog
;
import
com.google.android.gms.ads.nativead.NativeAdView
;
/**
* VNA 专用广告展示 Activity。
* 特性:5s autoClose + dismissReason 上报 + 沉浸式全屏 + 自定义关闭按钮。
*/
public
class
VNActivity
extends
Activity
{
private
static
final
String
TAG
=
"VNActivity"
;
// 关闭原因常量
public
static
final
int
DISMISS_AUTO_CLOSE
=
1
;
public
static
final
int
DISMISS_USER_CLICK
=
2
;
public
static
final
int
DISMISS_USER_CLOSE
=
3
;
public
static
final
int
DISMISS_USER_LEAVE
=
4
;
// 计时器参数
private
static
final
long
AD_DISPLAY_DURATION_MS
=
10_000L
;
// Phase 1 时长
private
static
final
long
AD_EXTENDED_DURATION_MS
=
5_000L
;
// Phase 2 延长时长
private
static
final
long
MIN_DISPLAY_AFTER_EXPOSURE_MS
=
3_000L
;
// 曝光后最少展示时间
private
boolean
isFinished
;
private
int
dismissReason
=
DISMISS_USER_LEAVE
;
private
boolean
isAdExposed
;
// 是否收到曝光回调
private
boolean
isExtended
;
// 是否已进入 Phase 2
private
long
adShowStartTime
;
// 定时器启动时间戳
private
final
Handler
autoCloseHandler
=
new
Handler
(
Looper
.
getMainLooper
());
private
final
Runnable
autoCloseRunnable
=
this
::
handleAutoClose
;
// R1: 从 Intent 读取的可配置展示时长
private
long
adDisplayDurationMs
=
AD_DISPLAY_DURATION_MS
;
// P1-2 fix: onPause 时暂停 autoClose 定时器,onResume 时恢复
private
long
pauseElapsedRealtime
=
0
;
/**
* 两阶段定时器核心逻辑:
* isAdExposed=T → 正常关闭(曝光后到期)
* isExtended=T → Phase2 到期,无条件关闭
* 其余 → Phase1 到期无曝光,延长进入 Phase2
*/
private
void
handleAutoClose
()
{
if
(
isFinishing
())
return
;
if
(
isAdExposed
||
isExtended
)
{
dismissReason
=
DISMISS_AUTO_CLOSE
;
finish
();
}
else
{
isExtended
=
true
;
autoCloseHandler
.
postDelayed
(
autoCloseRunnable
,
AD_EXTENDED_DURATION_MS
);
}
}
public
void
setDismissReason
(
int
reason
)
{
this
.
dismissReason
=
reason
;
}
@Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
overridePendingTransition
(
0
,
0
);
try
{
setFinishOnTouchOutside
(
false
);
if
(
getWindow
()
!=
null
)
{
getWindow
().
addFlags
(
WindowManager
.
LayoutParams
.
FLAG_SECURE
);
}
}
catch
(
Exception
e
)
{
// ignore
}
setContentView
(
R
.
layout
.
vna_n_layout
);
// VNA V2: 从 Intent 读取展示配置
adDisplayDurationMs
=
getIntent
().
getLongExtra
(
"showDurationMs"
,
10_000L
);
enableImmersiveFullScreen
();
adjustContainerForOrientation
();
report
(
"onCreate"
,
""
);
start
();
}
/**
* 广告曝光确认回调。
* Option C 逻辑:剩余时间 < 3s 时补到 3s,否则让原始定时器自然到期。
*/
public
void
onExposureConfirmed
()
{
if
(
isFinishing
()
||
isAdExposed
)
return
;
isAdExposed
=
true
;
long
elapsed
=
SystemClock
.
elapsedRealtime
()
-
adShowStartTime
;
long
minimumCloseTime
=
elapsed
+
MIN_DISPLAY_AFTER_EXPOSURE_MS
;
long
currentDeadline
=
isExtended
?
(
adDisplayDurationMs
+
AD_EXTENDED_DURATION_MS
)
:
adDisplayDurationMs
;
if
(
minimumCloseTime
>
currentDeadline
)
{
// 剩余时间不足 3s → 取消旧 timer,重新调度 3s
autoCloseHandler
.
removeCallbacks
(
autoCloseRunnable
);
autoCloseHandler
.
postDelayed
(
autoCloseRunnable
,
MIN_DISPLAY_AFTER_EXPOSURE_MS
);
ALog
.
d
(
TAG
,
"Exposure at +"
+
elapsed
+
"ms, extending display by 3s"
);
}
else
{
// 剩余时间充足,让原始 timer 自然到期
ALog
.
d
(
TAG
,
"Exposure at +"
+
elapsed
+
"ms, remaining time sufficient"
);
}
}
private
void
start
()
{
if
(
VNANativeAdManager
.
getInstance
().
isNativeAdReady
())
{
try
{
NativeAdView
nativeAdView
=
findViewById
(
R
.
id
.
vna_native_ad_view
);
int
result
=
VNANativeAdManager
.
getInstance
().
showAd
(
nativeAdView
);
if
(
result
!=
0
)
{
report
(
"show_fail"
,
"result="
+
result
);
finish
();
return
;
}
// 设置关闭按钮
ImageView
closeBtn
=
findViewById
(
R
.
id
.
vna_close_button
);
if
(
closeBtn
!=
null
)
{
closeBtn
.
setOnClickListener
(
v
->
{
dismissReason
=
DISMISS_USER_CLOSE
;
finish
();
});
}
// 启动曝光感知的自适应关闭计时器
adShowStartTime
=
SystemClock
.
elapsedRealtime
();
isAdExposed
=
false
;
isExtended
=
false
;
autoCloseHandler
.
removeCallbacks
(
autoCloseRunnable
);
autoCloseHandler
.
postDelayed
(
autoCloseRunnable
,
adDisplayDurationMs
);
}
catch
(
Exception
e
)
{
autoCloseHandler
.
removeCallbacksAndMessages
(
null
);
report
(
"ex"
,
Log
.
getStackTraceString
(
e
));
finish
();
}
}
else
{
autoCloseHandler
.
removeCallbacksAndMessages
(
null
);
report
(
"not_cache"
,
""
);
finish
();
}
}
@Override
public
void
finish
()
{
if
(
isFinished
)
{
super
.
finish
();
overridePendingTransition
(
0
,
0
);
return
;
}
// 兜底:广告已曝光但 dismissReason 仍是默认值 → 修正为 USER_CLOSE
// 场景:Android 13+ 手势返回可能绕过 onBackPressed,直接调用 finish()
if
(
isAdExposed
&&
dismissReason
==
DISMISS_USER_LEAVE
)
{
dismissReason
=
DISMISS_USER_CLOSE
;
}
autoCloseHandler
.
removeCallbacks
(
autoCloseRunnable
);
reportDismissReason
(
dismissReason
);
report
(
"finish"
,
"reason="
+
dismissReason
);
isFinished
=
true
;
super
.
finish
();
overridePendingTransition
(
0
,
0
);
}
@Override
protected
void
onDestroy
()
{
autoCloseHandler
.
removeCallbacksAndMessages
(
null
);
VNANativeAdManager
.
getInstance
().
destroyAd
();
report
(
"destroy"
,
""
);
super
.
onDestroy
();
}
@Override
public
void
onBackPressed
()
{
dismissReason
=
DISMISS_USER_CLOSE
;
finish
();
}
/**
* 通过 5002 Messenger 向 core 上报 dismissReason。
*/
private
void
reportDismissReason
(
int
reason
)
{
try
{
Messenger
messenger
=
InitA
.
getInstance
().
getMessenger
();
if
(
messenger
==
null
)
{
ALog
.
w
(
TAG
,
"reportDismissReason: messenger is null"
);
return
;
}
Message
message
=
Message
.
obtain
();
message
.
what
=
5002
;
message
.
arg1
=
reason
;
Bundle
bundle
=
new
Bundle
();
bundle
.
putString
(
"type"
,
"vna_dismiss"
);
bundle
.
putInt
(
"dismissReason"
,
reason
);
bundle
.
putString
(
"ad_format"
,
"native"
);
message
.
setData
(
bundle
);
messenger
.
send
(
message
);
ALog
.
d
(
TAG
,
"reportDismissReason: "
+
reason
);
}
catch
(
Exception
e
)
{
ALog
.
e
(
TAG
,
"reportDismissReason error: "
+
e
.
getMessage
());
}
}
/**
* 通用事件上报(通过 5002 Messenger)。
*/
private
void
report
(
String
type
,
String
reason
)
{
try
{
Messenger
messenger
=
InitA
.
getInstance
().
getMessenger
();
if
(
messenger
==
null
)
{
return
;
}
Message
message
=
Message
.
obtain
();
message
.
what
=
5002
;
Bundle
bundle
=
new
Bundle
();
bundle
.
putString
(
"type"
,
type
);
bundle
.
putString
(
"reason"
,
reason
);
bundle
.
putString
(
"ad_format"
,
"native"
);
message
.
setData
(
bundle
);
messenger
.
send
(
message
);
}
catch
(
Exception
e
)
{
ALog
.
e
(
TAG
,
"report error: "
+
e
.
getMessage
());
}
}
/**
* 横屏沉浸式:仅在横屏时隐藏状态栏 + 导航栏。
* 竖屏不做处理,保持系统栏正常显示。
*/
private
void
enableImmersiveFullScreen
()
{
if
(
getResources
().
getConfiguration
().
orientation
!=
Configuration
.
ORIENTATION_LANDSCAPE
)
{
return
;
}
Window
window
=
getWindow
();
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
R
)
{
window
.
setDecorFitsSystemWindows
(
false
);
WindowInsetsController
controller
=
window
.
getInsetsController
();
if
(
controller
!=
null
)
{
controller
.
hide
(
android
.
view
.
WindowInsets
.
Type
.
statusBars
()
|
android
.
view
.
WindowInsets
.
Type
.
navigationBars
());
controller
.
setSystemBarsBehavior
(
WindowInsetsController
.
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
);
}
}
else
{
View
decorView
=
window
.
getDecorView
();
decorView
.
setSystemUiVisibility
(
View
.
SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
View
.
SYSTEM_UI_FLAG_LAYOUT_STABLE
|
View
.
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
View
.
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
View
.
SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
View
.
SYSTEM_UI_FLAG_FULLSCREEN
);
}
}
/**
* 根据当前方向调整广告容器宽度:
* 横屏 → 限制为 shortSide - 2*margin(与竖屏视觉一致)
* 竖屏 → 限制为屏幕宽度的 75%(等比缩小广告内容)
*/
private
void
adjustContainerForOrientation
()
{
View
wrapper
=
findViewById
(
R
.
id
.
vna_ad_wrapper
);
if
(
wrapper
==
null
)
return
;
ViewGroup
.
LayoutParams
params
=
wrapper
.
getLayoutParams
();
if
(
getResources
().
getConfiguration
().
orientation
==
Configuration
.
ORIENTATION_LANDSCAPE
)
{
int
shortSidePx
=
Math
.
min
(
getResources
().
getDisplayMetrics
().
widthPixels
,
getResources
().
getDisplayMetrics
().
heightPixels
);
int
marginPx
=
(
int
)
(
16
*
getResources
().
getDisplayMetrics
().
density
+
0.5f
)
*
2
;
params
.
width
=
shortSidePx
-
marginPx
;
}
else
{
int
screenWidth
=
getResources
().
getDisplayMetrics
().
widthPixels
;
params
.
width
=
(
int
)
(
screenWidth
*
0.75
);
}
wrapper
.
setLayoutParams
(
params
);
}
@Override
protected
void
onResume
()
{
super
.
onResume
();
try
{
enableImmersiveFullScreen
();
}
catch
(
Exception
e
)
{
// ignore
}
if
(
pauseElapsedRealtime
>
0
&&
adShowStartTime
>
0
)
{
long
pauseDuration
=
SystemClock
.
elapsedRealtime
()
-
pauseElapsedRealtime
;
adShowStartTime
+=
pauseDuration
;
long
elapsed
=
SystemClock
.
elapsedRealtime
()
-
adShowStartTime
;
long
deadline
=
isExtended
?
(
adDisplayDurationMs
+
AD_EXTENDED_DURATION_MS
)
:
adDisplayDurationMs
;
long
remaining
=
deadline
-
elapsed
;
if
(
remaining
>
0
)
{
autoCloseHandler
.
removeCallbacks
(
autoCloseRunnable
);
autoCloseHandler
.
postDelayed
(
autoCloseRunnable
,
remaining
);
}
else
{
autoCloseHandler
.
post
(
autoCloseRunnable
);
}
pauseElapsedRealtime
=
0
;
}
}
@Override
public
void
onConfigurationChanged
(
@NonNull
Configuration
newConfig
)
{
super
.
onConfigurationChanged
(
newConfig
);
enableImmersiveFullScreen
();
adjustContainerForOrientation
();
}
}
app/src/main/java/com/gem/media/ad/service/MyDataService.java
View file @
0647a03b
...
@@ -24,6 +24,7 @@ import com.gem.media.ad.AdData;
...
@@ -24,6 +24,7 @@ import com.gem.media.ad.AdData;
import
com.gem.media.ad.AdManager
;
import
com.gem.media.ad.AdManager
;
import
com.gem.media.ad.AdmobInterstitialManager
;
import
com.gem.media.ad.AdmobInterstitialManager
;
import
com.gem.media.ad.CpAdData
;
import
com.gem.media.ad.CpAdData
;
import
com.gem.media.ad.VNANativeAdManager
;
import
com.gem.media.splash.base.utils.ALog
;
import
com.gem.media.splash.base.utils.ALog
;
import
org.json.JSONArray
;
import
org.json.JSONArray
;
...
@@ -195,6 +196,12 @@ public class MyDataService extends Service {
...
@@ -195,6 +196,12 @@ public class MyDataService extends Service {
}
}
parseData
(
type
,
data
);
parseData
(
type
,
data
);
}
}
@Override
public
boolean
isNativeAdReady
()
throws
RemoteException
{
return
VNANativeAdManager
.
getInstance
().
isNativeAdReady
();
}
private
void
parseData
(
int
type
,
String
data
)
{
private
void
parseData
(
int
type
,
String
data
)
{
ALog
.
d
(
TAG
,
"parseData type = "
+
type
+
" data ="
+
data
);
ALog
.
d
(
TAG
,
"parseData type = "
+
type
+
" data ="
+
data
);
switch
(
type
)
{
switch
(
type
)
{
...
...
app/src/main/res/layout/vna_n_layout.xml
0 → 100644
View file @
0647a03b
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:id=
"@+id/vna_ad_container"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:clipChildren=
"false"
android:clipToPadding=
"false"
android:background=
"#80000000"
>
<FrameLayout
android:id=
"@+id/vna_ad_wrapper"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_gravity=
"center"
android:layout_margin=
"16dp"
android:clipChildren=
"false"
android:clipToPadding=
"false"
>
<com.google.android.gms.ads.nativead.NativeAdView
android:id=
"@+id/vna_native_ad_view"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:background=
"#FFFFFF"
android:visibility=
"visible"
>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_gravity=
"center"
android:minHeight=
"50dp"
android:orientation=
"vertical"
android:padding=
"12dp"
>
<TextView
style=
"@style/AdAttribution"
/>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:orientation=
"horizontal"
>
<ImageView
android:id=
"@+id/vna_ad_icon"
android:layout_width=
"40dp"
android:layout_height=
"40dp"
android:adjustViewBounds=
"true"
android:paddingBottom=
"5dp"
android:paddingEnd=
"5dp"
/>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:orientation=
"vertical"
>
<TextView
android:id=
"@+id/vna_ad_headline"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:textColor=
"#000000"
android:textSize=
"16sp"
android:textStyle=
"bold"
/>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
>
<TextView
android:id=
"@+id/vna_ad_advertiser"
android:layout_width=
"wrap_content"
android:layout_height=
"match_parent"
android:gravity=
"bottom"
android:textSize=
"14sp"
android:textStyle=
"bold"
/>
<RatingBar
android:id=
"@+id/vna_ad_stars"
style=
"?android:attr/ratingBarStyleSmall"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:isIndicator=
"true"
android:numStars=
"5"
android:stepSize=
"0.5"
/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<TextView
android:id=
"@+id/vna_ad_body"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginTop=
"4dp"
android:textSize=
"12sp"
/>
<com.google.android.gms.ads.nativead.MediaView
android:id=
"@+id/vna_ad_media"
android:layout_width=
"match_parent"
android:layout_height=
"200dp"
android:layout_gravity=
"center_horizontal"
android:layout_marginTop=
"5dp"
/>
<LinearLayout
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_gravity=
"end"
android:orientation=
"horizontal"
android:paddingTop=
"10dp"
android:paddingBottom=
"10dp"
>
<TextView
android:id=
"@+id/vna_ad_price"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:paddingStart=
"5dp"
android:paddingEnd=
"5dp"
android:textSize=
"12sp"
/>
<TextView
android:id=
"@+id/vna_ad_store"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:paddingStart=
"5dp"
android:paddingEnd=
"5dp"
android:textSize=
"12sp"
/>
<Button
android:id=
"@+id/vna_ad_call_to_action"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:gravity=
"center"
android:textSize=
"12sp"
/>
</LinearLayout>
</LinearLayout>
</com.google.android.gms.ads.nativead.NativeAdView>
<!-- 自定义关闭按钮:定位在广告内容右上角,50%溢出 -->
<ImageView
android:id=
"@+id/vna_close_button"
android:layout_width=
"36dp"
android:layout_height=
"36dp"
android:layout_gravity=
"top|end"
android:layout_marginTop=
"-18dp"
android:layout_marginEnd=
"-18dp"
android:contentDescription=
"Close ad"
android:padding=
"8dp"
android:src=
"@android:drawable/ic_menu_close_clear_cancel"
/>
</FrameLayout>
</FrameLayout>
app/src/main/res/values/style.xml
View file @
0647a03b
...
@@ -54,4 +54,28 @@
...
@@ -54,4 +54,28 @@
<item
name=
"android:width"
>
15dp
</item>
<item
name=
"android:width"
>
15dp
</item>
<item
name=
"android:height"
>
15dp
</item>
<item
name=
"android:height"
>
15dp
</item>
</style>
</style>
<!-- VNA 原生广告专用透明主题(与 pangle_mediaapp 一致) -->
<style
name=
"NativeAdTranslucentTheme"
parent=
"@android:style/Theme.Translucent.NoTitleBar"
>
<item
name=
"android:windowIsTranslucent"
>
true
</item>
<item
name=
"android:windowBackground"
>
@android:color/transparent
</item>
<item
name=
"android:windowIsFloating"
>
false
</item>
<item
name=
"android:backgroundDimEnabled"
>
true
</item>
<item
name=
"android:backgroundDimAmount"
>
0.5
</item>
<item
name=
"android:windowAnimationStyle"
>
@null
</item>
<item
name=
"android:windowDisablePreview"
>
true
</item>
<item
name=
"android:windowNoTitle"
>
true
</item>
<item
name=
"android:windowCloseOnTouchOutside"
>
false
</item>
</style>
<style
name=
"AdAttribution"
>
<item
name=
"android:layout_width"
>
wrap_content
</item>
<item
name=
"android:layout_height"
>
wrap_content
</item>
<item
name=
"android:layout_gravity"
>
left
</item>
<item
name=
"android:textColor"
>
#FFFFFF
</item>
<item
name=
"android:textSize"
>
12sp
</item>
<item
name=
"android:text"
>
@string/app_ad_attribution
</item>
<item
name=
"android:background"
>
#FFCC66
</item>
<item
name=
"android:width"
>
15dp
</item>
<item
name=
"android:height"
>
15dp
</item>
</style>
</resources>
</resources>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment