An implicit deep link from a notification in JetpackNavigation library - android

I'm trying to implement an implicit deep link handling in my application, but the following code doesn't work out and onNewIntent in my single activity isn't calling, but always startDestination from navigation graph is opening.
In my navigation graph, I have the following deep link for a fragment
<deepLink
android:id="#+id/deepLink"
app:uri="people/{uuid}" />
Then I added the nav. graph to manifest file between the activity tag
<nav-graph android:value="#navigation/app_graph" />
After I put onNewIntent implementation to MainActivity and it looks like
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
findNavController(R.id.fragmentContainer).handleDeepLink(intent)
}
Creating of a pending intent is happening like:
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
data = Uri.parse("people/$uuid")
}
val pendingIntent = PendingIntent.getActivity(
context,
PENDING_INTENT_REQUEST_CODE,
intent,
0
)
And finally the dialog creation
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
// not important
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
NotificationManagerCompat
.from(context)
.notify(Random.nextInt(), notification)

I think first your scheme is wrong it should be something like <scheme_name>://people/{uuid}

Related

Why is my PendingIntent not broadcasting/receiving from Android widget setOnClickPendingIntent?

AppWidget.kt:
override fun onEnabled(context: Context) {
// register price and widget button update receivers
val filters = IntentFilter()
filters.addAction(BROADCAST_PRICE_UPDATED)
filters.addAction(BROADCAST_WIDGET_UPDATE_BUTTON_CLICK)
LocalBroadcastManager.getInstance(context.applicationContext).registerReceiver(br, filters)
}
override fun onDisabled(context: Context) {
// unregister price and widget button update receivers
LocalBroadcastManager.getInstance(context.applicationContext).unregisterReceiver(br)
}
private val br = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { ... }
Notes: local broadcast receiver receives all other registered broadcasts, except from piWidgetUpdateButtonClicked. Using LocalBroadcastManager is the only way I've been able to send/receive broadcasts, as apparently using global broadcast system filters out broadcasts from my app's package?
internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
// Construct the RemoteViews object
val views = RemoteViews(context.packageName, R.layout.app_widget)
// Create an Intent to launch MainActivity when widget background touched
val piLaunchMainActiv: PendingIntent = PendingIntent.getActivity(context,0,
Intent(context.applicationContext, MainActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.widget_background_layout, piLaunchMainActiv)
// create intent to update widget when button clicked TODO this not working
val piWidgetUpdateButtonClicked =
PendingIntent.getBroadcast(context, appWidgetId,
Intent(BROADCAST_WIDGET_UPDATE_BUTTON_CLICK), PendingIntent.FLAG_UPDATE_CURRENT
or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.widget_update_button, piWidgetUpdateButtonClicked)
Notes: *piLauchMainActiv *pending intent works fine, but piWidgetUpdateButtonClicked does nothing. I've tried using context.applicationContext, also does nothing. Also I've tried explicitly setting receiving class: Intent.setClass(context, AppWidget::class.java), nothing.
Apparently, latest Google Android releases want you to register broadcast receivers programmatically, as I've done above, rather than in the manifest, which I've tried too, but also doesn't work:
AndroidManifest.xml:
<receiver
android:name=".AppWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<intent-filter>
<action android:name="org.bitanon.bitcointicker.BROADCAST_PRICE_UPDATED" />
</intent-filter>
<intent-filter>
<action android:name="org.bitanon.bitcointicker.BROADCAST_WIDGET_UPDATE_BUTTON_CLICK" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="#xml/app_widget_info" />
</receiver>
After days and days of reading through other related posts on stackoverflow, nothing has helped. I'm completely stumped, please send help!

Unable to get package name of chosen third party app with PendingIntent.FLAG_IMMUTABLE

I need to get the name of the app chosen by user fired with Intent.ACTION_SEND for analytic purposes. The name of app will be obtained through BroadcastReceiver.
It works until one day, the security engineer in our team informed us that all PendingIntent in the codebase must have PendingIntent.FLAG_IMMUTABLE to be secure.
The flag added breaks the existing functionality because intent?.getParcelableExtra<ComponentName>(Intent.EXTRA_CHOSEN_COMPONENT)?.packageName will always return null.
Is there anything I can do? PendingIntent.FLAG_MUTABLE is sadly not an option for me.
You can find same way of doing this from Android Documentation - Getting information about sharing
MainActivity.kt
const val PENDING_INTENT_REQUEST_CODE = 0x1000
const val THIRD_PARTY_SHARE_REQUEST_CODE = 0x1001
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnShare.setOnClickListener {
openThirdPartyShareDialog()
}
}
private fun openThirdPartyShareDialog() {
val thirdPartyShareIntent = Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
}
val broadcastIntent = Intent(this, ThirdPartyAppBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(this,
PENDING_INTENT_REQUEST_CODE,
broadcastIntent,
getPendingFlagIntent()
)
startActivityForResult(Intent.createChooser(
thirdPartyShareIntent,
null,
pendingIntent.intentSender
), THIRD_PARTY_SHARE_REQUEST_CODE)
}
private fun getPendingFlagIntent(): Int {
var flags = PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= 23) {
flags = flags or PendingIntent.FLAG_IMMUTABLE
}
return flags
}
}
ThirdPartyAppBroadcastReceiver.kt
class ThirdPartyAppBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// packageName will always be null !
val packageName =
intent?.getParcelableExtra<ComponentName>(Intent.EXTRA_CHOSEN_COMPONENT)?.packageName
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.flamyoad.broadcast_share"
>
<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.Broadcastshare"
>
<receiver android:name="com.flamyoad.broadcast_share.ThirdPartyAppBroadcastReceiver" />
<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>
Turns out it's fine to remove PendingIntent.FLAG_IMMUTABLE if you're using explicit intent.
Android apps send messages between components using Intents. Intents
can either specify the target component (Explicit Intent) or list a
general action and let the operating system deliver the Intent to any
component on the device that registers an Intent Filter matching that
action (Implicit Intent).
PendingIntents are Intents delegated to another app to be delivered at
some future time. Creating an implicit intent wrapped under a
PendingIntent is a security vulnerability that might lead to
denial-of-service, private data theft, and privilege escalation.
You can read more about it from Remediation for Implicit PendingIntent Vulnerability

Android Notification Timeout Listener

I did implement a notification feature in android using the Notification.Builder in Android OREO+. I need to cancel the notification after a certain time frame, if the user has not clicked on the notification. which i completed using the setTimeOutAfter method.
https://developer.android.com/reference/android/app/Notification.Builder.html#setTimeoutAfter(long).
Now, i need to send a message to server that the notification wasn't clicked/timeout has occured. How can i implement this? Is there any notificationTimeout Listener?
There's nothing like a timeout listener but you can use a delete intent for your purpose. You'll need a Broadcast Receiver in order to do something (like calling your server) when the notification gets dismissed.
In code:
class NotificationDismissedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// call your server here
}
}
private fun getNotificationWithDeleteIntent() : Notification{
val deleteIntent = Intent(context, NotificationDismissedReceiver::class.java)
deleteIntent.action = "notification_cancelled"
val onDismissPendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT)
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle(textTitle)
.setContentText(textContent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setTimeoutAfter(TIMEOUT)
.setDeleteIntent(onDismissPendingIntent)
return builder.build()
}

Start activity from receiver in Android Q

I'm checking my app with the Android Q [beta 6] in order to add all the required changes to be fully-compatible with the last SO. However, I found out that I am using a Receiver to start an Activity from background and due to the last background limitations implemented (https://developer.android.com/preview/privacy/background-activity-starts) the activity is not being opened.
I tried to use both the receiver context and application context to start the activity but in both cases the system shows a toast saying that is not possible to start activity from background.
What I tried on the Receiver...
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
context?.applicationContext?.let {
it.startActivity(Intent(it, MyActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
PushUtils.showReceiverCalledNotification(it)
}
}
That way I wanted to start MyActivity and also show a notification when the receiver is called. Instead, I can see the notification but the Activity is never started. It is very important for the feature to start the activity immediately, so there is a way to continue starting the activity from the receiver?
It is very important for the feature to start the activity immediately, so there is a way to continue starting the activity from the receiver?
No, sorry. Use a high-priority notification, so it appears in "heads-up" mode. The user can then rapidly tap on it to bring up your activity.
Due to restrictions, you cannot start activity from background. Instead you can use notifications as CommonsWare suggested and also suggested on the android developer site.
Here's the official documentation that lists situations when it will work and when won't.
https://developer.android.com/guide/components/activities/background-starts
You can use something like this:
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
showNotification(context.applicationContext)
} else {
context.applicationContext.startActivity(Intent(context, MyActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
}
PushUtils.showReceiverCalledNotification(context)
}
private fun showNotification(context: Context) {
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("default", "default", NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
val intent = Intent(context, MyActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT)
with(NotificationCompat.Builder(context, "default")) {
setSmallIcon(R.drawable.ic_scan_colored)
setContentTitle("Custom Title")
setContentText("Tap to start the application")
setContentIntent(pendingIntent)
setAutoCancel(true)
manager.notify(87, build())
}
}
}

setSmallIcon(icon:Icon) and NotificationCompat

currently I have this WET code caused by the fact that NotificationCompat does not support setSmallIcon for a Icon and not a resource-id:
val notification = if (Build.VERSION.SDK_INT < 23) {
NotificationCompat.Builder(this)
.setLargeIcon(bitmap)
.setSmallIcon(R.drawable.ic_launcher)
.setContentText(intentDescriber!!.userFacingIntentDescription)
.setContentTitle(label)
.setContentIntent(contentIntent)
.setAutoCancel(true)
.build()
} else {
Notification.Builder(this)
.setSmallIcon(Icon.createWithBitmap(bitmap))
.setLargeIcon(bitmap)
.setContentText(intentDescriber!!.userFacingIntentDescription)
.setContentTitle(label)
.setContentIntent(contentIntent)
.setAutoCancel(true)
.build()
}
Is there a way to make this nicer ( DRY ? ) - the problem is that both builder-classes are different ..
If you are comfortable using reflection, then rather than setting the small icon in the builder, set it in the built notification itself. You can check there for SDK 23, and call setSmallIcon using reflection (it is a public method, but is hidden. I doubt it will change), otherwise set the icon field in the notification.
Short of reflection I suggest creating your own builder interface with two implementations: one for NotificationCompat.Builder and one for Notification.Builder. You might be repeating "android" but you won't be repeating yourself. e.g.:
interface NotificationFacadeBuilder<out T> {
/* facade builder method declarations go here */
fun build(): T
}
class SupportAppNotificationCompatFacadeBuilder(context: Context)
: NotificationFacadeBuilder<NotificationCompat> {
val builder = NotificationCompat.Builder(context)
/* facade builder method implementations go here and delegate to `builder` */
override fun build(): NotificationCompat = TODO()
}
class AppNotificationFacadeBuilder(context: Context)
: NotificationFacadeBuilder<Notification> {
val builder = Notification.Builder(context)
/* facade builder method implementations go here and delegate to `builder` */
override fun build(): Notification = TODO()
}
NotificationFacadeBuilder (or whatever you decide to call it) will have to declare each common builder method you need and then each implementing class will simply delegate those to their respective, actual builder implementations.

Categories

Resources