Commit 2beef962 authored by wanglei's avatar wanglei

....

parent c524029d
...@@ -25,7 +25,9 @@ ...@@ -25,7 +25,9 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.DataRecovery" android:theme="@style/Theme.DataRecovery"
tools:targetApi="34"> tools:targetApi="34">
<activity
android:name=".billing.BillingActivity"
android:exported="false" />
<activity <activity
android:name=".activity.SplashActivity" android:name=".activity.SplashActivity"
android:exported="true" android:exported="true"
...@@ -54,13 +56,12 @@ ...@@ -54,13 +56,12 @@
android:exported="false" android:exported="false"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" /> tools:ignore="DiscouragedApi,LockedOrientationActivity" /> <!-- <activity -->
<!-- <activity--> <!-- android:name=".activity.vip.VipActivity" -->
<!-- android:name=".activity.vip.VipActivity"--> <!-- android:exported="false" -->
<!-- android:exported="false"--> <!-- android:launchMode="singleTop" -->
<!-- android:launchMode="singleTop"--> <!-- android:screenOrientation="portrait" -->
<!-- android:screenOrientation="portrait"--> <!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" /> -->
<!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" />-->
<activity <activity
android:name=".activity.recyclebin.RecycleBinDetailActivity" android:name=".activity.recyclebin.RecycleBinDetailActivity"
android:exported="false" android:exported="false"
...@@ -113,13 +114,12 @@ ...@@ -113,13 +114,12 @@
android:exported="false" android:exported="false"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" /> tools:ignore="DiscouragedApi,LockedOrientationActivity" /> <!-- <activity -->
<!-- <activity--> <!-- android:name=".activity.privacyspace.PrivacyManageActivity" -->
<!-- android:name=".activity.privacyspace.PrivacyManageActivity"--> <!-- android:exported="false" -->
<!-- android:exported="false"--> <!-- android:launchMode="singleTop" -->
<!-- android:launchMode="singleTop"--> <!-- android:screenOrientation="portrait" -->
<!-- android:screenOrientation="portrait"--> <!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" /> -->
<!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" />-->
<activity <activity
android:name=".activity.repeat.RepeatActivity" android:name=".activity.repeat.RepeatActivity"
android:exported="false" android:exported="false"
...@@ -149,31 +149,30 @@ ...@@ -149,31 +149,30 @@
android:exported="false" android:exported="false"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" /> tools:ignore="DiscouragedApi,LockedOrientationActivity" /> <!-- <activity -->
<!-- <activity--> <!-- android:name=".activity.privacyspace.PrivacyPinTwoActivity" -->
<!-- android:name=".activity.privacyspace.PrivacyPinTwoActivity"--> <!-- android:exported="false" -->
<!-- android:exported="false"--> <!-- android:launchMode="singleTop" -->
<!-- android:launchMode="singleTop"--> <!-- android:screenOrientation="portrait" -->
<!-- android:screenOrientation="portrait"--> <!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" /> -->
<!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" />--> <!-- <activity -->
<!-- <activity--> <!-- android:name=".activity.privacyspace.PrivacyPinOneActivity" -->
<!-- android:name=".activity.privacyspace.PrivacyPinOneActivity"--> <!-- android:exported="false" -->
<!-- android:exported="false"--> <!-- android:launchMode="singleTop" -->
<!-- android:launchMode="singleTop"--> <!-- android:screenOrientation="portrait" -->
<!-- android:screenOrientation="portrait"--> <!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" /> -->
<!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" />--> <!-- <activity -->
<!-- <activity--> <!-- android:name=".activity.privacyspace.PrivacyImportActivity" -->
<!-- android:name=".activity.privacyspace.PrivacyImportActivity"--> <!-- android:exported="false" -->
<!-- android:exported="false"--> <!-- android:launchMode="singleTop" -->
<!-- android:launchMode="singleTop"--> <!-- android:screenOrientation="portrait" -->
<!-- android:screenOrientation="portrait"--> <!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" /> -->
<!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" />--> <!-- <activity -->
<!-- <activity--> <!-- android:name=".activity.privacyspace.PrivacySpaceActivity" -->
<!-- android:name=".activity.privacyspace.PrivacySpaceActivity"--> <!-- android:exported="false" -->
<!-- android:exported="false"--> <!-- android:launchMode="singleTop" -->
<!-- android:launchMode="singleTop"--> <!-- android:screenOrientation="portrait" -->
<!-- android:screenOrientation="portrait"--> <!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" /> -->
<!-- tools:ignore="DiscouragedApi,LockedOrientationActivity" />-->
<activity <activity
android:name=".activity.recovery.FileRecoveredActivity" android:name=".activity.recovery.FileRecoveredActivity"
android:exported="false" android:exported="false"
...@@ -192,6 +191,7 @@ ...@@ -192,6 +191,7 @@
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" /> tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<meta-data <meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION" android:name="com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION"
android:value="true" /> android:value="true" />
......
...@@ -9,6 +9,7 @@ import com.base.filerecoveryrecyclebin.activity.SplashActivity ...@@ -9,6 +9,7 @@ import com.base.filerecoveryrecyclebin.activity.SplashActivity
import com.base.filerecoveryrecyclebin.ads.AdmobMaxHelper import com.base.filerecoveryrecyclebin.ads.AdmobMaxHelper
import com.base.filerecoveryrecyclebin.ads.admob.AdmobOpenUtils import com.base.filerecoveryrecyclebin.ads.admob.AdmobOpenUtils
import com.base.filerecoveryrecyclebin.bean.ConstObject.ifAgreePrivacy import com.base.filerecoveryrecyclebin.bean.ConstObject.ifAgreePrivacy
import com.base.filerecoveryrecyclebin.billing.BillingClientLifecycle
import com.base.filerecoveryrecyclebin.fcm.FCMManager import com.base.filerecoveryrecyclebin.fcm.FCMManager
import com.base.filerecoveryrecyclebin.fcm.RecoveryTimerManager import com.base.filerecoveryrecyclebin.fcm.RecoveryTimerManager
import com.base.filerecoveryrecyclebin.fcm.ScreenStatusReceiver import com.base.filerecoveryrecyclebin.fcm.ScreenStatusReceiver
...@@ -31,6 +32,9 @@ class MyApplication : BaseApplication() { ...@@ -31,6 +32,9 @@ class MyApplication : BaseApplication() {
private val TAG = "MyApplication" private val TAG = "MyApplication"
var uuid = "" var uuid = ""
val billingClientLifecycle: BillingClientLifecycle
get() = BillingClientLifecycle.getInstance(this)
companion object { companion object {
@JvmField @JvmField
var PAUSED_VALUE = 0 var PAUSED_VALUE = 0
......
...@@ -162,7 +162,7 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() { ...@@ -162,7 +162,7 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() {
} }
} }
finish() finish()
// intent?.extras?.clear() intent?.extras?.clear()
} }
private fun closeNotification() { private fun closeNotification() {
......
package com.base.filerecoveryrecyclebin.bean
object BiliConstants {
//Product IDs
const val BASIC_PRODUCT = "basic_subscription"
const val PREMIUM_PRODUCT = "premium_subscription"
//Tags
const val BASIC_MONTHLY_PLAN = "basicmonthly"
const val BASIC_YEARLY_PLAN = "basicyearly"
const val BASIC_PREPAID_PLAN_TAG = "prepaidbasic"
}
\ No newline at end of file
package com.base.filerecoveryrecyclebin.bean
import com.android.billingclient.api.BillingClient
class BillingResponse(val code: Int) {
val isOk: Boolean
get() = code == BillingClient.BillingResponseCode.OK
val canFailGracefully: Boolean
get() = code == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED
val isRecoverableError: Boolean
get() = code in setOf(
BillingClient.BillingResponseCode.ERROR,
BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
)
val isNonrecoverableError: Boolean
get() = code in setOf(
BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
BillingClient.BillingResponseCode.BILLING_UNAVAILABLE,
BillingClient.BillingResponseCode.DEVELOPER_ERROR,
)
val isTerribleFailure: Boolean
get() = code in setOf(
BillingClient.BillingResponseCode.ITEM_UNAVAILABLE,
BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED,
BillingClient.BillingResponseCode.ITEM_NOT_OWNED,
BillingClient.BillingResponseCode.USER_CANCELED,
)
}
/*
* Copyright 2023 Google LLC. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.base.filerecoveryrecyclebin.bean
/**
* Local subscription data. This is stored on disk in a database.
*/
data class SubscriptionStatus(
// Local fields.
var primaryKey: Int = 0,
var subscriptionStatusJson: String? = null,
var subAlreadyOwned: Boolean = false,
var isLocalPurchase: Boolean = false,
// Remote fields.
var product: String? = null,
var purchaseToken: String? = null,
var isEntitlementActive: Boolean = false,
var willRenew: Boolean = false,
var activeUntilMillisec: Long = 0,
var isGracePeriod: Boolean = false,
var isAccountHold: Boolean = false,
var isPaused: Boolean = false,
var isAcknowledged: Boolean = false,
var autoResumeTimeMillis: Long = 0
) {
companion object {
}
}
package com.base.filerecoveryrecyclebin.billing
import com.base.filerecoveryrecyclebin.databinding.ActivityBillingBinding
import com.base.filerecoveryrecyclebin.help.BaseActivity
class BillingActivity : BaseActivity<ActivityBillingBinding>() {
override val binding: ActivityBillingBinding by lazy {
ActivityBillingBinding.inflate(layoutInflater)
}
override fun initView() {
}
}
\ No newline at end of file
package com.base.filerecoveryrecyclebin.billing package com.base.filerecoveryrecyclebin.billing
import android.content.Context import android.content.Context
import android.util.Log
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.ProductDetailsResponseListener
import com.android.billingclient.api.Purchase import com.android.billingclient.api.Purchase
import com.android.billingclient.api.PurchasesResponseListener
import com.android.billingclient.api.PurchasesUpdatedListener
import com.android.billingclient.api.QueryPurchasesParams
import com.base.filerecoveryrecyclebin.bean.BiliConstants
import com.base.filerecoveryrecyclebin.bean.BillingResponse
import com.base.filerecoveryrecyclebin.help.BaseApplication
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class BillingClientLifecycle( class BillingClientLifecycle(
private val applicationContext: Context, private val applicationContext: Context,
) : DefaultLifecycleObserver { private val coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
) : DefaultLifecycleObserver,
PurchasesUpdatedListener,
BillingClientStateListener,
PurchasesResponseListener,
ProductDetailsResponseListener {
private val TAG = "BillingClientLifecycle"
/**
* Instantiate a new BillingClient instance.
*/
private lateinit var billingClient: BillingClient
//订阅价格
private val _subscriptionPurchases = MutableStateFlow<List<Purchase>>(emptyList()) private val _subscriptionPurchases = MutableStateFlow<List<Purchase>>(emptyList())
val subscriptionPurchases = _subscriptionPurchases.asStateFlow() val subscriptionPurchases = _subscriptionPurchases.asStateFlow()
//基础商品信息
val basicSubProductWithProductDetails = MutableLiveData<ProductDetails?>()
//高级商品信息
val premiumSubProductWithProductDetails = MutableLiveData<ProductDetails?>()
override fun onCreate(owner: LifecycleOwner) {
Log.d(TAG, "onCreate")
billingClient = BillingClient.newBuilder(BaseApplication.context)
.setListener(this)
// .enablePendingPurchases() // Not used for subscriptions.
.build()
if (!billingClient.isReady) {
Log.d(TAG, "BillingClient: Start connection...")
billingClient.startConnection(this)
}
}
override fun onDestroy(owner: LifecycleOwner) {
Log.d(TAG, "onDestroy")
if (billingClient.isReady) {
Log.d(TAG, "BillingClient can only be used once -- closing connection")
// BillingClient can only be used once.
// After calling endConnection(), we must create a new BillingClient.
billingClient.endConnection()
}
}
/**
* 更新价格监听
* PurchasesUpdatedListener
*/
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
val responseCode = billingResult.responseCode
val debugMessage = billingResult.debugMessage
Log.d(TAG, "onPurchasesUpdated: $responseCode $debugMessage")
when (responseCode) {
BillingClient.BillingResponseCode.OK -> {
if (purchases == null) {
Log.d(TAG, "onPurchasesUpdated: null purchase list")
processPurchases(null)
} else {
processPurchases(purchases)
}
}
BillingClient.BillingResponseCode.USER_CANCELED -> {
Log.i(TAG, "onPurchasesUpdated: User canceled the purchase")
}
@Volatile BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
private var INSTANCE: BillingClientLifecycle? = null Log.i(TAG, "onPurchasesUpdated: The user already owns this item")
}
fun getInstance(applicationContext: Context): BillingClientLifecycle = BillingClient.BillingResponseCode.DEVELOPER_ERROR -> {
INSTANCE ?: synchronized(this) { Log.e(
INSTANCE ?: BillingClientLifecycle(applicationContext).also { INSTANCE = it } TAG, "onPurchasesUpdated: Developer error means that Google Play does " +
"not recognize the configuration. If you are just getting started, " +
"make sure you have configured the application correctly in the " +
"Google Play Console. The product ID must match and the APK you " +
"are using must be signed with release keys."
)
}
} }
}
private var cachedPurchasesList: List<Purchase>? = null
/**
* 重新发布价格
*/
private fun processPurchases(purchasesList: List<Purchase>?) {
purchasesList?.let { list ->
if (isUnchangedPurchaseList(list)) {
Log.d(TAG, "processPurchases: Purchase list has not changed")
return
}
coroutineScope.launch {
val subscriptionPurchaseList = list.filter { purchase ->
purchase.products.any { product ->
product in listOf(BiliConstants.PREMIUM_PRODUCT, BiliConstants.BASIC_PRODUCT)
}
}
_subscriptionPurchases.emit(subscriptionPurchaseList)
}
}
}
/**
* 对比旧的价格
*/
private fun isUnchangedPurchaseList(purchasesList: List<Purchase>): Boolean {
val isUnchanged = purchasesList == cachedPurchasesList
if (!isUnchanged) {
cachedPurchasesList = purchasesList
}
return isUnchanged
}
/**
* BillingClientStateListener
*/
override fun onBillingServiceDisconnected() {
Log.d(TAG, "onBillingServiceDisconnected")
}
/**
* BillingClientStateListener
*/
override fun onBillingSetupFinished(billingResult: BillingResult) {
Log.d(TAG, "onBillingSetupFinished")
val responseCode = billingResult.responseCode
val debugMessage = billingResult.debugMessage
Log.d(TAG, "onBillingSetupFinished: $responseCode $debugMessage")
if (responseCode == BillingClient.BillingResponseCode.OK) {
querySubscriptionPurchases()
}
}
/**
* 查询订阅价格
*/
private fun querySubscriptionPurchases() {
if (!billingClient.isReady) {
Log.e(TAG, "querySubscriptionPurchases: BillingClient is not ready")
billingClient.startConnection(this)
}
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.SUBS)
.build(), this
)
}
companion object {
@Volatile
private var INSTANCE: BillingClientLifecycle? = null
fun getInstance(applicationContext: Context): BillingClientLifecycle =
INSTANCE ?: synchronized(this) {
INSTANCE ?: BillingClientLifecycle(applicationContext).also { INSTANCE = it }
}
}
/**
* PurchasesResponseListener
* 购买价格响应
*/
override fun onQueryPurchasesResponse(billingResult: BillingResult, purchasesList: MutableList<Purchase>) {
processPurchases(purchasesList)
}
/**
* ProductDetailsResponseListener
* 产品细节响应
*/
override fun onProductDetailsResponse(
billingResult: BillingResult,
productDetailsList: MutableList<ProductDetails>
) {
val response = BillingResponse(billingResult.responseCode)
val debugMessage = billingResult.debugMessage
when {
response.isOk -> {
processProductDetails(productDetailsList)
}
response.isTerribleFailure -> {
// These response codes are not expected.
Log.w(
TAG,
"onProductDetailsResponse - Unexpected error: ${response.code} $debugMessage"
)
}
else -> {
Log.e(TAG, "onProductDetailsResponse: ${response.code} $debugMessage")
}
}
}
private val LIST_OF_SUBSCRIPTION_PRODUCTS = listOf(
BiliConstants.BASIC_PRODUCT,
BiliConstants.PREMIUM_PRODUCT,
)
private fun processProductDetails(productDetailsList: MutableList<ProductDetails>) {
if (productDetailsList.isEmpty()) {
val productDetailsCount = LIST_OF_SUBSCRIPTION_PRODUCTS.size
Log.e(
TAG,
"productDetailsCount=$productDetailsCount, Check to see if the products you requested are correctly published in the Google Play Console."
)
postProductDetails(emptyList())
} else {
postProductDetails(productDetailsList)
}
}
private fun postProductDetails(productDetailsList: List<ProductDetails>) {
productDetailsList.forEach { productDetails ->
when (productDetails.productType) {
BillingClient.ProductType.SUBS -> {//订阅
if (productDetails.productId == BiliConstants.PREMIUM_PRODUCT) {
premiumSubProductWithProductDetails.postValue(productDetails)
} else if (productDetails.productId == BiliConstants.BASIC_PRODUCT) {
basicSubProductWithProductDetails.postValue(productDetails)
}
}
}
}
}
} }
\ No newline at end of file
/*
* Copyright 2018 Google LLC. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.base.filerecoveryrecyclebin.billing
import com.android.billingclient.api.Purchase
import com.base.filerecoveryrecyclebin.bean.BiliConstants
import com.base.filerecoveryrecyclebin.bean.SubscriptionStatus
/**
* Return subscription for the provided Product, if it exists.
*/
fun subscriptionForProduct(
subscriptions: List<SubscriptionStatus>?,
product: String
): SubscriptionStatus? {
subscriptions?.let {
for (subscription in it) {
if (subscription.product == product) {
return subscription
}
}
}
// User does not have the subscription.
return null
}
/**
* Return purchase for the provided Product, if it exists.
*/
fun purchaseForProduct(purchases: List<Purchase>?, product: String): Purchase? {
purchases?.let {
for (purchase in it) {
if (purchase.products[0] == product) {
return purchase
}
}
}
return null
}
/**
* This will return true if the Google Play Billing APIs have a record for the subscription.
* This will not always match the server's record of the subscription for this app user.
*
* Example: App user buys the subscription on a different device with a different Google
* account. The server will show that this app user has the subscription, even if the
* Google account on this device has not purchased the subscription.
* In this example, the method will return false.
*
* Example: The app user changes by signing out and signing into the app with a different
* email address. The server will show that this app user does not have the subscription,
* even if the Google account on this device has purchased the subscription.
* In this example, the method will return true.
*/
fun deviceHasGooglePlaySubscription(purchases: List<Purchase>?, product: String) =
purchaseForProduct(purchases, product) != null
/**
* This will return true if the server has a record for the subscription.
* Sometimes this will return true even if the Google Play Billing APIs return false.
*
* For local purchases that are rejected by the server, this app attaches the field
* subAlreadyOwned=true to the subscription object. This means that whenever
* [deviceHasGooglePlaySubscription] returns true, and the server has processed all purchase tokens,
* we also expect this method to return true.
*
* Example: App user buys the subscription on a different device with a different Google
* account. The server will show that this app user has the subscription, even if the
* Google account on this device has not purchased the subscription.
* In this example, the method will return true, even though [deviceHasGooglePlaySubscription]
* will return false.
*
* Example: The app user changes by signing out and signing into the app with a different
* email address. The server will show that this app user does not have the subscription,
* by returning an API response indicating that it is ALREADY_OWNED.
* even if the Google account on this device has purchased the subscription.
* In this example, the method will return true. This is the same as the result from
* [deviceHasGooglePlaySubscription].
*/
fun serverHasSubscription(subscriptions: List<SubscriptionStatus>?, product: String) =
subscriptionForProduct(subscriptions, product) != null
/**
* Returns true if the grace period option should be shown.
*/
// TODO need to be refactored like isBasicContent
fun isGracePeriod(subscription: SubscriptionStatus?) =
subscription != null &&
subscription.isEntitlementActive &&
subscription.isGracePeriod &&
!subscription.subAlreadyOwned
/**
* Returns true if the subscription restore option should be shown.
*/
// TODO need to be refactored like isBasicContent
fun isSubscriptionRestore(subscription: SubscriptionStatus?) =
subscription != null &&
subscription.isEntitlementActive &&
!subscription.willRenew &&
!subscription.subAlreadyOwned
/**
* Returns true if the basic content should be shown.
*/
val SubscriptionStatus?.isBasicContent: Boolean
get() =
this != null &&
isEntitlementActive &&
BiliConstants.BASIC_PRODUCT == product &&
!subAlreadyOwned
/**
* Returns true if premium content should be shown.
*/
// TODO need to be refactored like isBasicContent
fun isPremiumContent(subscription: SubscriptionStatus?) =
subscription != null &&
subscription.isEntitlementActive &&
BiliConstants.PREMIUM_PRODUCT == subscription.product &&
!subscription.subAlreadyOwned
/**
* Returns true if account hold should be shown.
*/
// TODO need to be refactored like isBasicContent
fun isAccountHold(subscription: SubscriptionStatus?) =
subscription != null &&
!subscription.isEntitlementActive &&
subscription.isAccountHold &&
!subscription.subAlreadyOwned
/**
* Returns true if account pause should be shown.
*/
// TODO need to be refactored like isBasicContent
fun isPaused(subscription: SubscriptionStatus?) =
subscription != null &&
!subscription.isEntitlementActive &&
subscription.isPaused &&
!subscription.subAlreadyOwned
/**
* Returns true if the subscription is already owned and requires a transfer to this account.
*/
// TODO need to be refactored like isBasicContent
fun isTransferRequired(subscription: SubscriptionStatus?) =
subscription != null && subscription.subAlreadyOwned
/**
* Returns true if the subscription is a prepraid.
*/
val SubscriptionStatus?.isPrepaid: Boolean
get() =
this != null &&
!willRenew
\ No newline at end of file
//package com.base.filerecoveryrecyclebin.billing package com.base.filerecoveryrecyclebin.billing
//
//import android.app.Application import android.app.Application
//import android.content.Context import android.util.Log
//import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
//import androidx.lifecycle.MutableLiveData import com.base.filerecoveryrecyclebin.MyApplication
//import com.android.billingclient.api.BillingClient import com.base.filerecoveryrecyclebin.bean.BiliConstants
//import com.android.billingclient.api.ProductDetails import com.base.filerecoveryrecyclebin.utils.SingleLiveEvent
//
//class BillingViewModel(val application: Application) : AndroidViewModel(application) { class BillingViewModel(val application: Application) : AndroidViewModel(application) {
//
// private lateinit var billingClient: BillingClient private val TAG = "BillingViewModel"
//
//
// val billingClientLifecycle: BillingClientLifecycle private val purchases = (application as MyApplication).billingClientLifecycle.subscriptionPurchases
// get() = BillingClientLifecycle.getInstance(this) private val basicSubProductWithProductDetails =
// (application as MyApplication).billingClientLifecycle.basicSubProductWithProductDetails
// @Volatile private val premiumSubProductWithProductDetails =
// private var INSTANCE: BillingClientLifecycle? = null (application as MyApplication).billingClientLifecycle.premiumSubProductWithProductDetails
// fun getInstance(applicationContext: Context): BillingClientLifecycle =
// INSTANCE ?: synchronized(this) { val openPlayStoreSubscriptionsEvent = SingleLiveEvent<String>()
// INSTANCE ?: BillingClientLifecycle(application.applicationContext).also { INSTANCE = it }
// } /**
// * Open the Play Store subscription center. If the user has exactly one product,
// val basicSubProductWithProductDetails = MutableLiveData<ProductDetails?>() * then open the deeplink to the specific product.
// */
//} fun openPlayStoreSubscriptions() {
\ No newline at end of file val hasBasic = deviceHasGooglePlaySubscription(purchases.value, BiliConstants.BASIC_PRODUCT)
val hasPremium = deviceHasGooglePlaySubscription(purchases.value, BiliConstants.BASIC_PRODUCT)
Log.d(TAG, "hasBasic: $hasBasic, hasPremium: $hasPremium")
when {
hasBasic && !hasPremium -> {
// If we just have a basic subscription, open the basic Product.
openPlayStoreSubscriptionsEvent.postValue(BiliConstants.BASIC_PRODUCT)
}
!hasBasic && hasPremium -> {
// If we just have a premium subscription, open the premium Product.
openPlayStoreSubscriptionsEvent.postValue(BiliConstants.PREMIUM_PRODUCT)
}
else -> {
// If we do not have an active subscription,
// or if we have multiple subscriptions, open the default subscription center.
openPlayStoreSubscriptionsEvent.call()
}
}
}
}
\ No newline at end of file
/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.base.filerecoveryrecyclebin.utils
import androidx.annotation.MainThread
import androidx.lifecycle.MutableLiveData
import java.util.concurrent.atomic.AtomicBoolean
/**
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
* navigation and Snackbar messages.
*
*
* This avoids a common problem with events: on configuration change (like rotation) an update
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
* explicit call to setValue() or call().
*
*
* Note that only one observer is going to be notified of changes.
*/
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
@MainThread
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
companion object {
private const val TAG = "SingleLiveEvent"
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".billing.BillingActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment