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!
Related
I noticed strange behavior of the widget in my app. Sometimes the onDataSetChanged() method from RemoteViewsFactory class is not called (of course when the app is in the background and widget is visible) even though the notifyAppWidgetViewDataChanged() from AppWidgetManager class works correctly. I don't see any specific situation where this is happening - just sometimes it works, sometimes it doesn't. I spent a lot of time on debugging - everything looks like it's working fine.
val ids: IntArray = appWidgetManager.getAppWidgetIds(ComponentName(context, MyWidgetProvider::class.java))
appWidgetManager.notifyAppWidgetViewDataChanged(ids, R.id.appwidget_list_view)
I noticed that when I change it to:
ids.forEach { id ->
appWidgetManager.notifyAppWidgetViewDataChanged(id, R.id.appwidget_my_access_list_view)
}
the problem occurs less frequently, but maybe it's coincidence.
Significant code snippets:
MyWidgetProvider: AppWidgetProvider()
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
appWidgetIds.forEach { id ->
/* *** */
val intent = Intent(context, MyWidgetRemoteViewsService::class.java).apply {
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
}
val remoteViews = RemoteViews(context.packageName, R.layout.my_widget).apply {
setRemoteAdapter(R.id.my_widget_list_view, intent)
/* *** */
}
appWidgetManager.apply {
updateAppWidget(id, remoteViews)
}
}
}
MyWidgetRemoteViewsService: RemoteViewsService()
#AndroidEntryPoint
class MyWidgetRemoteViewsService : RemoteViewsService() {
#Inject lateinit var itemStore: LockStore
override fun onGetViewFactory(intent: Intent) = MyAccessRemoteViewsFactory(applicationContext, itemStore)
}
AndroidManifest
<receiver
android:name=".MyWidgetProvider"
android:label="My app">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="#xml/appwidget_info"/>
</receiver>
<service
android:name=".MyWidgetRemoteViewsService"
android:permission="android.permission.BIND_REMOTEVIEWS"/>
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
I have the following Broadcast Receiver
class ShutdownReceiver(): BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (Intent.ACTION_SHUTDOWN == intent?.action) {
HiddenFileUtility.appendLogs("ACTION_SHUTDOWN: true")
ApplicationGlobalContext.setShutDownState(true)
}
}
}
I register the ShutdownReceiver through the AndroidManifext.xml like this:
<receiver android:name=".BroadcastReceivers.ShutdownReceiver">
<intent-filter android:priority="1">
<action android:name="android.intent.action.BOOT_COMPLETED" android:priority="999"/>
</intent-filter>
</receiver>
And i never receive the ACTION_SHUTDOWN intent.
In the Android official documentation states that As of Build.VERSION_CODES#P this broadcast is only sent to receivers registered through Context.registerReceiver link here
The solution is to delete the ShutdownReceiver from the AndroidManifest.xml and register it using Context.registerReceiver like this:
val shutdownReceiver = ShutdownReceiver();
val bootIntentFilter = IntentFilter(Intent.ACTION_SHUTDOWN);
context.registerReceiver(shutdownReceiver, bootIntentFilter);
I have tried every possible answer on SO but nothing has helped.
I want to reset alarms using AlarmManager on device reboot and the code to do exactly that works, but it doesn't when I put it inside the receiver.
I had tried creating a service but that didn't seem to work at all which that was probably incompetence of my part, however I just can't see why this code isn't working.
AndroidManifest:
<manifest
(...)
<uses-permission android:name = "android.permission.RECEIVE_BOOT_COMPLETED" />
(...)
<application
(...)
<receiver
android:name = ".utils.DeviceRebootReceiver">
<intent-filter>
<action android:name = "android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
DeviceRebootReceiver:
class DeviceRebootReceiver : BroadcastReceiver() {
override fun onReceive(context : Context?, intent : Intent?) {
resetAlarms(context)
}
}
fun resetAlarms(context:Context):
fun resetAlarms(context : Context?) {
suspend fun resetAlarmsCoroutine(context : Context) {
val reminders = NotesDatabase.getInstance(context).notesDatabaseDAO.getAllActiveReminders()
reminders.forEach {
if (it.reminderTime > System.currentTimeMillis()) {
createAlarm(it.reminderTime, it.reminderMessage,null,context)
}
}
}
CoroutineScope(Dispatchers.IO).launch {
if (context != null) {
resetAlarmsCoroutine(context)
}
}
}
No need to show the createAlarm() function cause it works fine, BUT since I had seen that AlarmManager could cause problems on reboot I do instantiate it there like so:
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
I have a problem: the Broadcast Receiver in one of my apps isn't having its onReceive callback triggered by a custom action broadcast sent by my service from a separate app.
The broadcast receiver is context-registered, meaning I don't want to declare it in a manifest file anywhere because I don't want broadcasts launching my app. The broadcast is being sent from a service completely separate from the app, so we're dealing with inter-process communication here and a local broadcast won't do.
My suspicion is that i'm not correctly matching the intent action string declared in my broadcast sender (the Service) with my broadcast receiver (the App).
Looking at my code below, what am I doing incorrectly?
ScannerService.kt
Intent().also { intent ->
intent.action = "com.foo.bar.example.package.ScannerService.SCANNER_EVENT"
intent.putExtra("barcode", barcode)
intent.setPackage("com.nu.rms")
sendBroadcast(intent)
Timber.d("Sent broadcast")
}
AppActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerBroadcastReceivers()
}
private fun registerBroadcastReceivers() {
val broadcastReceiver = ScannerBroadcastReceiver()
val filter = IntentFilter().apply { addAction("SCANNER_EVENT") }
registerReceiver(broadcastReceiver, filter)
Timber.d("Registered broadcast receiver")
}
class ScannerBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Timber.d("Received broadcast")
StringBuilder().apply {
append("Action: ${intent.action}\n")
append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
toString().also { log -> Timber.d(log) }
}
}
}
Try to use a manifest-declared receiver (eg. in case permissions may have to be added there):
<receiver
android:name=".ScannerBroadcastReceiver"
android:permission="android.permission.INTERNET">
<intent-filter>
<action android:name="com.foo.bar.example.package.ScannerService.SCANNER_EVENT"/>
</intent-filter>
</receiver>
When using a context-registered receiver, the action might be ScannerService.SCANNER_EVENT or verbosely com.foo.bar.example.package.ScannerService.SCANNER_EVENT.