Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • videolan/vlc-android
  • thresh/vlc-android
  • Dekans/vlc-android
  • robUx4/vlc-android
  • tguillem/vlc-android
  • Corbax/vlc-android
  • judeosby/vlc-android
  • chouquette/vlc-android
  • washingtonmurphy93/vlc-android
  • wipawanbeadklang540/vlc-android
  • xcorail/vlc-android
  • Aza/vlc-android
  • Skantes/vlc-android
  • filipjares/vlc-android
  • kazemihabib/vlc-android
  • amq10x/vlc-android
  • qadrian370/vlc-android
  • shivanshs9/vlc-android
  • vitaliyg2/vlc-android
  • orgads/vlc-android
  • rom1v/vlc-android
  • evidence/vlc-android
  • lk888/vlc-android
  • Klaus81/vlc-android
  • EwoutH/vlc-android
  • XilasZ/vlc-android
  • pawelpablo1975/vlc-android
  • uae2ae/vlc-android
  • Garf/vlc-android
  • abetatsunori7/vlc-android
  • ePirat/vlc-android
  • magsoft/vlc-android
  • quink/vlc-android
  • Ektos974/vlc-android
  • CymGen30/vlc-android
  • b1ue/vlc-android
  • alessiavalenti8/vlc-android
  • bars27101982/vlc-android
  • billybanda18/vlc-android
  • dklvip/vlc-android
  • sherylynn/vlc-android
  • ramcoach5/vlc-android
  • takise/vlc-android
  • peibolovedie/vlc-android
  • AMHeijboer/vlc-android
  • dahburj/vlc-android
  • gobennyb/vlc-android
  • masripmasrip824/vlc-android
  • bubu/vlc-android
  • tmk907/vlc-android
  • gorbahaaa/vlc-android
  • govind3321/vlc-android
  • jamieboyer42818/vlc-android
  • mrtakilapop/vlc-android
  • kazikarter90/vlc-android
  • chunyie771/vlc-android
  • nop404/vlc-android
  • hcalzazperz/vlc-android
  • zagwojtek69/vlc-android
  • nikonnick24/vlc-android
  • gmohiuddin215/vlc-android
  • stheinthan31/vlc-android
  • rogelioloreto29/vlc-android
  • philippestcyr5/vlc-android
  • Happyheather20/vlc-android
  • jimbobmcgee/vlc-android
  • mohwie/vlc-android
  • HeartBeat1608/vlc-android
  • jahan/vlc-android
  • Kevo1987/vlc-android
  • rrangel3584/vlc-android
  • xfridrich/vlc-android
  • devswami23/vlc-android
  • Isira-Seneviratne/vlc-android
  • PartyPhone22/vlc-android
  • kmajeshkrishnan/vlc-android
  • ramirotorresjr/vlc-android
  • aaronsalas469/vlc-android
  • arnan.np31/vlc-android
  • hkosacki/vlc-android
  • mg0691872/vlc-android
  • louregni/vlc-android
  • ritmapp/vlc-android
  • diegofn/vlc-android
  • s-ayush2903/vlc-android
  • chrisbohn1984/vlc-android
  • alexandre-janniaux/vlc-android
  • optimumpr/vlc-android
  • chirag-jn/vlc-android
  • e9ab98e991ab/vlc-android
  • suvDev/vlc-android
  • killthelord/vlc-android
  • Phoenix/vlc-android
  • soriyallc/vlc-android
  • tda1009/vlc-android
  • rhstone/vlc-android
  • ltnokiago/vlc-android
  • elbrujo1987/vlc-android
  • m1s4k1/vlc-android
  • mdc/vlc-android
  • arnaudj/vlc-android
  • abhinavmarwaha/vlc-android
  • dali546/vlc-android
  • Jeffset/vlc-android
  • megan30/vlc-android
  • lizhengdao/vlc-android
  • YajTPG/vlc-android
  • halcyon/vlc-android
  • ilya.yanok/vlc-android
  • jeramydhallmon/vlc-android
  • tijoemecano77/vlc-android
  • cjcj125125/vlc-android
  • dejan2609/vlc-android
  • JATIN2111999/vlc-android
  • imrohitkumar/vlc-android
  • sagudev/vlc-android
  • ILoveLin/vlc-android
  • tfighiera/vlc-android
  • snehil101/vlc-android
  • MKornelsen/vlc-android
  • m/vlc-android
  • core1024/vlc-android
  • vadimdolgachev/vlc-android
  • Kk77539/vlc-android
  • linzj/vlc-android
  • dantalian-pv/vlc-android
  • admkhalid/vlc-android
  • yaron/vlc-android
  • kn21091974/vlc-android
  • mylove1302/vlc-android
  • roblav96/vlc-android
  • alabiaga/vlc-android
  • kmnaveen101/vlc-android
  • nikiforoff1407/vlc-android
  • Android-Jester/vlc-android
  • frieda.rtwski/vlc-android
  • glaciers7506/vlc-android
  • rahul-gill/vlc-android
  • gaoxugang/vlc-android
  • Rishavgupta12345/vlc-android
  • dinho991556460/vlc-android
  • lighterowl/vlc-android
  • mary-kate/vlc-android
  • adnank20216/vlc-android
  • anthonylgutierrez79/vlc-android
  • Heliottw/vlc-android
  • mu1zix.ft3/vlc-android
  • yvesmaltais1212/vlc-android
  • ereme/vlc-android
  • jhonypalomino829/vlc-android
  • mx1up/vlc-android
  • sjwaddy/vlc-android
  • MohitMandalia/vlc-android
  • temaershov/vlc-android
  • js6pak/vlc-android
  • mishikallu/vlc-android
  • irfanmumtaz008/vlc-android
  • smurfohrachie/vlc-android
  • Goooler/vlc-android
  • eldo203050/vlc-android
  • dejesuszeus99/vlc-android
  • mfkl/vlc-android
  • Samfun75/vlc-android
  • markg85/vlc-android-ipfs
  • doktamelek911/vlc-android
  • wikwity/vlc-android
  • aangelmaker/vlc-android
  • krawczykradek119/vlc-android
  • ylz18180813163/vlc-android
  • galaxy9sx3/vlc-android
  • huajie2020/vlc-android
  • XuanTung95/vlc-android
  • kl/vlc-android
  • melihyolcu83/vlc-android
  • rt1shnik/vlc-android
  • jerryboy307/vlc-android
  • ikeuzochukwu6/vlc-android
  • patrikgolis/vlc-android
  • Ismavv/vlc-android
  • clementosumo/vlc-android
  • joshlamp66/vlc-android
  • nkmoyonyathiericyounge/vlc-android
  • DanTm99/vlc-android
  • Jeffrow41/vlc-android
  • fromphfr/vlc-android
  • 16project/vlc-android
  • Tomas8874/vlc-android
  • fixxxer87/vlc-android
  • sanjay/vlc-android
  • franciscojrp/vlc-android
  • cashellauswaus23/vlc-android
  • hengwu0/vlc-android
  • naythu2020rain/vlc-android
  • TongtengInhole/vlc-android
  • pupdoggy666/vlc-android
  • jcj921013/vlc-android
  • Gc6026/vlc-android
  • crijojc/vlc-android
  • mdrewight/vlc-android
  • davidhaywood0782/vlc-android
  • jbschtt/vlc-android
  • macfarlandcamel/vlc-android
  • syazairi/vlc-android
  • Commander01/vlc-android
  • baileyterry014/vlc-android
  • rodrickfranklin38/vlc-android
  • diego1245hernb/vlc-android
  • anton.canada/vlc-android
  • egazaekb/vlc-android
  • Marissa111113/vlc-android
  • jeinerbruno2/vlc-android
  • wehnie13/vlc-android
  • ozill87/vlc-android
  • legionfso/vlc-android
  • anazahirajoel/vlc-android
  • naomirojas1227/vlc-android
  • xbao/vlc-android
  • antoni.kozubek/vlc-android
  • pajela8482/vlc-android
  • karlfandango55/vlc-android
  • ruanbester07/vlc-android
  • chigita73/vlc-android
  • giu.pat6/vlc-android
  • Aura/vlc-android
  • marcwabo/vlc-android
  • contact.adgrafix/vlc-android
  • exlaverdad/vlc-android
  • simon.marquis88/vlc-android
  • youngkinsamantha421/vlc-android
  • edgard1161/vlc-android
  • agzx77/vlc-android
  • ha7204993/vlc-android
  • alghazwani.jar.6090/vlc-android
  • zrowton1206/vlc-android
  • darek1979513/vlc-android
  • matthiaskett18198/vlc-android
  • kubadyr77/vlc-android
  • romanstudeny1982/vlc-android
  • kumar107375/vlc-android
  • schong0525/vlc-android
  • Kt/vlc-android
  • jovelyn.esconde125/vlc-android
  • corriemacbarnard/vlc-android
  • apisbg91/vlc-android
  • yyusea/vlc-android
  • protechq88/vlc-android
  • rmangaraman/vlc-android
  • soutomikel/vlc-android
  • goregladaleksej5/vlc-android
  • tao/vlc-android
  • aruiz595/vlc-android
  • horvathpeter1202/vlc-android
  • litteh82/vlc-android
  • kerriochoa96/vlc-android
  • skshemul2022/vlc-android
  • popy/vlc-android
  • mizadpanahdev/vlc-android
  • pinarim2035/vlc-android
  • davidgooch1127/vlc-android
  • NyanCatTW1/vlc-android
  • Pendynurcahyo/vlc-android
  • fcartegnie/vlc-android
  • xiaoxiao921/vlc-android
  • rjtoell/vlc-android
  • devanshu6445/vlc-android
  • DopeDo69/vlc-android
  • quimsical/vlc-android
  • nickita.koltsoff/vlc-android
  • zeestander8617/vlc-android
  • alexsonarin06/vlc-android
  • minh189999h/vlc-android
  • JonnycatMeow/vlc-android
  • pup.ragnarok.1984/vlc-android2
  • jlcalderon13/vlc-android
  • franciszekk51214/vlc-android
  • yinsheng996/vlc-android
  • KenN3RD/vlc-android
  • leogps/vlc-android
  • alicuteo0407/vlc-android
  • MessirVoland/vlc-android
  • brett2uk/vlc-android
  • thomas.hermes/vlc-android
  • dgyudin/vlc-android
  • anuoshemohammad/vlc-android
  • r7truong/vlc-android
  • aaa1115910/vlc-android
  • arunkennedy78/vlc-android
  • nicholaszarra0069/vlc-android
  • georgipetrovdochev/vlc-android
  • Nikhil-z/vlc-android
  • misb1033/vlc-android
  • ktcoooot1/vlc-android
  • manmuc5/vlc-android
  • ahwhatisinttaken/vlc-android
  • m.nozka90/vlc-android
  • kabeermuhammad124319/vlc-android
  • ANGELONCE/vlc-android
  • manstabuk/vlc-android
  • yajcoca/vlc-android
  • dreamscell83/vlc-android
  • oldsssteveo/vlc-android
  • c0ff330k/vlc-android
  • ZhangXinmin528/vlc-android
  • lacsimarnald09/vlc-android
  • boykaisaac758/vlc-android
  • ooseidesmond/vlc-android
  • andresbott/vlc-android
  • yolandawanttoplay/vlc-android
  • lapaz17/vlc-android
  • sillyearl0138/vlc-android
  • NF-Repo/vlc-android
  • aaasg4001/vlc-android
  • mongia.puneet/vlc-android
  • slablaykon/vlc-android
  • Shabgardtanha1111/vlc-android
  • Benjamin_Loison/vlc-android
  • ashishami2002/vlc-android
  • Niram7777/vlc-android
  • Yashraj254/vlc-android
  • Choucroute_melba/vlc-android
  • Soete/vlc-android
  • MangalK/vlc-android
  • mohak2003/vlc-android
  • advait-0/vlc-android
  • McLP/vlc-android
  • fhuber/vlc-android
  • sami-sweng/vlc-android
  • josiahcarlson/vlc-android
328 results
Show changes
Showing
with 1607 additions and 87 deletions
/*
* ************************************************************************
* VLCBilling.kt
* *************************************************************************
* Copyright © 2020 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
* **************************************************************************
*
*
*/
package org.videolan.vlc.donations
import android.app.Application
import android.content.IntentFilter
import android.util.Log
import org.videolan.tools.SingletonHolder
import org.videolan.vlc.donations.util.*
import java.util.concurrent.atomic.AtomicBoolean
private const val DONATION_TIER_1 = "donation_tier_1"
private const val DONATION_TIER_2 = "donation_tier_2"
private const val DONATION_TIER_3 = "donation_tier_3"
private const val DONATION_TIER_4 = "donation_tier_4"
private const val DONATION_TIER_5 = "donation_tier_5"
private const val SUBSCRIPTION_TIER_1 = "subscription_tier_1"
private const val SUBSCRIPTION_TIER_2 = "subscription_tier_2"
private const val SUBSCRIPTION_TIER_3 = "subscription_tier_3"
private const val SUBSCRIPTION_TIER_4 = "subscription_tier_4"
private const val SUBSCRIPTION_TIER_5 = "subscription_tier_5"
class VLCBilling private constructor(private val context: Application) : IabBroadcastReceiver.IabBroadcastListener, IabHelper.QueryInventoryFinishedListener {
private var debug = false
private var alreadySetup = false
private lateinit var mBroadcastReceiver: IabBroadcastReceiver
val iabHelper by lazy { IabHelper(context, BuildConfig.PUBLIC_API_KEY).apply { if (debug) enableDebugLogging(true, "VLCBillingInt") } }
private val skuList = arrayListOf(DONATION_TIER_1, DONATION_TIER_2, DONATION_TIER_3, DONATION_TIER_4, DONATION_TIER_5, SUBSCRIPTION_TIER_1, SUBSCRIPTION_TIER_2, SUBSCRIPTION_TIER_3, SUBSCRIPTION_TIER_4, SUBSCRIPTION_TIER_5)
val skuDetails = ArrayList<SkuDetails>()
val subsDetails = ArrayList<SkuDetails>()
val purchases = ArrayList<Purchase>()
public var status = BillingStatus.NONE
set(value) {
field = value
listeners.forEach { it.invoke(value) }
}
var listeners: ArrayList<(BillingStatus) -> Unit> = arrayListOf()
fun addStatusListener(listener: (BillingStatus) -> Unit) {
listeners.add(listener)
}
fun removeListener(listener: (BillingStatus) -> Unit) {
listeners.remove(listener)
}
fun retrieveSkus() {
if (alreadySetup) return
alreadySetup = true
iabHelper.startSetup { result ->
status = BillingStatus.CONNECTING
if (!result.isSuccess) {
Log.e("VLCBilling", "Problem setting up in-app billing: $result")
status = BillingStatus.FAILURE
return@startSetup
}
status = BillingStatus.CONNECTED
mBroadcastReceiver = IabBroadcastReceiver(this)
val broadcastFilter = IntentFilter(IabBroadcastReceiver.ACTION)
context.registerReceiver(mBroadcastReceiver, broadcastFilter)
if (!iabHelper.mAsyncInProgress) iabHelper.queryInventoryAsync(true, skuList, this)
}
}
fun reloadSkus() {
if (!iabHelper.mAsyncInProgress) iabHelper.queryInventoryAsync(true, skuList, this)
}
override fun receivedBroadcast() {}
override fun onQueryInventoryFinished(result: IabResult?, inventory: Inventory?) {
// Is it a failure?
if (result!!.isFailure) {
Log.e("VLCBilling", "Failed to query inventory: $result")
status = BillingStatus.FAILURE
return
}
if (debug) Log.d("VLCBilling", "Query inventory was successful.")
subsDetails.clear()
subsDetails.clear()
purchases.clear()
skuList.forEach { skuDetail ->
val details = inventory?.getSkuDetails(skuDetail)
details?.let { if (skuDetail.contains("subscription")) subsDetails.add(it) else skuDetails.add(it) }
if (skuDetail.contains("donation") && inventory?.hasPurchase(skuDetail) == true) {
iabHelper.consumeAsync(inventory.getPurchase(skuDetail)!!){ _, _ ->
if (debug) Log.d("VLCBilling", "Consumed")
}
}
if (skuDetail.contains("subscription") && inventory?.hasPurchase(skuDetail) == true) purchases.add(inventory.getPurchase(skuDetail)!!)
if (debug) Log.d("VLCBilling", "${details?.price}")
}
status = BillingStatus.SKU_RETRIEVED
}
companion object : SingletonHolder<VLCBilling, Application>(::VLCBilling)
}
enum class BillingStatus {
NONE, CONNECTING, CONNECTED, FAILURE, SKU_RETRIEVED
}
\ No newline at end of file
/* Copyright (c) 2014 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 org.videolan.vlc.donations.util
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
/**
* Receiver for the "com.android.vending.billing.PURCHASES_UPDATED" Action
* from the Play Store.
*
*
* It is possible that an in-app item may be acquired without the
* application calling getBuyIntent(), for example if the item can be
* redeemed from inside the Play Store using a promotional code. If this
* application isn't running at the time, then when it is started a call
* to getPurchases() will be sufficient notification. However, if the
* application is already running in the background when the item is acquired,
* a message to this BroadcastReceiver will indicate that the an item
* has been acquired.
*/
class IabBroadcastReceiver(private val mListener: IabBroadcastListener?) : BroadcastReceiver() {
/**
* Listener interface for received broadcast messages.
*/
interface IabBroadcastListener {
fun receivedBroadcast()
}
override fun onReceive(context: Context, intent: Intent) {
mListener?.receivedBroadcast()
}
companion object {
/**
* The Intent action that this Receiver should filter for.
*/
const val ACTION = "com.android.vending.billing.PURCHASES_UPDATED"
}
}
\ No newline at end of file
/* Copyright (c) 2012 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 org.videolan.vlc.donations.util
/**
* Exception thrown when something went wrong with in-app billing.
* An IabException has an associated IabResult (an error).
* To get the IAB result that caused this exception to be thrown,
* call [.getResult].
*/
class IabException @JvmOverloads constructor(val result: IabResult, cause: Exception? = null) : Exception(result.message, cause) {
constructor(response: Int, message: String?) : this(IabResult(response, message))
constructor(response: Int, message: String?, cause: Exception?) : this(IabResult(response, message), cause)
}
\ No newline at end of file
/* Copyright (c) 2012 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 org.videolan.vlc.donations.util
import android.app.Activity
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentSender.SendIntentException
import android.content.ServiceConnection
import android.os.*
import android.text.TextUtils
import android.util.Log
import com.android.vending.billing.IInAppBillingService
import org.json.JSONException
import org.videolan.vlc.donations.util.IabException
import org.videolan.vlc.donations.util.Security.verifyPurchase
import java.util.*
/**
* Provides convenience methods for in-app billing. You can create one instance of this
* class for your application and use it to process in-app billing operations.
* It provides synchronous (blocking) and asynchronous (non-blocking) methods for
* many common in-app billing operations, as well as automatic signature
* verification.
*
* After instantiating, you must perform setup in order to start using the object.
* To perform setup, call the [.startSetup] method and provide a listener;
* that listener will be notified when setup is complete, after which (and not before)
* you may call other methods.
*
* After setup is complete, you will typically want to request an inventory of owned
* items and subscriptions. See [.queryInventory], [.queryInventoryAsync]
* and related methods.
*
* When you are done with this object, don't forget to call [.dispose]
* to ensure proper cleanup. This object holds a binding to the in-app billing
* service, which will leak unless you dispose of it correctly. If you created
* the object on an Activity's onCreate method, then the recommended
* place to dispose of it is the Activity's onDestroy method.
*
* A note about threading: When using this object from a background thread, you may
* call the blocking versions of methods; when using from a UI thread, call
* only the asynchronous versions and handle the results via callbacks.
* Also, notice that you can only call one asynchronous operation at a time;
* attempting to start a second asynchronous operation while the first one
* has not yet completed will result in an exception being thrown.
*
*/
class IabHelper(ctx: Context, base64PublicKey: String?) {
// Is debug logging enabled?
var mDebugLog = false
var mDebugTag = "IabHelper"
// Is setup done?
var mSetupDone = false
// Has this object been disposed of? (If so, we should ignore callbacks, etc)
var mDisposed = false
// Are subscriptions supported?
var mSubscriptionsSupported = false
// Is subscription update supported?
var mSubscriptionUpdateSupported = false
// Is an asynchronous operation in progress?
// (only one at a time can be in progress)
var mAsyncInProgress = false
// (for logging/debugging)
// if mAsyncInProgress == true, what asynchronous operation is in progress?
var mAsyncOperation = ""
// Context we were passed during initialization
var mContext: Context?
// Connection to the service
var mService: IInAppBillingService? = null
var mServiceConn: ServiceConnection? = null
// The request code used to launch purchase flow
var mRequestCode = 0
// The item type of the current purchase flow
var mPurchasingItemType: String? = null
// Public key for verifying signature, in base64 encoding
var mSignatureBase64: String? = null
/**
* Enables or disable debug logging through LogCat.
*/
fun enableDebugLogging(enable: Boolean, tag: String) {
checkNotDisposed()
mDebugLog = enable
mDebugTag = tag
}
fun enableDebugLogging(enable: Boolean) {
checkNotDisposed()
mDebugLog = enable
}
/**
* Starts the setup process. This will start up the setup process asynchronously.
* You will be notified through the listener when the setup process is complete.
* This method is safe to call from a UI thread.
*
* @param listener The listener to notify when the setup process is complete.
*/
fun startSetup(listener: (IabResult)-> Unit) {
// If already set up, can't do it again.
checkNotDisposed()
check(!mSetupDone) { "IAB helper is already set up." }
// Connection to IAB service
logDebug("Starting in-app billing setup.")
mServiceConn = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName) {
logDebug("Billing service disconnected.")
mService = null
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
if (mDisposed) return
logDebug("Billing service connected.")
mService = IInAppBillingService.Stub.asInterface(service)
val packageName = mContext!!.packageName
try {
logDebug("Checking for in-app billing 3 support.")
// check for in-app billing v3 support
var response = mService!!.isBillingSupported(3, packageName, ITEM_TYPE_INAPP)
if (response != BILLING_RESPONSE_RESULT_OK) {
listener.invoke(IabResult(response,
"Error checking for billing v3 support."))
// if in-app purchases aren't supported, neither are subscriptions
mSubscriptionsSupported = false
mSubscriptionUpdateSupported = false
return
} else {
logDebug("In-app billing version 3 supported for $packageName")
}
// Check for v5 subscriptions support. This is needed for
// getBuyIntentToReplaceSku which allows for subscription update
response = mService!!.isBillingSupported(5, packageName, ITEM_TYPE_SUBS)
mSubscriptionUpdateSupported = if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Subscription re-signup AVAILABLE.")
true
} else {
logDebug("Subscription re-signup not available.")
false
}
if (mSubscriptionUpdateSupported) {
mSubscriptionsSupported = true
} else {
// check for v3 subscriptions support
response = mService!!.isBillingSupported(3, packageName, ITEM_TYPE_SUBS)
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Subscriptions AVAILABLE.")
mSubscriptionsSupported = true
} else {
logDebug("Subscriptions NOT AVAILABLE. Response: $response")
mSubscriptionsSupported = false
mSubscriptionUpdateSupported = false
}
}
mSetupDone = true
} catch (e: RemoteException) {
listener.invoke(IabResult(IABHELPER_REMOTE_EXCEPTION,
"RemoteException while setting up in-app billing."))
e.printStackTrace()
return
}
listener.invoke(IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."))
}
}
val serviceIntent = Intent("com.android.vending.billing.InAppBillingService.BIND")
serviceIntent.setPackage("com.android.vending")
if (mContext!!.packageManager.queryIntentServices(serviceIntent, 0).isNullOrEmpty()) {
// no service available to handle that Intent
listener.invoke(
IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
"Billing service unavailable on device."))
} else {
// service available to handle that Intent
mContext!!.bindService(serviceIntent, mServiceConn!!, Context.BIND_AUTO_CREATE)
}
}
/**
* Dispose of object, releasing resources. It's very important to call this
* method when you are done with this object. It will release any resources
* used by it such as service connections. Naturally, once the object is
* disposed of, it can't be used again.
*/
fun dispose() {
logDebug("Disposing.")
mSetupDone = false
if (mServiceConn != null) {
logDebug("Unbinding from service.")
if (mContext != null) mContext!!.unbindService(mServiceConn!!)
}
mDisposed = true
mContext = null
mServiceConn = null
mService = null
mPurchaseListener = null
}
private fun checkNotDisposed() {
check(!mDisposed) { "IabHelper was disposed of, so it cannot be used." }
}
/** Returns whether subscriptions are supported. */
fun subscriptionsSupported(): Boolean {
checkNotDisposed()
return mSubscriptionsSupported
}
/**
* Callback that notifies when a purchase is finished.
*/
interface OnIabPurchaseFinishedListener {
/**
* Called to notify that an in-app purchase finished. If the purchase was successful,
* then the sku parameter specifies which item was purchased. If the purchase failed,
* the sku and extraData parameters may or may not be null, depending on how far the purchase
* process went.
*
* @param result The result of the purchase.
* @param info The purchase information (null if purchase failed)
*/
fun onIabPurchaseFinished(result: IabResult?, info: Purchase?)
}
// The listener registered on launchPurchaseFlow, which we have to call back when
// the purchase finishes
var mPurchaseListener: OnIabPurchaseFinishedListener? = null
@JvmOverloads
fun launchPurchaseFlow(act: Activity, sku: String, requestCode: Int,
listener: OnIabPurchaseFinishedListener?, extraData: String? = "") {
launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, null, requestCode, listener, extraData)
}
@JvmOverloads
fun launchSubscriptionPurchaseFlow(act: Activity, sku: String, requestCode: Int,
listener: OnIabPurchaseFinishedListener?, extraData: String? = "") {
launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, null, requestCode, listener, extraData)
}
/**
* Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
* which will involve bringing up the Google Play screen. The calling activity will be paused
* while the user interacts with Google Play, and the result will be delivered via the
* activity's [Activity.onActivityResult] method, at which point you must call
* this object's [.handleActivityResult] method to continue the purchase flow. This method
* MUST be called from the UI thread of the Activity.
*
* @param act The calling activity.
* @param sku The sku of the item to purchase.
* @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or
* ITEM_TYPE_SUBS)
* @param oldSkus A list of SKUs which the new SKU is replacing or null if there are none
* @param requestCode A request code (to differentiate from other responses -- as in
* [Activity.startActivityForResult]).
* @param listener The listener to notify when the purchase process finishes
* @param extraData Extra data (developer payload), which will be returned with the purchase
* data when the purchase completes. This extra data will be permanently bound to that
* purchase and will always be returned when the purchase is queried.
*/
fun launchPurchaseFlow(act: Activity, sku: String, itemType: String, oldSkus: List<String?>?,
requestCode: Int, listener: OnIabPurchaseFinishedListener?, extraData: String?) {
checkNotDisposed()
checkSetupDone("launchPurchaseFlow")
flagStartAsync("launchPurchaseFlow")
var result: IabResult
if (itemType == ITEM_TYPE_SUBS && !mSubscriptionsSupported) {
val r = IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
"Subscriptions are not available.")
flagEndAsync()
listener?.onIabPurchaseFinished(r, null)
return
}
try {
logDebug("Constructing buy intent for $sku, item type: $itemType")
val buyIntentBundle: Bundle
buyIntentBundle = if (oldSkus == null || oldSkus.isEmpty()) {
// Purchasing a new item or subscription re-signup
mService!!.getBuyIntent(3, mContext!!.packageName, sku, itemType,
extraData)
} else {
// Subscription upgrade/downgrade
if (!mSubscriptionUpdateSupported) {
val r = IabResult(IABHELPER_SUBSCRIPTION_UPDATE_NOT_AVAILABLE,
"Subscription updates are not available.")
flagEndAsync()
listener?.onIabPurchaseFinished(r, null)
return
}
mService!!.getBuyIntentToReplaceSkus(5, mContext!!.packageName,
oldSkus, sku, itemType, extraData)
}
val response = getResponseCodeFromBundle(buyIntentBundle)
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response))
flagEndAsync()
result = IabResult(response, "Unable to buy item")
listener?.onIabPurchaseFinished(result, null)
return
}
val pendingIntent = buyIntentBundle.getParcelable<PendingIntent>(RESPONSE_BUY_INTENT)
logDebug("Launching buy intent for $sku. Request code: $requestCode")
mRequestCode = requestCode
mPurchaseListener = listener
mPurchasingItemType = itemType
act.startIntentSenderForResult(pendingIntent!!.intentSender,
requestCode, Intent(),
Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0))
} catch (e: SendIntentException) {
logError("SendIntentException while launching purchase flow for sku $sku")
e.printStackTrace()
flagEndAsync()
result = IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.")
listener?.onIabPurchaseFinished(result, null)
} catch (e: RemoteException) {
logError("RemoteException while launching purchase flow for sku $sku")
e.printStackTrace()
flagEndAsync()
result = IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow")
listener?.onIabPurchaseFinished(result, null)
}
}
/**
* Handles an activity result that's part of the purchase flow in in-app billing. If you
* are calling [.launchPurchaseFlow], then you must call this method from your
* Activity's [@onActivityResult][Activity] method. This method
* MUST be called from the UI thread of the Activity.
*
* @param requestCode The requestCode as you received it.
* @param resultCode The resultCode as you received it.
* @param data The data (Intent) as you received it.
* @return Returns true if the result was related to a purchase flow and was handled;
* false if the result was not related to a purchase, in which case you should
* handle it normally.
*/
fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
var result: IabResult
if (requestCode != mRequestCode) return false
checkNotDisposed()
checkSetupDone("handleActivityResult")
// end of async purchase operation that started on launchPurchaseFlow
flagEndAsync()
if (data == null) {
logError("Null data in IAB activity result.")
result = IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result")
if (mPurchaseListener != null) mPurchaseListener!!.onIabPurchaseFinished(result, null)
return true
}
val responseCode = getResponseCodeFromIntent(data)
val purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA)
val dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE)
if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {
logDebug("Successful resultcode from purchase activity.")
logDebug("Purchase data: $purchaseData")
logDebug("Data signature: $dataSignature")
logDebug("Extras: " + data.extras)
logDebug("Expected item type: $mPurchasingItemType")
if (purchaseData == null || dataSignature == null) {
logError("BUG: either purchaseData or dataSignature is null.")
logDebug("Extras: " + data.extras.toString())
result = IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature")
if (mPurchaseListener != null) mPurchaseListener!!.onIabPurchaseFinished(result, null)
return true
}
var purchase: Purchase? = null
try {
purchase = Purchase(mPurchasingItemType!!, purchaseData, dataSignature)
val sku = purchase.sku
// Verify signature
if (!verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {
logError("Purchase signature verification FAILED for sku $sku")
result = IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku $sku")
if (mPurchaseListener != null) mPurchaseListener!!.onIabPurchaseFinished(result, purchase)
return true
}
logDebug("Purchase signature successfully verified.")
} catch (e: JSONException) {
logError("Failed to parse purchase data.")
e.printStackTrace()
result = IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.")
if (mPurchaseListener != null) mPurchaseListener!!.onIabPurchaseFinished(result, null)
return true
}
if (mPurchaseListener != null) {
mPurchaseListener!!.onIabPurchaseFinished(IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase)
}
} else if (resultCode == Activity.RESULT_OK) {
// result code was OK, but in-app billing response was not OK.
logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode))
if (mPurchaseListener != null) {
result = IabResult(responseCode, "Problem purchashing item.")
mPurchaseListener!!.onIabPurchaseFinished(result, null)
}
} else if (resultCode == Activity.RESULT_CANCELED) {
logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode))
result = IabResult(IABHELPER_USER_CANCELLED, "User canceled.")
if (mPurchaseListener != null) mPurchaseListener!!.onIabPurchaseFinished(result, null)
} else {
logError("Purchase failed. Result code: " + resultCode.toString()
+ ". Response: " + getResponseDesc(responseCode))
result = IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.")
if (mPurchaseListener != null) mPurchaseListener!!.onIabPurchaseFinished(result, null)
}
return true
}
@Throws(IabException::class)
fun queryInventory(querySkuDetails: Boolean, moreSkus: List<String?>?): Inventory {
return queryInventory(querySkuDetails, moreSkus, null)
}
/**
* Queries the inventory. This will query all owned items from the server, as well as
* information on additional skus, if specified. This method may block or take long to execute.
* Do not call from a UI thread. For that, use the non-blocking version [.queryInventoryAsync].
*
* @param querySkuDetails if true, SKU details (price, description, etc) will be queried as well
* as purchase information.
* @param moreItemSkus additional PRODUCT skus to query information on, regardless of ownership.
* Ignored if null or if querySkuDetails is false.
* @param moreSubsSkus additional SUBSCRIPTIONS skus to query information on, regardless of ownership.
* Ignored if null or if querySkuDetails is false.
* @throws IabException if a problem occurs while refreshing the inventory.
*/
@Throws(IabException::class)
fun queryInventory(querySkuDetails: Boolean, moreItemSkus: List<String?>?,
moreSubsSkus: List<String?>?): Inventory {
checkNotDisposed()
checkSetupDone("queryInventory")
return try {
val inv = Inventory()
var r = queryPurchases(inv, ITEM_TYPE_INAPP)
if (r != BILLING_RESPONSE_RESULT_OK) {
throw IabException(r, "Error refreshing inventory (querying owned items).")
}
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus)
if (r != BILLING_RESPONSE_RESULT_OK) {
throw IabException(r, "Error refreshing inventory (querying prices of items).")
}
}
// if subscriptions are supported, then also query for subscriptions
if (mSubscriptionsSupported) {
r = queryPurchases(inv, ITEM_TYPE_SUBS)
if (r != BILLING_RESPONSE_RESULT_OK) {
throw IabException(r, "Error refreshing inventory (querying owned subscriptions).")
}
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreItemSkus)
if (r != BILLING_RESPONSE_RESULT_OK) {
throw IabException(r, "Error refreshing inventory (querying prices of subscriptions).")
}
}
}
inv
} catch (e: RemoteException) {
throw IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while refreshing inventory.", e)
} catch (e: JSONException) {
throw IabException(IABHELPER_BAD_RESPONSE, "Error parsing JSON response while refreshing inventory.", e)
}
}
/**
* Listener that notifies when an inventory query operation completes.
*/
interface QueryInventoryFinishedListener {
/**
* Called to notify that an inventory query operation completed.
*
* @param result The result of the operation.
* @param inv The inventory.
*/
fun onQueryInventoryFinished(result: IabResult?, inv: Inventory?)
}
/**
* Asynchronous wrapper for inventory query. This will perform an inventory
* query as described in [.queryInventory], but will do so asynchronously
* and call back the specified listener upon completion. This method is safe to
* call from a UI thread.
*
* @param querySkuDetails as in [.queryInventory]
* @param moreSkus as in [.queryInventory]
* @param listener The listener to notify when the refresh operation completes.
*/
fun queryInventoryAsync(querySkuDetails: Boolean, moreSkus: List<String?>?,
listener: QueryInventoryFinishedListener?) {
val handler = Handler(Looper.getMainLooper())
checkNotDisposed()
checkSetupDone("queryInventory")
flagStartAsync("refresh inventory")
Thread {
var result = IabResult(BILLING_RESPONSE_RESULT_OK, "Inventory refresh successful.")
var inv: Inventory? = null
try {
inv = queryInventory(querySkuDetails, moreSkus)
} catch (ex: IabException) {
result = ex.result
}
flagEndAsync()
val result_f = result
val inv_f = inv
if (!mDisposed && listener != null) {
handler.post { listener.onQueryInventoryFinished(result_f, inv_f) }
}
}.start()
}
fun queryInventoryAsync(listener: QueryInventoryFinishedListener?) {
queryInventoryAsync(true, null, listener)
}
fun queryInventoryAsync(querySkuDetails: Boolean, listener: QueryInventoryFinishedListener?) {
queryInventoryAsync(querySkuDetails, null, listener)
}
/**
* Consumes a given in-app product. Consuming can only be done on an item
* that's owned, and as a result of consumption, the user will no longer own it.
* This method may block or take long to return. Do not call from the UI thread.
* For that, see [.consumeAsync].
*
* @param itemInfo The PurchaseInfo that represents the item to consume.
* @throws IabException if there is a problem during consumption.
*/
@Throws(IabException::class)
fun consume(itemInfo: Purchase) {
checkNotDisposed()
checkSetupDone("consume")
if (itemInfo.itemType != ITEM_TYPE_INAPP) {
throw IabException(IABHELPER_INVALID_CONSUMPTION,
"Items of type '" + itemInfo.itemType + "' can't be consumed.")
}
try {
val token = itemInfo.token
val sku = itemInfo.sku
if (token == null || token == "") {
logError("Can't consume $sku. No token.")
throw IabException(IABHELPER_MISSING_TOKEN, "PurchaseInfo is missing token for sku: "
+ sku + " " + itemInfo)
}
logDebug("Consuming sku: $sku, token: $token")
val response = mService!!.consumePurchase(3, mContext!!.packageName, token)
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Successfully consumed sku: $sku")
} else {
logDebug("Error consuming consuming sku " + sku + ". " + getResponseDesc(response))
throw IabException(response, "Error consuming sku $sku")
}
} catch (e: RemoteException) {
throw IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while consuming. PurchaseInfo: $itemInfo", e)
}
}
/**
* Callback that notifies when a consumption operation finishes.
*/
interface OnConsumeFinishedListener {
/**
* Called to notify that a consumption has finished.
*
* @param purchase The purchase that was (or was to be) consumed.
* @param result The result of the consumption operation.
*/
fun onConsumeFinished(purchase: Purchase?, result: IabResult?)
}
/**
* Callback that notifies when a multi-item consumption operation finishes.
*/
interface OnConsumeMultiFinishedListener {
/**
* Called to notify that a consumption of multiple items has finished.
*
* @param purchases The purchases that were (or were to be) consumed.
* @param results The results of each consumption operation, corresponding to each
* sku.
*/
fun onConsumeMultiFinished(purchases: List<Purchase>?, results: List<IabResult>?)
}
/**
* Asynchronous wrapper to item consumption. Works like [.consume], but
* performs the consumption in the background and notifies completion through
* the provided listener. This method is safe to call from a UI thread.
*
* @param purchase The purchase to be consumed.
* @param listener The listener to notify when the consumption operation finishes.
*/
fun consumeAsync(purchase: Purchase, listener: (purchase: Purchase?, result: IabResult?)-> Unit) {
checkNotDisposed()
checkSetupDone("consume")
val purchases: MutableList<Purchase> = ArrayList()
purchases.add(purchase)
consumeAsyncInternal(purchases, listener, null)
}
/**
* Same as [.consumeAsync], but for multiple items at once.
* @param purchases The list of PurchaseInfo objects representing the purchases to consume.
* @param listener The listener to notify when the consumption operation finishes.
*/
fun consumeAsync(purchases: List<Purchase>, listener: OnConsumeMultiFinishedListener?) {
checkNotDisposed()
checkSetupDone("consume")
consumeAsyncInternal(purchases, null, listener)
}
// Checks that setup was done; if not, throws an exception.
fun checkSetupDone(operation: String) {
if (!mSetupDone) {
logError("Illegal state for operation ($operation): IAB helper is not set up.")
throw IllegalStateException("IAB helper is not set up. Can't perform operation: $operation")
}
}
// Workaround to bug where sometimes response codes come as Long instead of Integer
fun getResponseCodeFromBundle(b: Bundle): Int {
val o = b[RESPONSE_CODE]
return if (o == null) {
logDebug("Bundle with null response code, assuming OK (known issue)")
BILLING_RESPONSE_RESULT_OK
} else if (o is Int) o.toInt() else if (o is Long) o.toLong().toInt() else {
logError("Unexpected type for bundle response code.")
logError(o.javaClass.name)
throw RuntimeException("Unexpected type for bundle response code: " + o.javaClass.name)
}
}
// Workaround to bug where sometimes response codes come as Long instead of Integer
fun getResponseCodeFromIntent(i: Intent): Int {
val o = i.extras!![RESPONSE_CODE]
return if (o == null) {
logError("Intent with no response code, assuming OK (known issue)")
BILLING_RESPONSE_RESULT_OK
} else if (o is Int) o.toInt() else if (o is Long) o.toLong().toInt() else {
logError("Unexpected type for intent response code.")
logError(o.javaClass.name)
throw RuntimeException("Unexpected type for intent response code: " + o.javaClass.name)
}
}
fun flagStartAsync(operation: String) {
check(!mAsyncInProgress) {
"Can't start async operation (" +
operation + ") because another async operation(" + mAsyncOperation + ") is in progress."
}
mAsyncOperation = operation
mAsyncInProgress = true
logDebug("Starting async operation: $operation")
}
fun flagEndAsync() {
logDebug("Ending async operation: $mAsyncOperation")
mAsyncOperation = ""
mAsyncInProgress = false
}
@Throws(JSONException::class, RemoteException::class)
fun queryPurchases(inv: Inventory, itemType: String): Int {
// Query purchases
logDebug("Querying owned items, item type: $itemType")
logDebug("Package name: " + mContext!!.packageName)
var verificationFailed = false
var continueToken: String? = null
do {
logDebug("Calling getPurchases with continuation token: $continueToken")
val ownedItems = mService!!.getPurchases(3, mContext!!.packageName,
itemType, continueToken)
val response = getResponseCodeFromBundle(ownedItems)
logDebug("Owned items response: $response")
if (response != BILLING_RESPONSE_RESULT_OK) {
logDebug("getPurchases() failed: " + getResponseDesc(response))
return response
}
if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST)
|| !ownedItems.containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST)
|| !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) {
logError("Bundle returned from getPurchases() doesn't contain required fields.")
return IABHELPER_BAD_RESPONSE
}
val ownedSkus = ownedItems.getStringArrayList(
RESPONSE_INAPP_ITEM_LIST)
val purchaseDataList = ownedItems.getStringArrayList(
RESPONSE_INAPP_PURCHASE_DATA_LIST)
val signatureList = ownedItems.getStringArrayList(
RESPONSE_INAPP_SIGNATURE_LIST)
for (i in purchaseDataList!!.indices) {
val purchaseData = purchaseDataList[i]
val signature = signatureList!![i]
val sku = ownedSkus!![i]
if (verifyPurchase(mSignatureBase64, purchaseData, signature)) {
logDebug("Sku is owned: $sku")
val purchase = Purchase(itemType, purchaseData, signature)
if (TextUtils.isEmpty(purchase.token)) {
logWarn("BUG: empty/null token!")
logDebug("Purchase data: $purchaseData")
}
// Record ownership and token
inv.addPurchase(purchase)
} else {
logWarn("Purchase signature verification **FAILED**. Not adding item.")
logDebug(" Purchase data: $purchaseData")
logDebug(" Signature: $signature")
verificationFailed = true
}
}
continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN)
logDebug("Continuation token: $continueToken")
} while (!TextUtils.isEmpty(continueToken))
return if (verificationFailed) IABHELPER_VERIFICATION_FAILED else BILLING_RESPONSE_RESULT_OK
}
@Throws(RemoteException::class, JSONException::class)
fun querySkuDetails(itemType: String?, inv: Inventory, moreSkus: List<String?>?): Int {
logDebug("Querying SKU details.")
val skuList = ArrayList<String?>()
skuList.addAll(inv.getAllOwnedSkus(itemType!!))
if (moreSkus != null) {
for (sku in moreSkus) {
if (!skuList.contains(sku)) {
skuList.add(sku)
}
}
}
if (skuList.size == 0) {
logDebug("queryPrices: nothing to do because there are no SKUs.")
return BILLING_RESPONSE_RESULT_OK
}
// Split the sku list in blocks of no more than 20 elements.
val packs = ArrayList<ArrayList<String?>>()
var tempList: ArrayList<String?>
val n = skuList.size / 20
val mod = skuList.size % 20
for (i in 0 until n) {
tempList = ArrayList()
for (s in skuList.subList(i * 20, i * 20 + 20)) {
tempList.add(s)
}
packs.add(tempList)
}
if (mod != 0) {
tempList = ArrayList()
for (s in skuList.subList(n * 20, n * 20 + mod)) {
tempList.add(s)
}
packs.add(tempList)
}
for (skuPartList in packs) {
val querySkus = Bundle()
querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuPartList)
val skuDetails = mService!!.getSkuDetails(3, mContext!!.packageName,
itemType, querySkus)
if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
val response = getResponseCodeFromBundle(skuDetails)
return if (response != BILLING_RESPONSE_RESULT_OK) {
logDebug("getSkuDetails() failed: " + getResponseDesc(response))
response
} else {
logError("getSkuDetails() returned a bundle with neither an error nor a detail list.")
IABHELPER_BAD_RESPONSE
}
}
val responseList = skuDetails.getStringArrayList(
RESPONSE_GET_SKU_DETAILS_LIST)
for (thisResponse in responseList!!) {
val d = SkuDetails(itemType, thisResponse!!)
logDebug("Got sku details: $d")
inv.addSkuDetails(d)
}
}
return BILLING_RESPONSE_RESULT_OK
}
fun consumeAsyncInternal(purchases: List<Purchase>,
singleListener: ((purchase: Purchase?, result: IabResult?)-> Unit)?,
multiListener: OnConsumeMultiFinishedListener?) {
val handler = Handler(Looper.getMainLooper())
flagStartAsync("consume")
Thread {
val results: MutableList<IabResult> = ArrayList()
for (purchase in purchases) {
try {
consume(purchase)
results.add(IabResult(BILLING_RESPONSE_RESULT_OK, "Successful consume of sku " + purchase.sku))
} catch (ex: IabException) {
results.add(ex.result)
}
}
flagEndAsync()
if (!mDisposed && singleListener != null) {
handler.post { singleListener.invoke(purchases[0], results[0]) }
}
if (!mDisposed && multiListener != null) {
handler.post { multiListener.onConsumeMultiFinished(purchases, results) }
}
}.start()
}
fun logDebug(msg: String?) {
msg?.let { if (mDebugLog) Log.d(mDebugTag, it) }
}
fun logError(msg: String) {
Log.e(mDebugTag, "In-app billing error: $msg")
}
fun logWarn(msg: String) {
Log.w(mDebugTag, "In-app billing warning: $msg")
}
companion object {
// Billing response codes
const val BILLING_RESPONSE_RESULT_OK = 0
const val BILLING_RESPONSE_RESULT_USER_CANCELED = 1
const val BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE = 2
const val BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3
const val BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4
const val BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5
const val BILLING_RESPONSE_RESULT_ERROR = 6
const val BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7
const val BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8
// IAB Helper error codes
const val IABHELPER_ERROR_BASE = -1000
const val IABHELPER_REMOTE_EXCEPTION = -1001
const val IABHELPER_BAD_RESPONSE = -1002
const val IABHELPER_VERIFICATION_FAILED = -1003
const val IABHELPER_SEND_INTENT_FAILED = -1004
const val IABHELPER_USER_CANCELLED = -1005
const val IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006
const val IABHELPER_MISSING_TOKEN = -1007
const val IABHELPER_UNKNOWN_ERROR = -1008
const val IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009
const val IABHELPER_INVALID_CONSUMPTION = -1010
const val IABHELPER_SUBSCRIPTION_UPDATE_NOT_AVAILABLE = -1011
// Keys for the responses from InAppBillingService
const val RESPONSE_CODE = "RESPONSE_CODE"
const val RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST"
const val RESPONSE_BUY_INTENT = "BUY_INTENT"
const val RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA"
const val RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE"
const val RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST"
const val RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST"
const val RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST"
const val INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN"
// Item types
const val ITEM_TYPE_INAPP = "inapp"
const val ITEM_TYPE_SUBS = "subs"
// some fields on the getSkuDetails response bundle
const val GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST"
const val GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST"
/**
* Returns a human-readable description for the given response code.
*
* @param code The response code
* @return A human-readable string explaining the result code.
* It also includes the result code numerically.
*/
fun getResponseDesc(code: Int): String {
val iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" +
"3:Billing Unavailable/4:Item unavailable/" +
"5:Developer Error/6:Error/7:Item Already Owned/" +
"8:Item not owned").split("/".toRegex()).toTypedArray()
val iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" +
"-1002:Bad response received/" +
"-1003:Purchase signature verification failed/" +
"-1004:Send intent failed/" +
"-1005:User cancelled/" +
"-1006:Unknown purchase response/" +
"-1007:Missing token/" +
"-1008:Unknown error/" +
"-1009:Subscriptions not available/" +
"-1010:Invalid consumption attempt").split("/".toRegex()).toTypedArray()
return if (code <= IABHELPER_ERROR_BASE) {
val index = IABHELPER_ERROR_BASE - code
if (index >= 0 && index < iabhelper_msgs.size) iabhelper_msgs[index] else "$code:Unknown IAB Helper Error"
} else if (code < 0 || code >= iab_msgs.size) "$code:Unknown" else iab_msgs[code]
}
}
/**
* Creates an instance. After creation, it will not yet be ready to use. You must perform
* setup by calling [.startSetup] and wait for setup to complete. This constructor does not
* block and is safe to call from a UI thread.
*
* @param ctx Your application or Activity context. Needed to bind to the in-app billing service.
* @param base64PublicKey Your application's public key, encoded in base64.
* This is used for verification of purchase signatures. You can find your app's base64-encoded
* public key in your application's page on Google Play Developer Console. Note that this
* is NOT your "developer public key".
*/
init {
mContext = ctx.applicationContext
mSignatureBase64 = base64PublicKey
logDebug("IAB helper created.")
}
}
\ No newline at end of file
/* Copyright (c) 2012 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 org.videolan.vlc.donations.util
/**
* Represents the result of an in-app billing operation.
* A result is composed of a response code (an integer) and possibly a
* message (String). You can get those by calling
* [.getResponse] and [.getMessage], respectively. You
* can also inquire whether a result is a success or a failure by
* calling [.isSuccess] and [.isFailure].
*/
class IabResult(var response: Int, message: String?) {
var message: String? = null
val isSuccess: Boolean
get() = response == IabHelper.BILLING_RESPONSE_RESULT_OK
val isFailure: Boolean
get() = !isSuccess
override fun toString(): String {
return "IabResult: $message"
}
init {
if (message == null || message.trim { it <= ' ' }.isEmpty()) {
this.message = IabHelper.getResponseDesc(response)
} else {
this.message = message + " (response: " + IabHelper.getResponseDesc(response) + ")"
}
}
}
\ No newline at end of file
/* Copyright (c) 2012 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 org.videolan.vlc.donations.util
import java.util.*
/**
* Represents a block of information about in-app items.
* An Inventory is returned by such methods as [IabHelper.queryInventory].
*/
class Inventory internal constructor() {
var skuMap: MutableMap<String, SkuDetails?> = HashMap()
var purchaseMap: MutableMap<String, Purchase?> = HashMap()
/** Returns the listing details for an in-app product. */
fun getSkuDetails(sku: String?): SkuDetails? {
return skuMap[sku]
}
/** Returns purchase information for a given product, or null if there is no purchase. */
fun getPurchase(sku: String?): Purchase? {
return purchaseMap[sku]
}
/** Returns whether or not there exists a purchase of the given product. */
fun hasPurchase(sku: String?): Boolean {
return purchaseMap.containsKey(sku)
}
/** Return whether or not details about the given product are available. */
fun hasDetails(sku: String?): Boolean {
return skuMap.containsKey(sku)
}
/**
* Erase a purchase (locally) from the inventory, given its product ID. This just
* modifies the Inventory object locally and has no effect on the server! This is
* useful when you have an existing Inventory object which you know to be up to date,
* and you have just consumed an item successfully, which means that erasing its
* purchase data from the Inventory you already have is quicker than querying for
* a new Inventory.
*/
fun erasePurchase(sku: String?) {
if (purchaseMap.containsKey(sku)) purchaseMap.remove(sku)
}
/** Returns a list of all owned product IDs. */
val allOwnedSkus: List<String>
get() = ArrayList(purchaseMap.keys)
/** Returns a list of all owned product IDs of a given type */
fun getAllOwnedSkus(itemType: String): List<String> {
val result: MutableList<String> = ArrayList()
for (p in purchaseMap.values) {
if (p!!.itemType == itemType) result.add(p.sku)
}
return result
}
/** Returns a list of all purchases. */
val allPurchases: List<Purchase?>
get() = ArrayList(purchaseMap.values)
fun addSkuDetails(d: SkuDetails) {
skuMap[d.sku] = d
}
fun addPurchase(p: Purchase) {
purchaseMap[p.sku] = p
}
}
\ No newline at end of file
/* Copyright (c) 2012 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 org.videolan.vlc.donations.util
import org.json.JSONObject
/**
* Represents an in-app billing purchase.
*/
class Purchase(var itemType: String, private var originalJson: String, signature: String) {
var orderId: String
var packageName: String
var sku: String
var purchaseTime: Long
var purchaseState: Int
var developerPayload: String
var token: String
var signature: String
var isAutoRenewing: Boolean
override fun toString(): String {
return "PurchaseInfo(type:$itemType):$originalJson"
}
init {
val o = JSONObject(originalJson)
orderId = o.optString("orderId")
packageName = o.optString("packageName")
sku = o.optString("productId")
purchaseTime = o.optLong("purchaseTime")
purchaseState = o.optInt("purchaseState")
developerPayload = o.optString("developerPayload")
token = o.optString("token", o.optString("purchaseToken"))
isAutoRenewing = o.optBoolean("autoRenewing")
this.signature = signature
}
}
\ No newline at end of file
/* Copyright (c) 2012 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 org.videolan.vlc.donations.util
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.X509EncodedKeySpec
/**
* Security-related methods. For a secure implementation, all of this code
* should be implemented on a server that communicates with the
* application on the device. For the sake of simplicity and clarity of this
* example, this code is included here and is executed on the device. If you
* must verify the purchases on the phone, you should obfuscate this code to
* make it harder for an attacker to replace the code with stubs that treat all
* purchases as verified.
*/
object Security {
private const val TAG = "IABUtil/Security"
private const val KEY_FACTORY_ALGORITHM = "RSA"
private const val SIGNATURE_ALGORITHM = "SHA1withRSA"
/**
* Verifies that the data was signed with the given signature, and returns
* the verified purchase. The data is in JSON format and signed
* with a private key. The data also contains the [PurchaseState]
* and product ID of the purchase.
* @param base64PublicKey the base64-encoded public key to use for verifying.
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
*/
@JvmStatic
fun verifyPurchase(base64PublicKey: String?, signedData: String, signature: String?): Boolean {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
Log.e(TAG, "Purchase verification failed: missing data.")
return false
}
val key = generatePublicKey(base64PublicKey)
return verify(key, signedData, signature)
}
/**
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
*
* @param encodedPublicKey Base64-encoded public key
* @throws IllegalArgumentException if encodedPublicKey is invalid
*/
fun generatePublicKey(encodedPublicKey: String?): PublicKey {
return try {
val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT)
val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM)
keyFactory.generatePublic(X509EncodedKeySpec(decodedKey))
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
} catch (e: InvalidKeySpecException) {
Log.e(TAG, "Invalid key specification.")
throw IllegalArgumentException(e)
}
}
/**
* Verifies that the signature from the server matches the computed
* signature on the data. Returns true if the data is correctly signed.
*
* @param publicKey public key associated with the developer account
* @param signedData signed data from server
* @param signature server signature
* @return true if the data and signature match
*/
fun verify(publicKey: PublicKey?, signedData: String, signature: String?): Boolean {
val signatureBytes: ByteArray = try {
Base64.decode(signature, Base64.DEFAULT)
} catch (e: IllegalArgumentException) {
Log.e(TAG, "Base64 decoding failed.")
return false
}
try {
val sig = Signature.getInstance(SIGNATURE_ALGORITHM)
sig.initVerify(publicKey)
sig.update(signedData.toByteArray())
if (!sig.verify(signatureBytes)) {
Log.e(TAG, "Signature verification failed.")
return false
}
return true
} catch (e: NoSuchAlgorithmException) {
Log.e(TAG, "NoSuchAlgorithmException.")
} catch (e: InvalidKeyException) {
Log.e(TAG, "Invalid key specification.")
} catch (e: SignatureException) {
Log.e(TAG, "Signature exception.")
}
return false
}
}
\ No newline at end of file
/* Copyright (c) 2012 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 org.videolan.vlc.donations.util
import org.json.JSONObject
/**
* Represents an in-app product's listing details.
*/
class SkuDetails(private val mItemType: String, private val mJson: String) {
val sku: String
val type: String
val price: String
val priceAmountMicros: Long
val priceCurrencyCode: String
val title: String
val description: String
constructor(jsonSkuDetails: String) : this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails) {}
override fun toString(): String {
return "SkuDetails:$mJson"
}
init {
val o = JSONObject(mJson)
sku = o.optString("productId")
type = o.optString("type")
price = o.optString("price")
priceAmountMicros = o.optLong("price_amount_micros")
priceCurrencyCode = o.optString("price_currency_code")
title = o.optString("title")
description = o.optString("description")
}
}
\ No newline at end of file
/**
* **************************************************************************
* WarningActivity.java
* ****************************************************************************
* Copyright © 2016 VLC authors and VideoLAN
* Author: Geoffrey Métais
*
/*
* ************************************************************************
* ExampleUnitTest.kt
* *************************************************************************
* Copyright © 2020 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
......@@ -18,20 +17,25 @@
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
* ***************************************************************************
* **************************************************************************
*
*
*/
package org.videolan.vlc.extensions.api;
import android.app.Activity;
import android.os.Bundle;
package org.videolan.vlc.donations
import org.videolan.vlc.extensions.api.tools.Dialogs;
import org.junit.Test
public class WarningActivity extends Activity {
import org.junit.Assert.*
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Dialogs.showInstallVlc(this);
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
}
\ No newline at end of file
......@@ -24,15 +24,13 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
compileSdk rootProject.ext.compileSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
......@@ -43,6 +41,14 @@ android {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
vlcBundle {
initWith release
matchingFallbacks = ['release']
}
}
namespace 'org.videolan.liveplotgraph'
buildFeatures {
buildConfig true
}
}
......@@ -50,12 +56,12 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation "androidx.appcompat:appcompat:$rootProject.ext.appCompatVersion"
implementation "androidx.core:core-ktx:$rootProject.ext.androidxCoreVersion"
implementation project(':application:tools')
implementation "androidx.constraintlayout:constraintlayout:$rootProject.ext.constraintLayoutVersion"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
testImplementation "junit:junit:$rootProject.ext.junitVersion"
androidTestImplementation "androidx.test.ext:junit:$rootProject.ext.junitExtVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$rootProject.ext.espressoVersion"
}
......@@ -22,5 +22,4 @@
~
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.videolan.liveplotgraph" />
<manifest />
......@@ -26,6 +26,7 @@ package org.videolan.liveplotgraph
import android.app.Activity
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.util.Log
import android.view.ViewGroup
......@@ -56,11 +57,11 @@ class LegendView : ConstraintLayout, PlotViewDataChangeListener {
private fun initAttributes(attrs: AttributeSet, defStyle: Int) {
attrs.let {
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.LPGPlotView, 0, defStyle)
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.LPGLegendView, 0, defStyle)
try {
plotViewId = a.getResourceId(a.getIndex(R.styleable.LPGLegendView_lpg_plot_view), -1)
plotViewId = a.getResourceId(R.styleable.LPGLegendView_lpg_plot_view, -1)
} catch (e: Exception) {
Log.w("", e.message, e)
Log.w("LegendView", e.message, e)
} finally {
a.recycle()
}
......@@ -69,9 +70,15 @@ class LegendView : ConstraintLayout, PlotViewDataChangeListener {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
plotView = (context as Activity).findViewById(plotViewId)
if (!::plotView.isInitialized) throw IllegalStateException("A valid plot view has to be provided")
plotView.addListener(this)
//workaround for editor
if (!isInEditMode) {
(context as? Activity)?.let { activity ->
activity.findViewById<PlotView>(plotViewId)?.let {
plotView = it
plotView.addListener(this)
}
} ?: Log.w("LegendView", "Cannot find the plot view with id $plotViewId")
}
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
}
......
......@@ -85,8 +85,8 @@ class PlotView : FrameLayout {
}
invalidate()
val listenerValue = ArrayList<Pair<LineGraph, String>>(data.size)
data.forEach { lineGraph ->
listenerValue.add(Pair(lineGraph, "${String.format("%.0f", lineGraph.data[lineGraph.data.keys.max()])} kb/s"))
data.forEach { line ->
listenerValue.add(Pair(line, "${String.format("%.0f", line.data[line.data.keys.maxOrNull()])} kb/s"))
}
listeners.forEach { it.onDataChanged(listenerValue) }
}
......@@ -101,7 +101,7 @@ class PlotView : FrameLayout {
listeners.remove(listener)
}
override fun onDraw(canvas: Canvas?) {
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
maxsY.clear()
......@@ -109,19 +109,19 @@ class PlotView : FrameLayout {
minsX.clear()
data.forEach {
maxsY.add(it.data.maxBy { it.value }?.value ?: 0f)
maxsY.add(it.data.maxByOrNull { it.value }?.value ?: 0f)
}
val maxY = maxsY.max() ?: 0f
val maxY = maxsY.maxOrNull() ?: 0f
data.forEach {
maxsX.add(it.data.maxBy { it.key }?.key ?: 0L)
maxsX.add(it.data.maxByOrNull { it.key }?.key ?: 0L)
}
val maxX = maxsX.max() ?: 0L
val maxX = maxsX.maxOrNull() ?: 0L
data.forEach {
minsX.add(it.data.minBy { it.key }?.key ?: 0L)
minsX.add(it.data.minByOrNull { it.key }?.key ?: 0L)
}
val minX = minsX.min() ?: 0L
val minX = minsX.minOrNull() ?: 0L
drawLines(maxY, minX, maxX, canvas)
drawGrid(canvas, maxY, minX, maxX)
......
......@@ -25,18 +25,20 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
packagingOptions {
pickFirst '**/*.so'
jniLibs {
pickFirsts += ['**/*.so']
}
}
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
compileSdk rootProject.ext.compileSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
......@@ -51,22 +53,26 @@ android {
initWith debug
matchingFallbacks = ['debug']
}
vlcBundle {
initWith release
matchingFallbacks = ['release']
}
}
namespace 'org.videolan.vlc.mediadb'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// fixme When we update to 1.2.0 remember to remove the workaround in SecondaryActivity.applyOverrideConfiguration
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation "androidx.appcompat:appcompat:$rootProject.ext.appCompatVersion"
implementation "androidx.core:core-ktx:$rootProject.ext.androidxCoreVersion"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
testImplementation "junit:junit:$rootProject.ext.junitVersion"
androidTestImplementation "androidx.test.ext:junit:$rootProject.ext.junitExtVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$rootProject.ext.espressoVersion"
//Room
implementation "androidx.room:room-ktx:$rootProject.ext.roomVersion"
kapt ('org.xerial:sqlite-jdbc:3.36.0')
kapt "androidx.room:room-compiler:$rootProject.ext.roomVersion"
implementation project(':application:tools')
implementation project(':application:resources')
......
......@@ -22,5 +22,4 @@
~
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.videolan.vlc.mediadb" />
<manifest />
......@@ -41,7 +41,7 @@ interface BrowserFavDao {
fun getAll(): Flow<List<BrowserFav>>
@Query("SELECT * from fav_table where type = 0")
fun getAllNetwrokFavs(): LiveData<List<BrowserFav>>
fun getAllNetworkFavs(): Flow<List<BrowserFav>>
@Query("SELECT * from fav_table where type = 1")
fun getAllLocalFavs(): LiveData<List<BrowserFav>>
......