Skip to content

Commit c4d8033

Browse files
committed
Handle widget clicks via Broadcast instead of via Service, as Oreo disallows service clicks #1252
1 parent 07229ef commit c4d8033

18 files changed

+235
-86
lines changed

app/src/main/AndroidManifest.xml

+16-5
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@
8181
android:value="li.klass.fhem.search.SearchResultsActivity" />
8282

8383
<provider
84-
android:exported="false"
8584
android:name=".search.MySearchSuggestionsProvider"
86-
android:authorities="li.klass.fhem.search.MySearchSuggestionsProvider" />
85+
android:authorities="li.klass.fhem.search.MySearchSuggestionsProvider"
86+
android:exported="false" />
8787

8888
<activity android:name=".activities.DuplicateInstallActivity" />
8989
<activity
@@ -155,6 +155,16 @@
155155
android:noHistory="true"
156156
android:theme="@style/Theme.Dialog.Light" />
157157

158+
<receiver
159+
android:name=".appwidget.action.AppWidgetBroadcastReceiver"
160+
android:exported="false">
161+
<intent-filter>
162+
<action android:name="li.klass.fhem.constants.Actions.DEVICE_WIDGET_TOGGLE" />
163+
<action android:name="li.klass.fhem.constants.Actions.DEVICE_WIDGET_TARGET_STATE" />
164+
<action android:name="li.klass.fhem.constants.Actions.WIDGET_REQUEST_UPDATE" />
165+
</intent-filter>
166+
</receiver>
167+
158168
<receiver
159169
android:name=".appwidget.provider.SmallAppWidgetProvider"
160170
android:exported="true"
@@ -191,8 +201,10 @@
191201
android:name="android.appwidget.provider"
192202
android:resource="@xml/andfhem_appwidget_big" />
193203
</receiver>
204+
194205
<service
195206
android:name=".appwidget.update.AppWidgetUpdateIntentService"
207+
android:enabled="true"
196208
android:exported="false">
197209
<intent-filter>
198210
<action android:name="li.klass.fhem.constants.Actions.REDRAW_WIDGET" />
@@ -254,7 +266,6 @@
254266
<action android:name="li.klass.fhem.constants.Actions.DEVICE_SET_ALIAS" />
255267
<action android:name="li.klass.fhem.constants.Actions.DEVICE_REFRESH_STATE" />
256268
<action android:name="li.klass.fhem.constants.Actions.DEVICE_REFRESH_VALUES" />
257-
<action android:name="li.klass.fhem.constants.Actions.DEVICE_WIDGET_TOGGLE" />
258269
<action android:name="li.klass.fhem.constants.Actions.DEVICE_SET_SUB_STATE" />
259270
<action android:name="li.klass.fhem.constants.Actions.DEVICE_SET_SUB_STATES" />
260271
<action android:name="li.klass.fhem.constants.Actions.RESEND_LAST_FAILED_COMMAND" />
@@ -326,8 +337,8 @@
326337

327338
<service
328339
android:name=".fcm.receiver.FcmIntentService"
329-
android:exported="false"
330-
android:enabled="true">
340+
android:enabled="true"
341+
android:exported="false">
331342
<intent-filter>
332343
<action android:name="com.google.firebase.MESSAGING_EVENT" />
333344
</intent-filter>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* AndFHEM - Open Source Android application to control a FHEM home automation
3+
* server.
4+
*
5+
* Copyright (c) 2011, Matthias Klass or third-party contributors as
6+
* indicated by the @author tags or express copyright attribution
7+
* statements applied by the authors. All third-party contributions are
8+
* distributed under license by Red Hat Inc.
9+
*
10+
* This copyrighted material is made available to anyone wishing to use, modify,
11+
* copy, or redistribute it subject to the terms and conditions of the GNU GENERAL PUBLIC LICENSE, as published by the Free Software Foundation.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU GENERAL PUBLIC LICENSE
16+
* for more details.
17+
*
18+
* You should have received a copy of the GNU GENERAL PUBLIC LICENSE
19+
* along with this distribution; if not, write to:
20+
* Free Software Foundation, Inc.
21+
* 51 Franklin Street, Fifth Floor
22+
* Boston, MA 02110-1301 USA
23+
*/
24+
25+
package li.klass.fhem.appwidget.action
26+
27+
import android.content.Context
28+
import android.os.Bundle
29+
import android.os.Handler
30+
import android.widget.Toast
31+
import com.google.common.base.Optional
32+
import li.klass.fhem.R
33+
import li.klass.fhem.appwidget.update.AppWidgetUpdateService
34+
import li.klass.fhem.constants.Actions
35+
import li.klass.fhem.constants.BundleExtraKeys.*
36+
import li.klass.fhem.devices.backend.GenericDeviceService
37+
import li.klass.fhem.devices.backend.ToggleableService
38+
import li.klass.fhem.domain.core.FhemDevice
39+
import li.klass.fhem.update.backend.DeviceListService
40+
import li.klass.fhem.update.backend.DeviceListUpdateService
41+
import org.jetbrains.anko.doAsync
42+
import javax.inject.Inject
43+
44+
class AppWidgetActionHandler @Inject constructor(
45+
private val deviceListService: DeviceListService,
46+
private val genericDeviceService: GenericDeviceService,
47+
private val toggleableService: ToggleableService,
48+
private val appWidgetUpdateService: AppWidgetUpdateService,
49+
deviceListUpdateService: DeviceListUpdateService
50+
) {
51+
internal val handlers: Map<String, ActionHandler> = mapOf(
52+
Actions.DEVICE_WIDGET_TOGGLE to object : ActionHandler {
53+
override fun handle(device: FhemDevice?, connectionId: String?, bundle: Bundle, context: Context) {
54+
device ?: return
55+
toggleableService.toggleState(device, connectionId)
56+
}
57+
},
58+
Actions.DEVICE_WIDGET_TARGET_STATE to object : ActionHandler {
59+
override fun handle(device: FhemDevice?, connectionId: String?, bundle: Bundle, context: Context) {
60+
device ?: return
61+
val targetState = bundle.getString(DEVICE_TARGET_STATE) ?: return
62+
genericDeviceService.setState(device, targetState, Optional.fromNullable(connectionId), true)
63+
}
64+
},
65+
Actions.WIDGET_REQUEST_UPDATE to object : ActionHandler {
66+
override fun handle(device: FhemDevice?, connectionId: String?, bundle: Bundle, context: Context) {
67+
Handler(context.mainLooper).post { Toast.makeText(context, R.string.widget_remote_update_started, Toast.LENGTH_LONG).show() }
68+
deviceListUpdateService.updateAllDevices(connectionId)
69+
}
70+
}
71+
)
72+
73+
fun handle(context: Context, bundle: Bundle, action: String) {
74+
val handler = handlers.get(action) ?: return
75+
val deviceName = bundle.getString(DEVICE_NAME)
76+
val connectionId = bundle.getString(CONNECTION_ID)
77+
78+
doAsync {
79+
val device = deviceName?.let { deviceListService.getDeviceForName(it, connectionId) }
80+
handler.handle(device, connectionId, bundle, context)
81+
appWidgetUpdateService.updateAllWidgets()
82+
}
83+
}
84+
85+
internal interface ActionHandler {
86+
fun handle(device: FhemDevice?, connectionId: String?, bundle: Bundle, context: Context)
87+
}
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* AndFHEM - Open Source Android application to control a FHEM home automation
3+
* server.
4+
*
5+
* Copyright (c) 2011, Matthias Klass or third-party contributors as
6+
* indicated by the @author tags or express copyright attribution
7+
* statements applied by the authors. All third-party contributions are
8+
* distributed under license by Red Hat Inc.
9+
*
10+
* This copyrighted material is made available to anyone wishing to use, modify,
11+
* copy, or redistribute it subject to the terms and conditions of the GNU GENERAL PUBLIC LICENSE, as published by the Free Software Foundation.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU GENERAL PUBLIC LICENSE
16+
* for more details.
17+
*
18+
* You should have received a copy of the GNU GENERAL PUBLIC LICENSE
19+
* along with this distribution; if not, write to:
20+
* Free Software Foundation, Inc.
21+
* 51 Franklin Street, Fifth Floor
22+
* Boston, MA 02110-1301 USA
23+
*/
24+
25+
package li.klass.fhem.appwidget.action
26+
27+
import android.content.BroadcastReceiver
28+
import android.content.Context
29+
import android.content.Intent
30+
import li.klass.fhem.AndFHEMApplication
31+
import javax.inject.Inject
32+
33+
class AppWidgetBroadcastReceiver : BroadcastReceiver() {
34+
@Inject
35+
lateinit var appWidgetActionHandler: AppWidgetActionHandler
36+
37+
init {
38+
AndFHEMApplication.application?.daggerComponent?.inject(this)
39+
}
40+
41+
override fun onReceive(context: Context?, intent: Intent?) {
42+
intent ?: return
43+
context ?: return
44+
45+
appWidgetActionHandler.handle(context, intent.extras, intent.action)
46+
}
47+
}

app/src/main/java/li/klass/fhem/appwidget/ui/widget/medium/DimWidgetView.kt

+5-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import android.content.Context
2929
import android.content.Intent
3030
import android.widget.RemoteViews
3131
import li.klass.fhem.R
32-
import li.klass.fhem.appwidget.action.AppWidgetActionIntentService
32+
import li.klass.fhem.appwidget.action.AppWidgetBroadcastReceiver
3333
import li.klass.fhem.appwidget.ui.widget.base.DeviceAppWidgetView
3434
import li.klass.fhem.appwidget.update.WidgetConfiguration
3535
import li.klass.fhem.behavior.dim.DimmableBehavior
@@ -55,21 +55,21 @@ class DimWidgetView : DeviceAppWidgetView() {
5555

5656
val dimDownState = behavior.getDimStateForPosition(behavior.currentDimPosition - 1)
5757
val dimDownIntent = sendTargetDimState(context, device, dimDownState, connectionId)
58-
view.setOnClickPendingIntent(R.id.dimDown, PendingIntent.getService(context, (widgetId.toString() + "dimDown").hashCode(), dimDownIntent,
58+
view.setOnClickPendingIntent(R.id.dimDown, PendingIntent.getBroadcast(context, (widgetId.toString() + "dimDown").hashCode(), dimDownIntent,
5959
PendingIntent.FLAG_UPDATE_CURRENT))
6060

6161
val dimUpState = behavior.getDimStateForPosition(behavior.currentDimPosition + 1)
6262
val dimUpIntent = sendTargetDimState(context, device, dimUpState, connectionId)
63-
view.setOnClickPendingIntent(R.id.dimUp, PendingIntent.getService(context, (widgetId.toString() + "dimUp").hashCode(), dimUpIntent,
63+
view.setOnClickPendingIntent(R.id.dimUp, PendingIntent.getBroadcast(context, (widgetId.toString() + "dimUp").hashCode(), dimUpIntent,
6464
PendingIntent.FLAG_UPDATE_CURRENT))
6565
}
6666

6767
override fun supports(device: FhemDevice, context: Context): Boolean =
6868
DimmableBehavior.supports(device)
6969

7070
private fun sendTargetDimState(context: Context, device: FhemDevice, targetState: String, connectionId: String?): Intent {
71-
return Intent(Actions.DEVICE_SET_STATE)
72-
.setClass(context, AppWidgetActionIntentService::class.java)
71+
return Intent(context, AppWidgetBroadcastReceiver::class.java)
72+
.setAction(Actions.DEVICE_WIDGET_TARGET_STATE)
7373
.putExtra(BundleExtraKeys.DEVICE_TARGET_STATE, targetState)
7474
.putExtra(BundleExtraKeys.DEVICE_NAME, device.name)
7575
.putExtra(BundleExtraKeys.CONNECTION_ID, connectionId)

app/src/main/java/li/klass/fhem/appwidget/ui/widget/medium/OnOffWidgetView.kt

+13-15
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ import android.widget.RemoteViews
3232
import li.klass.fhem.R
3333
import li.klass.fhem.adapter.devices.hook.DeviceHookProvider
3434
import li.klass.fhem.adapter.devices.toggle.OnOffBehavior
35+
import li.klass.fhem.appwidget.action.AppWidgetBroadcastReceiver
3536
import li.klass.fhem.appwidget.ui.widget.base.DeviceAppWidgetView
3637
import li.klass.fhem.appwidget.update.WidgetConfiguration
3738
import li.klass.fhem.constants.Actions
3839
import li.klass.fhem.constants.BundleExtraKeys
3940
import li.klass.fhem.dagger.ApplicationComponent
4041
import li.klass.fhem.domain.core.FhemDevice
41-
import li.klass.fhem.service.intent.DeviceIntentService
4242
import javax.inject.Inject
4343

4444
class OnOffWidgetView : DeviceAppWidgetView() {
@@ -64,27 +64,25 @@ class OnOffWidgetView : DeviceAppWidgetView() {
6464
val backgroundColor = if (isOn) R.color.android_green else android.R.color.white
6565
view.setInt(R.id.widgetOnButton, "setBackgroundColor", ContextCompat.getColor(context, backgroundColor))
6666

67-
val onIntent = Intent(Actions.DEVICE_SET_STATE)
68-
.setClass(context, DeviceIntentService::class.java)
69-
.putExtra(BundleExtraKeys.DEVICE_NAME, device.name)
70-
.putExtra(BundleExtraKeys.DEVICE_TARGET_STATE, onStateName)
71-
.putExtra(BundleExtraKeys.CONNECTION_ID, widgetConfiguration.connectionId)
72-
val onPendingIntent = PendingIntent.getService(context, widgetConfiguration.widgetId,
73-
onIntent, PendingIntent.FLAG_UPDATE_CURRENT)
67+
val onPendingIntent = targetStatePendingIntent(context, device, onStateName, widgetConfiguration, widgetConfiguration.widgetId)
7468
view.setOnClickPendingIntent(R.id.widgetOnButton, onPendingIntent)
7569

76-
val offIntent = Intent(Actions.DEVICE_SET_STATE)
77-
.setClass(context, DeviceIntentService::class.java)
78-
.putExtra(BundleExtraKeys.DEVICE_NAME, device.name)
79-
.putExtra(BundleExtraKeys.DEVICE_TARGET_STATE, offStateName)
80-
.putExtra(BundleExtraKeys.CONNECTION_ID, widgetConfiguration.connectionId)
81-
val offPendingIntent = PendingIntent.getService(context, -1 * widgetConfiguration.widgetId,
82-
offIntent, PendingIntent.FLAG_UPDATE_CURRENT)
70+
val offPendingIntent = targetStatePendingIntent(context, device, offStateName, widgetConfiguration, -widgetConfiguration.widgetId)
8371
view.setOnClickPendingIntent(R.id.widgetOffButton, offPendingIntent)
8472

8573
openDeviceDetailPageWhenClicking(R.id.deviceName, view, device, widgetConfiguration, context)
8674
}
8775

76+
private fun targetStatePendingIntent(context: Context, device: FhemDevice, targetState: String, widgetConfiguration: WidgetConfiguration, requestCode: Int): PendingIntent? {
77+
val intent = Intent(context, AppWidgetBroadcastReceiver::class.java)
78+
.setAction(Actions.DEVICE_WIDGET_TARGET_STATE)
79+
.putExtra(BundleExtraKeys.DEVICE_NAME, device.name)
80+
.putExtra(BundleExtraKeys.DEVICE_TARGET_STATE, targetState)
81+
.putExtra(BundleExtraKeys.CONNECTION_ID, widgetConfiguration.connectionId)
82+
return PendingIntent.getBroadcast(context, requestCode,
83+
intent, PendingIntent.FLAG_UPDATE_CURRENT)
84+
}
85+
8886
override fun supports(device: FhemDevice, context: Context): Boolean =
8987
onOffBehavior.supports(device)
9088

app/src/main/java/li/klass/fhem/appwidget/ui/widget/medium/TargetStateWidgetView.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableList
3333
import li.klass.fhem.R
3434
import li.klass.fhem.adapter.devices.genericui.AvailableTargetStatesDialogUtil
3535
import li.klass.fhem.adapter.devices.genericui.availableTargetStates.OnTargetStateSelectedCallback
36+
import li.klass.fhem.appwidget.action.AppWidgetBroadcastReceiver
3637
import li.klass.fhem.appwidget.ui.widget.WidgetConfigurationCreatedCallback
3738
import li.klass.fhem.appwidget.ui.widget.WidgetType
3839
import li.klass.fhem.appwidget.ui.widget.activity.TargetStateAdditionalInformationActivity
@@ -43,7 +44,6 @@ import li.klass.fhem.constants.BundleExtraKeys
4344
import li.klass.fhem.dagger.ApplicationComponent
4445
import li.klass.fhem.domain.core.DeviceStateRequiringAdditionalInformation.requiresAdditionalInformation
4546
import li.klass.fhem.domain.core.FhemDevice
46-
import li.klass.fhem.service.intent.DeviceIntentService
4747

4848
class TargetStateWidgetView : DeviceAppWidgetView() {
4949
override fun getWidgetName(): Int = R.string.widget_targetstate
@@ -65,13 +65,13 @@ class TargetStateWidgetView : DeviceAppWidgetView() {
6565
pendingIntent = PendingIntent.getActivity(context, widgetConfiguration.widgetId,
6666
actionIntent, FLAG_UPDATE_CURRENT)
6767
} else {
68-
val actionIntent = Intent(Actions.DEVICE_SET_STATE)
69-
.setClass(context, DeviceIntentService::class.java)
68+
val actionIntent = Intent(context, AppWidgetBroadcastReceiver::class.java)
69+
.setAction(Actions.DEVICE_WIDGET_TARGET_STATE)
7070
.putExtra(BundleExtraKeys.DEVICE_NAME, device.name)
7171
.putExtra(BundleExtraKeys.DEVICE_TARGET_STATE, payload)
7272
.putExtra(BundleExtraKeys.CONNECTION_ID, widgetConfiguration.connectionId)
7373

74-
pendingIntent = PendingIntent.getService(context, widgetConfiguration.widgetId, actionIntent,
74+
pendingIntent = PendingIntent.getBroadcast(context, widgetConfiguration.widgetId, actionIntent,
7575
FLAG_UPDATE_CURRENT)
7676
}
7777

app/src/main/java/li/klass/fhem/appwidget/ui/widget/medium/ToggleWidgetView.kt

+15-19
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ import li.klass.fhem.R
3333
import li.klass.fhem.adapter.devices.hook.ButtonHook
3434
import li.klass.fhem.adapter.devices.hook.DeviceHookProvider
3535
import li.klass.fhem.adapter.devices.toggle.OnOffBehavior
36+
import li.klass.fhem.appwidget.action.AppWidgetBroadcastReceiver
3637
import li.klass.fhem.appwidget.ui.widget.base.DeviceAppWidgetView
3738
import li.klass.fhem.appwidget.update.WidgetConfiguration
3839
import li.klass.fhem.constants.Actions
3940
import li.klass.fhem.constants.BundleExtraKeys.*
4041
import li.klass.fhem.dagger.ApplicationComponent
4142
import li.klass.fhem.domain.core.FhemDevice
42-
import li.klass.fhem.service.intent.DeviceIntentService
4343
import javax.inject.Inject
4444

4545
open class ToggleWidgetView : DeviceAppWidgetView() {
@@ -66,7 +66,7 @@ open class ToggleWidgetView : DeviceAppWidgetView() {
6666
view.setTextViewText(R.id.toggleOff, device.getEventMapStateFor(onOffBehavior.getOffStateName(device)!!))
6767
}
6868

69-
val pendingIntent = PendingIntent.getService(context, widgetConfiguration.widgetId, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT)
69+
val pendingIntent = PendingIntent.getBroadcast(context, widgetConfiguration.widgetId, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT)
7070
view.setOnClickPendingIntent(R.id.toggleOff, pendingIntent)
7171
view.setOnClickPendingIntent(R.id.toggleOn, pendingIntent)
7272

@@ -76,26 +76,22 @@ open class ToggleWidgetView : DeviceAppWidgetView() {
7676
private fun actionIntentFor(device: FhemDevice, widgetConfiguration: WidgetConfiguration, context: Context): Intent {
7777
val hook = deviceHookProvider.buttonHookFor(device)
7878

79-
val actionIntent = when (hook) {
80-
ButtonHook.ON_DEVICE -> actionIntentForOnOffDevice(device, widgetConfiguration)
81-
.putExtra(DEVICE_TARGET_STATE, onOffBehavior.getOnStateName(device))
82-
ButtonHook.OFF_DEVICE -> actionIntentForOnOffDevice(device, widgetConfiguration)
83-
.putExtra(DEVICE_TARGET_STATE, onOffBehavior.getOffStateName(device))
84-
else -> Intent(Actions.DEVICE_WIDGET_TOGGLE)
85-
.putExtra(APP_WIDGET_ID, widgetConfiguration.widgetId)
86-
.putExtra(DEVICE_NAME, device.name)
87-
.putExtra(CONNECTION_ID, widgetConfiguration.connectionId)
88-
}
89-
return actionIntent
79+
return Intent(context, AppWidgetBroadcastReceiver::class.java)
9080
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
91-
.setClass(context, DeviceIntentService::class.java)
81+
.putExtra(APP_WIDGET_ID, widgetConfiguration.widgetId)
82+
.putExtra(DEVICE_NAME, device.name)
83+
.putExtra(CONNECTION_ID, widgetConfiguration.connectionId)
84+
.apply {
85+
when (hook) {
86+
ButtonHook.ON_DEVICE -> setAction(Actions.DEVICE_WIDGET_TARGET_STATE)
87+
.putExtra(DEVICE_TARGET_STATE, onOffBehavior.getOnStateName(device))
88+
ButtonHook.OFF_DEVICE -> setAction(Actions.DEVICE_WIDGET_TARGET_STATE)
89+
.putExtra(DEVICE_TARGET_STATE, onOffBehavior.getOffStateName(device))
90+
else -> setAction(Actions.DEVICE_WIDGET_TOGGLE)
91+
}
92+
}
9293
}
9394

94-
private fun actionIntentForOnOffDevice(device: FhemDevice, widgetConfiguration: WidgetConfiguration): Intent =
95-
Intent(Actions.DEVICE_SET_STATE)
96-
.putExtra(DEVICE_NAME, device.name)
97-
.putExtra(CONNECTION_ID, widgetConfiguration.connectionId)
98-
9995
override fun supports(device: FhemDevice, context: Context): Boolean =
10096
onOffBehavior.supports(device)
10197

0 commit comments

Comments
 (0)