Callbacks not working for TikTok Login kit on Android - android

I have been following the documentation for integrating TikTok's login kit for Android. Here is my complete Activity for receiving the callbacks from the IAPIEventHandler interfact provided by the TikTok SDK:
internal class ATikTokAuth : BaseActivity(), IApiEventHandler {
private val clientKey = TIKTOK_CLIENT_KEY
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.a_tik_tok_auth)
val tiktokOpenConfig = TikTokOpenConfig(clientKey)
TikTokOpenApiFactory.init(tiktokOpenConfig)
val tikTokOpenApi = TikTokOpenApiFactory.create(this)
tikTokOpenApi.handleIntent(intent, this)
val request = Authorization.Request()
request.scope = "user.info.basic"
request.state = "starting"
request.callerLocalEntry = "com.package.name.ATikTokAuth"
tikTokOpenApi.authorize(request)
}
override fun onReq(request: BaseReq?) {
Timber.d("onRequest called: ${request?.extras}")
}
override fun onResp(resp: BaseResp?) {
Timber.d("onResponse: isSuccess: ${resp?.isSuccess} If not, error: ${resp?.errorMsg}")
if (resp is Authorization.Response) {
val code = resp.authCode
Timber.d("onResponse authcode: $code ")
requestAccessToken(resp.authCode)
}
}
override fun onErrorIntent(intent: Intent?) {
Timber.d("onErrorIntent ${intent?.extras}")
}
}
Running this code creates the webview for the user to select a means to log in to TikTok and connect that their TikTok account to my app, but after authorization the user is returned to this activity without onResp being called. onErrorIntent() is called when the webview is launched, but the intent has no data and thus no information useful for debugging.
Also, Although the documentation initializes TikTokOpenConfig like this:
TikTokOpenConfig tiktokOpenConfig = new TikTokOpenConfig(clientKey);
TikTokOpenApiFactory.init(new TikTokOpenConfig(tiktokOpenConfig));
The TikTokOpenConfig only takes a string argument of clientKey, so I assumed it should be
val tiktokOpenConfig = TikTokOpenConfig(clientKey)
TikTokOpenApiFactory.init(tiktokOpenConfig)
I saw no other way since the code in the documentation wouldn't even compile
TikTok is declared as
implementation 'com.bytedance.ies.ugc.aweme:opensdk-oversea-external:0.2.0.2'
in my manifest. What am I doing wrong?

You should add android:exported="true" in manifest file.
<activity
android:name=".ATikTokAuth"
android:exported="true">
</activity>

I moved the initialization of tikTokOpenConfig
val tiktokOpenConfig = TikTokOpenConfig(clientKey)
TikTokOpenApiFactory.init(tiktokOpenConfig)
into a custom App class.

Related

How to fix android run time error in Stripe Terminal discover device code build using kotlin

I am trying to integrate stripe terminal code with my android app build using kotlin, unfortunately I am getting the following run time error which I could not able to fix
java.lang.IllegalStateException: initTerminal must be called before attempting to get the instance
The code I have added is used below
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pay_screen)
onDiscoverReaders()
}
fun onDiscoverReaders() {
val config = DiscoveryConfiguration(
timeout = 0,
discoveryMethod = DiscoveryMethod.LOCAL_MOBILE,
isSimulated = false,
location = "xxxxxxxxxxxxxxx"
)
// Save this cancelable to an instance variable
discoveryCancelable = Terminal.getInstance().discoverReaders(config,
discoveryListener = object : DiscoveryListener {
override fun onUpdateDiscoveredReaders(readers: List<Reader>) {
}
}
, object : Callback {
override fun onSuccess() {
println("Finished discovering readers")
}
override fun onFailure(e: TerminalException) {
e.printStackTrace()
}
})
}
I have added this to one of my activity and my intention is to check if my phone is supporting stripe tap on mobile
I guess the issue could be calling onDiscoverReaders() from a wrong place, someone please help me to fix this issue
Thanks in advance
In stripe docs you can check
// Create your listener object. Override any methods that you want to be notified about
val listener = object : TerminalListener {
}
// Choose the level of messages that should be logged to your console
val logLevel = LogLevel.VERBOSE
// Create your token provider.
val tokenProvider = TokenProvider()
// Pass in the current application context, your desired logging level, your token provider, and the listener you created
if (!Terminal.isInitialized()) {
Terminal.initTerminal(applicationContext, logLevel, tokenProvider, listener)
}
// Since the Terminal is a singleton, you can call getInstance whenever you need it
Terminal.getInstance()
might be you missed to initialise terminal before getting Instance so try add above code before onDiscoverReaders()
The error speaks for itself - first you need to initialize the api terminal, and then call the terminal instance.
Based on the documentation, we follow the following steps to get started with the api terminal:
Initialize the terminal application in the application class of the
application
class App : Application() {
override fun onCreate() {
super.onCreate()
TerminalApplicationDelegate.onCreate(this)
}
}
We request the necessary permissions for correct work with the
terminal search (bluetooth, geolocation), if everything is provided,
we call the init terminal with parameters like that:
Terminal.initTerminal(
context = context,
logLevel = LogLevel.VERBOSE,
tokenProvider = TokenProvider(),
listener = object : TerminalListener {
override fun onUnexpectedReaderDisconnect(reader: Reader) {
Log.d("log", "onUnexpectedReaderDisconnect")
}
override fun onConnectionStatusChange(status: ConnectionStatus) {
super.onConnectionStatusChange(status)
Log.d("log", "onConnectionStatusChange")
}
override fun onPaymentStatusChange(status: PaymentStatus) {
super.onPaymentStatusChange(status)
Log.d("log", "onPaymentStatusChange")
}
}
)
After this initialization, you can call the terminal instance and
work with it.

Integer Value increase with TextView after Ad Show

ok I am working on concept idea my dad has pitched to me. I have an app that runs AdMobs. On the interstitial ads based off button. The idea of the app is you press the start button and you watch an ad. However, when the ad is closed out, the value should increase in the Ads Watched Field.
I have created a function that increases the TextView no problem. My issue is with AdMob functions, when I call the function in AdDismissed, it does not change the value. I can plug the function into the Start Button and it increases value, but when the Ad is dismissed it zeros out the textView.
I am showing the demo portion of the app, this is still experimental, but also learning with Admobs and the coding on functions. Any advice would be appreciated. Also the adCounter is in the stop button, that was just to make sure the increments where firing. Which it does work perfectly. My thing is when the ad ends keeping the value.
SO in example the Ads Watched: 167,897,256 should increment by one when the ad is dismissed. However placing adCount() in the dismissed section of the ad does not work it just zeros out that textView.
MainActivity
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.ads.*
import com.google.android.gms.ads.interstitial.InterstitialAd
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
class MainActivity : AppCompatActivity() {
lateinit var mAdView : AdView
private var mInterstitialAd: InterstitialAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadBanner()
loadInterAd()
val interAdBtnStart : Button = findViewById(R.id.btnStartAds)
val interAdBtnStop : Button = findViewById(R.id.btnStopAds)
interAdBtnStart.setOnClickListener {
showInterAd()
}
interAdBtnStop.setOnClickListener {
adCountInc()
}
}
fun adCountInc(){
val tvAdsAmount : TextView = findViewById(R.id.tvAdsAmount)
var i : Int = tvAdsAmount.text.toString().toInt()
tvAdsAmount.text = "${++i}"
}
private fun showInterAd() {
if (mInterstitialAd != null)
{
mInterstitialAd?.fullScreenContentCallback = object : FullScreenContentCallback(){
override fun onAdClicked() {
super.onAdClicked()
}
override fun onAdDismissedFullScreenContent() {
super.onAdDismissedFullScreenContent()
val intent = Intent(this#MainActivity, MainActivity::class.java)
startActivity(intent)
}
override fun onAdFailedToShowFullScreenContent(p0: AdError) {
super.onAdFailedToShowFullScreenContent(p0)
}
override fun onAdImpression() {
super.onAdImpression()
}
override fun onAdShowedFullScreenContent() {
super.onAdShowedFullScreenContent()
}
}
mInterstitialAd?.show(this)
}
else
{
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
private fun loadInterAd() {
var adRequest = AdRequest.Builder().build()
InterstitialAd.load(this,"ca-app-pub-3940256099942544/1033173712", adRequest, object : InterstitialAdLoadCallback() {
override fun onAdFailedToLoad(adError: LoadAdError) {
mInterstitialAd = null
}
override fun onAdLoaded(interstitialAd: InterstitialAd) {
mInterstitialAd = interstitialAd
}
})
}
private fun loadBanner() {
MobileAds.initialize(this) {}
mAdView = findViewById(R.id.adView)
val adRequest = AdRequest.Builder().build()
mAdView.loadAd(adRequest)
mAdView.adListener = object: AdListener() {
override fun onAdLoaded() {
// Code to be executed when an ad finishes loading.
}
override fun onAdFailedToLoad(adError : LoadAdError) {
// Code to be executed when an ad request fails.
}
override fun onAdOpened() {
// Code to be executed when an ad opens an overlay that
// covers the screen.
}
override fun onAdClicked() {
// Code to be executed when the user clicks on an ad.
}
override fun onAdClosed() {
// Code to be executed when the user is about to return
// to the app after tapping on an ad.
}
}
}
}
this is the full code to the app so far. Any advice will help. If i place the adCounter() anywhere in the ads section it will not update the textfield at all. Even after the textfield shows 1 then an ad is displayed it will always zero out the text field.
The value is not updated because you are opening the same Activity (MainActivity) on onAdDismissedFullScreenContent again.
First create a global TextView variable like:
private lateinit var tvAdsAmount : TextView`\
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvAdsAmount = findViewById(R.id.tvAdsAmount)
// Other things...
}
Then simply use:
override fun onAdDismissedFullScreenContent() {
val value = tvAdsAmount.text.toString().toInt()
// make sure that value is an Integer.
val updateValue = value++
tvAdsAmount.text = "$updatedValue"
}
Some points you should learn:
Every time you start a new Activity, you're getting a new TextView that has no memory of what was in a TextView of some previous Activity.
You should never use a UI component like TextView to store application state. It's just not reliable. UI components are intended to be a bridge between your application state and the user. They aren't supposed to be the application state themselves. This is the programming principle of separation of concerns.
Since you're not finishing the previous Activity, you're building up a large stack of duplicate Activities. The user will be surprised when they push the back button to see an outdated copy of the Activity, one after another.
Whenever there's a configuration change (such as a screen rotation, or the user changing some setting in the Android settings like the device language), Android destroys all of the Activities that you have open and creates new instances of them. So any application state you were holding in them is going to be lost. This is why there is a ViewModel class for holding state that will survive configuration changes.
To fix your app:
Change your logic so you aren't starting new Activities. Keep everything in the same Activity instance. If you want to load a new ad, just call the function that loads ads rather than creating a brand new Activity.
Create a ViewModel to hold your application state. In this case, it will just need a LiveData<Int> to hold your count. You can observe this LiveData in your Activity and update the value of the TextView in the observer function. Your ViewModel can have an increment function that increases the LiveData's integer value, and you'll call this after ads are dismissed.
Long term, you can consider backing up this value with SharedPreferences, so the value will persist between sessions of your app.
Points 2 and 3 have many tutorials online and questions on this site about them, so I'm not going to explain them in detail.

Android 11 - CallRedirectionService not getting triggered

We need to invoke a service when an outgoing call is placed so that we can use the target number to show certain additional information. As per Android documentation, CallRedirectionService should be used. However, after declaring a custom service as depicted in documentation, we find that the custom service is not getting triggered. Please let us know what we are doing wrong. Appreciate your help.
I referred to this link as well but not clear on the answer. There is a mention to role acquisition but I did not find that in Android documentation. Please direct me to the relevant page if available.
CallRedirectionService Implementation not working
Manifest.xml
<service android:name="<mypackage>.CustomCallService"
android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
<intent-filter>
<action android:name="android.telecom.CallRedirectionService"/>
</intent-filter>
</service>
Custom service code
#Override
public void onPlaceCall(#NonNull Uri handle, #NonNull PhoneAccountHandle initialPhoneAccount, boolean allowInteractiveResponse) {
System.out.println("Outgoing:" + initialPhoneAccount + ":" + handle); //Call does not reach here
placeCallUnmodified();
}
In Kotlin:
Your implementation of the CallRedirectionService seems correct. I understand that the only step missing is the role request and acquisition.
You can prompt the user to give you the CallRedirectionService role by using the RoleManager class.
In this example below, we are requesting this role as soon as the MainActivity is created:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (!isRedirection())
roleAcquire(RoleManager.ROLE_CALL_REDIRECTION)
}
}
The following functions shall be used:
private fun isRedirection(): Boolean {
return isRoleHeldByApp(RoleManager.ROLE_CALL_REDIRECTION)
}
private fun isRoleHeldByApp(roleName: String): Boolean {
val roleManager: RoleManager? = getSystemService(RoleManager::class.java)
return roleManager!!.isRoleHeld(roleName)
}
private fun roleAcquire(roleName: String) {
val roleManager: RoleManager?
if (roleAvailable(roleName)) {
roleManager = getSystemService(RoleManager::class.java)
val intent = roleManager.createRequestRoleIntent(roleName)
startActivityForResult(intent, 1)
} else {
Toast.makeText(
this,
"Redirection call with role in not available",
Toast.LENGTH_SHORT
).show()
}
}
private fun roleAvailable(roleName: String): Boolean {
val roleManager: RoleManager? = getSystemService(RoleManager::class.java)
return roleManager!!.isRoleAvailable(roleName)
}

Using AuroraStore code, how does it get the "library" (history of installed apps), and how to get the time they were installed?

Background
In the past, I've found a special app called "Purchased apps" that somehow gets a list of the apps you've purchased. Not seeing any API for this, I asked how does it do it (and sadly still couldn't find a clear answer and a POC to demonstrate it).
The problem
Time passed, and I've noticed there is actually an open sourced app called "Aurora Store" (repository here) that can get about as much information as the Play Store. Screenshot from it:
Thing is, I got issues trying to figure out how to use its code properly, and the weird thing is that those apps get the information from different sources.
What I've tried
So, seeing it allows you to login to Google, and then get the "library" information (history of installed apps), I decided to give it a go (full sample on Github, here) :
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
private val cookieManager = CookieManager.getInstance()
#SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val cachedEmail = defaultSharedPreferences.getString("email", null)
val cachedAasToken = defaultSharedPreferences.getString("aasToken", null)
if (cachedEmail != null && cachedAasToken != null) {
onGotAasToken(applicationContext, cachedEmail, cachedAasToken)
} else {
webView = findViewById(R.id.webView)
cookieManager.removeAllCookies(null)
cookieManager.acceptThirdPartyCookies(webView)
cookieManager.setAcceptThirdPartyCookies(webView, true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
webView.settings.safeBrowsingEnabled = false
}
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
val cookies = CookieManager.getInstance().getCookie(url)
val cookieMap: MutableMap<String, String> = AC2DMUtil.parseCookieString(cookies)
val oauthToken: String? = cookieMap[AUTH_TOKEN]
oauthToken?.let {
webView.evaluateJavascript("(function() { return document.getElementById('profileIdentifier').innerHTML; })();") {
val email = it.replace("\"".toRegex(), "")
Log.d("AppLog", "got email?${email.isNotBlank()} got oauthToken?${oauthToken.isNotBlank()}")
buildAuthData(applicationContext, email, oauthToken)
}
} ?: Log.d("AppLog", "could not get oauthToken")
}
}
webView.settings.apply {
allowContentAccess = true
databaseEnabled = true
domStorageEnabled = true
javaScriptEnabled = true
cacheMode = WebSettings.LOAD_DEFAULT
}
webView.loadUrl(EMBEDDED_SETUP_URL)
}
}
companion object {
const val EMBEDDED_SETUP_URL =
"https://accounts.google.com/EmbeddedSetup/identifier?flowName=EmbeddedSetupAndroid"
const val AUTH_TOKEN = "oauth_token"
private fun buildAuthData(context: Context, email: String, oauthToken: String?) {
thread {
try {
val aC2DMResponse: Map<String, String> =
AC2DMTask().getAC2DMResponse(email, oauthToken)
val aasToken = aC2DMResponse["Token"]!!
PreferenceManager.getDefaultSharedPreferences(context)
.edit().putString("email", email).putString("aasToken", aasToken).apply()
onGotAasToken(context, email, aasToken)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun onGotAasToken(context: Context, email: String, aasToken: String) {
thread {
val properties = NativeDeviceInfoProvider(context).getNativeDeviceProperties()
val authData = AuthHelper.build(email, aasToken, properties)
val purchaseHelper = PurchaseHelper(authData).using(HttpClient.getPreferredClient())
var offset = 0
Log.d("AppLog", "list of purchase history:")
while (true) {
val purchaseHistory = purchaseHelper.getPurchaseHistory(offset)
if (purchaseHistory.isNullOrEmpty())
break
val size = purchaseHistory.size
offset += size
purchaseHistory.forEach {
Log.d("AppLog", "${it.packageName} ${it.displayName}")
}
}
Log.d("AppLog", "done")
}
}
}
}
It seems it got the token it needs (and the email), but sadly it seems to get 2 apps and that's it, and then when I try to get the next ones, I get the same 2 apps, twice more, meaning as such:
list of purchase history:
dev.southpaw.dungeon Dungeon Live Wallpaper
com.crydata.mylivewallpaper Hex AMOLED Neon Live Wallpaper 2021
dev.southpaw.dungeon Dungeon Live Wallpaper
com.crydata.mylivewallpaper Hex AMOLED Neon Live Wallpaper 2021
dev.southpaw.dungeon Dungeon Live Wallpaper
com.crydata.mylivewallpaper Hex AMOLED Neon Live Wallpaper 2021
and on the last time it tries to get the next chunk of apps, it crashes with this exception:
FATAL EXCEPTION: Thread-4
Process: com.lb.getplaystoreinstalledappshistory, PID: 6149
Server(code=400, reason=Bad Request)
at com.aurora.gplayapi.helpers.AppDetailsHelper.getAppByPackageName(AppDetailsHelper.kt:115)
at com.aurora.gplayapi.helpers.PurchaseHelper.getPurchaseHistory(PurchaseHelper.kt:63)
at com.lb.getplaystoreinstalledappshistory.MainActivity$Companion$onGotAasToken$1.invoke(MainActivity.kt:96)
at com.lb.getplaystoreinstalledappshistory.MainActivity$Companion$onGotAasToken$1.invoke(MainActivity.kt:68)
at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
The questions
What's wrong with how I tried to get the list of apps? How can I get it right, ordered by time installed?
Is there any way to get the time they were installed (or any clue about it) ? Somehow the "Purchased apps" app got the time. Granted it was only for purchased apps, but still...
The "Purchased apps" app even got login better, as it doesn't require user-name and password. Instead it offers a dialog to choose the account. Assuming I get it right, is it possible to get the same information using the same login dialog ?
Not sure if this information is remotely useful but might as well mention it...
I accidentally decompiled their archived APK from 2015 when they didn't minimize their code and at least back then, they were using a JSoup HTML parser spider. Possibly they still are, which is probably not allowed by Google and incredibly prone to maintenance.

How to test whether the activity has not been routed with Robolectric?

I have an activity which is as simple as this:
class HomeActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
if (!this.userPreference.memberRegistered) {
goToActivity(AuthActivity::class.java)
}
}
}
So if the user is registered, it will stay at HomeActivity, otherwise it will route to AuthActivity.
userPreference is a wrapper around SharedPreference. Works well and well tested in another codebase.
I first tried whether RuntimeEnvironment from Robolectric could do the trick or not, and it works of course.
#Test
fun try_test() {
val userPreference = UserPreference(
Settings(SETTINGS_NAME, RuntimeEnvironment.application.applicationContext)
)
// default value should be false, PASS
assertEquals(false, userPreference.memberRegistered)
// change it to true, then it should be true, PASS
userPreference.memberRegistered = true
assertEquals(true, userPreference.memberRegistered)
}
Then I tried whether the route to AuthActivity works or not, and it works. The test pass.
#Test
fun should_go_to_AuthActivity_when_1st_start() {
val homeActivity = Robolectric.setupActivity(HomeActivity::class.java)
val expectedIntent = Intent(homeActivity, AuthActivity::class.java)
val actual = ShadowApplication.getInstance().nextStartedActivity
assertEquals(expectedIntent.component, actual.component)
}
Problem
Then the problem is I don't know how to check if the activity stays at HomeActivty when this.userPreference.memberRegistered = true. The following test failed due to the reason that actual is null. It is because there is no routing happen, so nextStartedActivity is null, but how to verify that it stays at this activity from the test?
#Test
fun should_stay_at_HomeActivity_when_already_member() {
val userPreference = UserPreference(
Settings(SETTINGS_NAME, RuntimeEnvironment.application.applicationContext)
)
userPreference.memberRegistered = true
val actual = ShadowApplication.getInstance().nextStartedActivity
assertEquals(
actual.component.shortClassName,
".ui.home.HomeActivity"
)
}

Categories

Resources