I am trying to develop a small app that can send WhatsApp messages without user interaction,
I know that this is the kind of problem that may a lot of people are trying to solve.
I am using accessibility services to do such things.
This is a sample of my code.
Accessibility Service Class
class WhatsappAccessibilityService : AccessibilityService() {
override fun onAccessibilityEvent(event: AccessibilityEvent) {
if (rootInActiveWindow == null) {
return
}
val rootInActiveWindow = AccessibilityNodeInfoCompat.wrap(rootInActiveWindow)
val messageNodeList = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.whatsapp:id/entry")
if (messageNodeList == null || messageNodeList.isEmpty()) {
return
}
val messageField = messageNodeList[0]
if (messageField.text == null || messageField.text.length == 0 || !messageField.text.toString()
.endsWith("mysuffix")
) {
return
}
val sendMessageNodeInfoList = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.whatsapp:id/send")
if (sendMessageNodeInfoList == null || sendMessageNodeInfoList.isEmpty()) {
return
}
val sendMessageButton = sendMessageNodeInfoList[0]
if (!sendMessageButton.isVisibleToUser) {
return
}
sendMessageButton.performAction(AccessibilityNodeInfo.ACTION_CLICK)
try {
Thread.sleep(500)
performGlobalAction(GLOBAL_ACTION_BACK)
Thread.sleep(500)
} catch (ignored: InterruptedException) {
}
performGlobalAction(GLOBAL_ACTION_BACK)
}
override fun onInterrupt() {
TODO("Not yet implemented")
}
And this is my whatsapp_service.xml file
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeWindowContentChanged"
android:packageNames="com.whatsapp"
android:accessibilityFeedbackType="feedbackSpoken"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"/>
This is my AndroidManifest.xml file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.messagewithwhatsapp">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.MessageWithWhatsapp">
<service
android:name=".WhatsappAccessibilityService"
android:exported="true"
android:label="My Accessibility Service" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<meta-data
android:name="android.accessibilityservice"
android:resource="#xml/whatsapp_service"/>
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
</service>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
And finally, this is how I implemented the send login.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnSend.setOnClickListener {
if (!isAccessibilityOn(this#MainActivity, WhatsappAccessibilityService::class.java)) {
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
this#MainActivity.startActivity(intent)
} else {
val smsNumber = binding.txfPhone.editText?.text.toString()
val sendIntent = Intent(Intent.ACTION_SEND)
sendIntent.type = "text/plain"
sendIntent.putExtra(Intent.EXTRA_TEXT, "${binding.txtfMessage.editText?.text} mysuffix")
sendIntent.putExtra("jid", "$smsNumber#s.whatsapp.net")
sendIntent.setPackage("com.whatsapp")
startActivity(sendIntent)
}
}
}
private fun isAccessibilityOn(context: Context, clazz: Class<out AccessibilityService?>): Boolean {
var accessibilityEnabled = 0
val service: String = context.packageName + "/" + clazz.canonicalName
try {
accessibilityEnabled = Settings.Secure.getInt(
context.applicationContext.contentResolver,
Settings.Secure.ACCESSIBILITY_ENABLED
)
} catch (ignored: Settings.SettingNotFoundException) {
}
val colonSplitter = SimpleStringSplitter(':')
if (accessibilityEnabled == 1) {
val settingValue: String = Settings.Secure.getString(
context.applicationContext.contentResolver,
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
)
colonSplitter.setString(settingValue)
while (colonSplitter.hasNext()) {
val accessibilityService = colonSplitter.next()
if (accessibilityService.equals(service, ignoreCase = true)) {
return true
}
}
}
return false
}
}
What happen are:
I press the send button in my app and firstly my app checks for Accessibility Service.
If the Accessibility service is off then my app will launch system settings to grand the Service.
But if the Accessibility service is on for my app service then it should open WhatsApp and start the service to allow clicking on the WhatsApp send button.
My service not working I tried to use the Log class but nothing show in the run for android studio.
Please anyone can help me and tell me how to do that.
Related
I am making a library that can be used to incorporate breaking and entering detection into any application
There is an arduino set to the alarm of the house which sends an SMS to a specific phone upon trigger
Within my sdk I register an sms receiver which upon receiving an sms with a specific text, should show a full screen activity (on top of the lockscreen too) that will alert the user
I created an application to test this behaviour
the application's package is : com.example.demo
the library's package is : com.example.sdk
the sms receiver looks like this:
class SMSReceiver : BroadcastReceiver() {
companion object {
const val TAG = "SMSReceiver"
}
private val logger by lazy { Injections.logger }
override fun onReceive(context: Context?, intent: Intent?) {
logger.log(TAG) { "Got sms" }
val ctx = context ?: return
val bundle = intent?.extras ?: return
val format = bundle.getString("format") ?: return
val pdus = (bundle["pdus"] as? Array<*>) ?: return
for (idx in pdus.indices) {
val pdu = pdus[idx] as? ByteArray ?: continue
val msg = SmsMessage.createFromPdu(pdu, format)
if (msg.messageBody.startsWith("theft event", true)) {
logger.log(TAG) { "Got theft event" }
abortBroadcast()
showTheftActivity(ctx, msg.messageBody)
break
}
}
}
private fun showTheftActivity(context: Context, messageBody: String) {
val intent = Intent(context, TheftActivity::class.java)
intent.addFlags(Intent.FLAG_FROM_BACKGROUND)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// .addCategory(Intent.CATEGORY_LAUNCHER)
val location = messageBody.split(" ").getOrNull(2)
if (location != null) {
val coords = location.split(",")
if (coords.size == 2) {
val x = coords[0].toBigDecimalOrNull()
val y = coords[1].toBigDecimalOrNull()
if (x != null && y != null) {
intent.putExtra(TheftActivity.X, x.toString())
intent.putExtra(TheftActivity.Y, y.toString())
}
}
}
context.startActivity(intent)
}
}
the activity that should show on top of everything is this :
class TheftActivity : Activity() {
companion object {
const val X = "locationX"
const val Y = "locationY"
}
private val x: String? by lazy { intent.getStringExtra(X) }
private val y: String? by lazy { intent.getStringExtra(Y) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_theft)
val location = findViewById<Button>(R.id.locate)
if (x != null && y != null) {
location.setOnClickListener { Toast.makeText(applicationContext, "Going to $x , $y", Toast.LENGTH_SHORT).show() }
location.isEnabled = true
finish()
} else {
location.isEnabled = false
}
findViewById<Button>(R.id.cancel).setOnClickListener {
finish()
}
turnScreenOnAndKeyguardOff()
}
private fun turnScreenOnAndKeyguardOff() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
setTurnScreenOn(true)
} else {
window.addFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
)
}
with(getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
requestDismissKeyguard(this#TheftActivity, null)
}
}
}
override fun onDestroy() {
super.onDestroy()
turnScreenOffAndKeyguardOn()
}
private fun turnScreenOffAndKeyguardOn() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(false)
setTurnScreenOn(false)
} else {
window.clearFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
)
}
}
}
and the sdk's android manifest contains this:
<application>
<activity
android:name=".ui.TheftActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:excludeFromRecents="true"
android:label="#string/title_activity_theft"
android:showOnLockScreen="true"
android:theme="#style/Theme.Sdk.Fullscreen" />
<receiver
android:name=".receivers.SMSReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
when testing this on an emulator I send the sms to trigger the theft event
if the activity for testing (the one in the com.example.demo package) is not closed then it is brought to the front , but if it is closed nothing happens (though I do see the log messages from the receiver)
how can I make my sms receiver open the TheftActivity instead of the main activity from the main package?
edit: if it helps, the theft activity seems to start and then get immediately destroyed
It looks like the system can't bring the activity to the foreground due to the restrictions implemented in Android Q
With Android Q, it is impossible to start an activity from the background automatically if your app does not include those exceptions listed in the link below.
https://developer.android.com/guide/components/activities/background-starts
For possible solutions :
https://stackoverflow.com/a/59421118/11982611
I have successfully implemented SMS retriever in my application using the guideline here . My Code is working fine and otp is auto populated in many devices but some of the devices like vivo v15 pro, redmi note 4 it is not working (BroadcastReceiver's onReceive() not get triggered) . i have attached my code here. check and let me know if you have any solution for this. Thanks
Manifest.xml
<receiver
android:name=".sms.SMSRetrieverBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED" />
</intent-filter>
</receiver>
SMSRetrieverBroadcastReceiver.kt
class SMSRetrieverBroadcastReceiver : BroadcastReceiver() {
companion object{
private var otpReceiver: OtpReceiver? = null
fun initOTPListener(receiver: OtpReceiver) {
this.otpReceiver = receiver
}
}
override fun onReceive(context: Context, intent: Intent) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val status = extras!!.get(SmsRetriever.EXTRA_STATUS) as Status
when (status.statusCode) {
CommonStatusCodes.SUCCESS -> {
// Get SMS message contents
var otp: String = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as String
Timber.d("OTP_Message "+otp)
if(otpReceiver != null) {
otpReceiver!!.onOTPReceived(otp)
}
}
CommonStatusCodes.TIMEOUT -> {
// Waiting for SMS timed out (5 minutes)
// Handle the error ...
if(otpReceiver != null) {
otpReceiver!!.onOTPTimeOut()
}
}
}
}}
MainActivity.kt
fun startListeningForSMS() {
SMSRetrieverBroadcastReceiver.initOTPListener(this)
startSmsListener()
}
private fun startSmsListener() {
val client = SmsRetriever.getClient(mContext)
val task = client.startSmsRetriever()
task.addOnSuccessListener {
Timber.d("Success")
}
task.addOnFailureListener {
Timber.d("Failed")
}
}
override fun onOTPReceived(otp: String) {
setReceivedOtp(otp)
}
override fun onOTPTimeOut() {
setReceivedOtp(null)
}
Go to Messeges-click on (:) on top left ->Go to setting->Disable prevention from Otp verification.It worked fine in my case.
Can you check with this intent-filter action:
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
I just want to ask why StartActivity(intent) failed?
Then logcat show :
D/AddPhotosFragment: categoryDesignID: ClothingDesign
D/EGL_emulation: eglMakeCurrent: 0x7ea0c3349a00: ver 2 0 (tinfo 0x7ea19df7cea0)
W/ActivityThread: handleWindowVisibility: no activity for token android.os.BinderProxy#15c2c02
D/CategoryDetailRecyclerViewActivity: Intent:Intent { }
D/CategoryDetailRecyclerViewActivity: categoryDesignID: null
AddPhotosFragment.kt
//Upload images button
upload_imgs_button.setOnClickListener {
Log.d(TAG, "uploadImgsPath: $uploadImgsPath");
//Upload images to firebase storage
if (uploadImgsPath != null) {
var count = imagesList.size
for (uriForUpload in imagesList) {
val uri = Uri.parse(uriForUpload)
val imgNameUUID = UUID.randomUUID().toString()
val withAppendedPath =
Uri.withAppendedPath(uri, imgNameUUID).toString()
var imgFileRef = uploadImgsPath!!.child(withAppendedPath)
imgFileRef.putFile(uri)
.continueWithTask {
if (!it.isSuccessful) {
it.exception?.let {
throw it
}
}
imgFileRef.downloadUrl
}.addOnCompleteListener {
if (it.isSuccessful) {
var uriForDownload = it.result.toString()
Log.d(TAG, "uriForDownload:$uriForDownload ");
downloadImagesUriList.add(uriForDownload)
count--
if (count == 0) {
Log.d(
TAG,
"downloadImagesUriList:$downloadImagesUriList "
);
var uuidOfGroupId = UUID.randomUUID().toString()
var uploadedImages = UploadedImages(
categoryDesignID,
user!!.uid,
downloadImagesUriList,
Timestamp.now(),
uuidOfGroupId,
user!!.photoUrl.toString(),
user!!.displayName
)
Log.d(TAG, "uploadedImages:$uploadedImages ");
//Save uploaded images path info to firestore
firestore.collection("uploadedImages")
.document(uuidOfGroupId)
.set(uploadedImages)
.addOnSuccessListener {
Toast.makeText(
context,
"Upload successful",
Toast.LENGTH_LONG
).show()
//TODO Show RecyclerView
var intent = Intent(
context,
CategoryDetailRecyclerViewActivity::class.java
)
Log.d(
TAG,
"categoryDesignID: ${uploadedImages.categoryDesignID}"
);
intent.putExtra(
"categoryDesignID",
uploadedImages.categoryDesignID
)
startActivity(intent)
}
}
}
}
}
CategoryDetailRecyclerViewActivity.kt
class CategoryDetailRecyclerViewActivity : AppCompatActivity() {
val TAG = CategoryDetailRecyclerViewActivity::class.java.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_category_detail_recycler_view)
var intent = Intent()
Log.d(TAG, "Intent:$intent ");
var categoryDesignID = intent.getStringExtra("categoryDesignID")
Log.d(TAG, "categoryDesignID: $categoryDesignID");
My manifest:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".ui.categoryDetailRecyclerView.CategoryDetailRecyclerViewActivity"></activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
I am unsure what you mean by why StartActivity(intent) failed but based on your Logcat, it prints what I would expect from the code.
First of all it doesn't fail. You see in the Log that the code inside the onCreate() is executed. So the activity is started.
D/CategoryDetailRecyclerViewActivity: Intent:Intent { }
D/CategoryDetailRecyclerViewActivity: categoryDesignID: null
Here you see one of the problems: Your categoryDesignID is null. That is because you are not reading it from the right place. To get the data from the intent you used to start an Activity, you should ask for the intent instead of creating a new one.
Change your onCreate code to this and is should at least log the right id value:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_category_detail_recycler_view)
// don't create an Intent instance, it is already available and contains what you have send via startActivity(intent)
// var intent = Intent()
Log.d(TAG, "Intent:$intent ");
var categoryDesignID = intent.getStringExtra("categoryDesignID")
Log.d(TAG, "categoryDesignID: $categoryDesignID");
}
This problem has solved.
I later changed var intent = Intent() into var intent = getIntent(), then CategoryDetailRecyclerViewActivity can get intent.
Basically, whenever I launch a second activity and pause the application and relaunch app by tapping on its icon in launcher, app does not resumes on second activity but goes back to first activity. On top of that, if I pause the app(view in task manager), which app makes shows whole activity again like really fast(talking in milliseconds) and then continues to task manager.
Manifest file
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme"
tools:replace="android:icon">
<activity
android:name=".Activity.MainActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".Activity.ReferenceActivity"
android:configChanges="orientation|screenSize" /> <!-- Find a better solution for orientation change -->
<meta-data
android:name="preloaded_fonts"
android:resource="#array/preloaded_fonts" />
<activity android:name=".Activity.AboutActivity"></activity>
</application>
Since you would have noticed launchMode set to "singleTask", there is a reason behind it. My app contains recycler view. Its data is updated from second activity. Without launchMode set to singleTask, recyclerview data is not updated so to update the data I had to relaunch whole app just to see any data change. It is a temporary workaround but I am hoping if someone would help me on this matter as well.
About reycler view not updating, I have asked about that question countless times but never got a solution. Yes, I have gone through probably hundreds of similar problems on stack over flow and none of them work. Please bear in mind that I am beginner in Android development hence I do not have knowledge on advanced things like MvvM, RxJava or live data architecture.
P.S Just so any one gives a solution that I recall my data loader function again somewhere in onStart or on Resume, I have tried that countless times and it does not works. My app is not using fragments or any other advanced stuff just basic activities. Any help is appreciated!
Second activity
class ReferenceActivity : AppCompatActivity() {
private var dbHandler: PediaDatabase? = null
private var note = UserNotes()
private var noteExisted: Boolean = false
private var cardAdapterPos: Int? = null
private var title: String? = null
private var text: String? = null
private var sharingMenuOpened: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_reference)
val toolbarRef: Toolbar = findViewById(R.id.toolbarRefID)
setSupportActionBar(toolbarRef)
val toolbarTxtView = findViewById<TextView>(R.id.refToolbarTitleID)
supportActionBar!!.setDisplayShowTitleEnabled(false)
overridePendingTransition(R.anim.slide_in, R.anim.slide_out)
dbHandler = PediaDatabase(this)
val data = intent
if (!isNewNote) {
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
if (data != null) {
noteExisted = true
this.cardAdapterPos = data.extras.getInt("cardPosition")
cardID = data.extras.getInt("cardID")
existingNote = dbHandler!!.readNote(cardID)
text = existingNote.noteText
title = existingNote.noteTitle
refTitleID.setText(existingNote.noteTitle, TextView.BufferType.EDITABLE)
refTextID.setText(existingNote.noteText, TextView.BufferType.EDITABLE)
toolbarTxtView.text = "Created: " + existingNote.noteDate.toString()
}
} else {
toolbarTxtView.text = "New note"
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
}
}
override fun onResume() {
super.onResume()
sharingMenuOpened = false
}
override fun onPause() {
super.onPause()
if (!sharingMenuOpened)
saveNote()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.top_menu, menu)
val addItem: MenuItem = menu!!.findItem(R.id.add_note_menu)
val delItem: MenuItem = menu.findItem(R.id.delete_note_menu)
val shareButton: MenuItem = menu.findItem(R.id.shareID)
addItem.isVisible = false
delItem.isVisible = false
shareButton.isVisible = false
if (noteExisted) {
delItem.isVisible = true
shareButton.isVisible = true
}
return true
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
if (item!!.itemId == R.id.delete_note_menu) {
val deletionMsg = SweetAlertDialog(this, SweetAlertDialog.WARNING_TYPE)
deletionMsg.titleText = "Delete this note?"
deletionMsg.confirmText = "Yes"
deletionMsg.setCancelable(false)
deletionMsg.setConfirmClickListener(object : SweetAlertDialog.OnSweetClickListener {
override fun onClick(sweetAlertDialog: SweetAlertDialog?) {
dbHandler!!.deleteNote(cardID)
deletionMsg.dismissWithAnimation()
val successMsg = SweetAlertDialog(this#ReferenceActivity, SweetAlertDialog.SUCCESS_TYPE)
successMsg.setCancelable(false)
successMsg.titleText = "Note deleted!"
successMsg.setConfirmClickListener(object : SweetAlertDialog.OnSweetClickListener {
override fun onClick(sweetAlertDialog: SweetAlertDialog?) {
successMsg.dismissWithAnimation()
finish()
}
}).show()
}
})
deletionMsg.setCancelButton("No", object : SweetAlertDialog.OnSweetClickListener {
override fun onClick(sweetAlertDialog: SweetAlertDialog?) {
deletionMsg.dismissWithAnimation()
}
})
deletionMsg.show()
}
if (item.itemId == R.id.shareID) {
var sharingTitle: String = title!!.trim()
var sharingText: String = text!!.trim()
sharingMenuOpened = true
val sharingIntent = Intent(android.content.Intent.ACTION_SEND)
sharingIntent.type = "text/plain"
val shareBody: String? = sharingText
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, sharingTitle)
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody)
startActivity(Intent.createChooser(sharingIntent, "Share to"))
}
return super.onOptionsItemSelected(item)
}
private fun saveNote() {
title = refTitleID.text.toString().trim()
text = refTextID.text.toString().trim()
if (existingNote.noteText == text && existingNote.noteTitle == title) {
finish()
} else {
if (noteExisted) {
if (TextUtils.isEmpty(title) && TextUtils.isEmpty(text)) {
dbHandler!!.deleteNote(cardID)
val parsedColor = Color.parseColor("#263238")
Toasty.Config.getInstance().setInfoColor(parsedColor).apply()
Toasty.info(this, "Note deleted", Toast.LENGTH_SHORT, true).show()
} else {
if (TextUtils.isEmpty(title))
title = "No title"
existingNote.noteTitle = title
existingNote.noteText = text
existingNote.noteDate = System.currentTimeMillis().toString()
dbHandler!!.updateNote(existingNote)
Toasty.success(this, "Note saved", Toast.LENGTH_SHORT, true).show()
startActivity(Intent(this, MainActivity::class.java))
finish()
}
} else {
if (TextUtils.isEmpty(title) && TextUtils.isEmpty(text)) {
finish()
} else {
if (TextUtils.isEmpty(title))
title = "No title"
note.noteTitle = title
note.noteText = text
note.noteDate = System.currentTimeMillis().toString()
dbHandler!!.createNote(note)
Toasty.success(this, "Note saved", Toast.LENGTH_SHORT, true).show()
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}
}
}
}
You are invoking finish() in saveNote() method, which is being called in onPause(), hence every time your app goes into background, current activity is being finished (closed) due to invokation of finish() in it.
Remove the calls to finish() in saveNote() method, and you will be resumed to your current activity instead of landing on launcher/Main Activity.
You can try use this in MainActivty:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Intents.isActivityExpandedFromLauncherIcon(this)) {
finish()
} else {
setContentView(R.layout.activity_main)
}
}
upd:
public class Intents {
public static boolean isActivityExpandedFromLauncherIcon(#NonNull Activity activity) {
return !activity.isTaskRoot() && isActivityStartedFromLauncherIcon(activity.getIntent());
}
public static boolean isActivityStartedFromLauncherIcon(#Nullable Intent intent) {
return intent != null &&
intent.hasCategory(Intent.CATEGORY_LAUNCHER) &&
intent.getAction() != null &&
intent.getAction().equals(Intent.ACTION_MAIN);
}
}
I followed everything in the android authentication guide and the quick start guide. I did as the guide told me to generate a SHA1 and adding it to Spotify app dashboard, and I got the app_client and added it to my app as well. Both scenarios still return the same thing. I even tried implementing the Login thru Browser feature, yet it still returns type EMPTY.
Here's my Login class
class SignInActivity : AppCompatActivity(), ConnectionStateCallback, Player.NotificationCallback {
private var mPlayer : SpotifyPlayer? = null
private val CLIENT_ID = //replace this
private val REDIRECT_URI = //replace this
private val REQUEST_CODE = 1337
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
btnSignin.setOnClickListener {
val builder = AuthenticationRequest.Builder(CLIENT_ID, AuthenticationResponse.Type.TOKEN, REDIRECT_URI)
builder.setScopes(arrayOf("user-read-private", "streaming"))
val request = builder.build()
AuthenticationClient.openLoginActivity(this, REQUEST_CODE, request)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
// Check if result comes from the correct activity
if (requestCode == REQUEST_CODE) {
val response = AuthenticationClient.getResponse(resultCode, intent)
Log.i("LoginActivity", Gson().toJson(response))
when (response.type) {
// Response was successful and contains auth token
AuthenticationResponse.Type.TOKEN -> {
Log.i("LoginActivity", "is equals to TOKEN")
val playerConfig = Config(this, response.accessToken, CLIENT_ID)
Spotify.getPlayer(playerConfig, this, object : SpotifyPlayer.InitializationObserver {
override fun onInitialized(spotifyPlayer: SpotifyPlayer) {
mPlayer = spotifyPlayer
mPlayer!!.addConnectionStateCallback(this#SignInActivity)
mPlayer!!.addNotificationCallback(this#SignInActivity)
}
override fun onError(throwable: Throwable) {
Log.e("LoginActivity", "Could not initialize player: " + throwable.message)
}
})
}
// Auth flow returned an error
AuthenticationResponse.Type.ERROR -> {
Log.i("LoginActivity", "ERROR!: $response.error")
}
AuthenticationResponse.Type.EMPTY -> {
Log.i("LoginActivity", "EMPTY!")
}
}
}
}
override fun onLoggedIn() {
Log.d("LoginActivity", "User logged in")
// This is the line that plays a song.
mPlayer!!.playUri(null, "spotify:track:2TpxZ7JUBn3uw46aR7qd6V", 0, 0)
}
}
and here's my AndroidManifest file
`
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".SignInActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<data
android:host="callback"
android:scheme="backdrop.io"/>
</intent-filter>
</activity>
<activity
android:name="com.spotify.sdk.android.authentication.LoginActivity"
android:theme="#android:style/Theme.Translucent.NoTitleBar"/>
</application>
I'm trying to get a Type.TOKEN from this
Had this exact same issue myself, the problem is with this line in onActivityResult():
val response = AuthenticationClient.getResponse(resultCode, intent)
data should be passed as the second argument instead of intent.
The reason this isn't a compiler error is due to Kotlin turning getters and setters into properties, intent is a call to the Activity method getIntent().