Skip to content

Commit

Permalink
Add authentication to widget buttons (#2798)
Browse files Browse the repository at this point in the history
  • Loading branch information
andyboeh authored Sep 5, 2022
1 parent 84aa445 commit 6b06e0d
Show file tree
Hide file tree
Showing 10 changed files with 825 additions and 7 deletions.
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="io.homeassistant.companion.android.widgets.ButtonWidget.CALL_SERVICE" />
<action android:name="io.homeassistant.companion.android.widgets.ButtonWidget.CALL_SERVICE_AUTH" />
<action android:name="io.homeassistant.companion.android.widgets.ButtonWidget.RECEIVE_DATA" />
</intent-filter>

Expand Down Expand Up @@ -176,6 +177,11 @@
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity android:name=".widgets.common.WidgetAuthenticationActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:theme="@style/Theme.Transparent">
</activity>
<activity android:name=".widgets.camera.CameraWidgetConfigureActivity"
android:configChanges="orientation|screenSize"
android:exported="true">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import io.homeassistant.companion.android.database.widget.ButtonWidgetDao
import io.homeassistant.companion.android.database.widget.ButtonWidgetEntity
import io.homeassistant.companion.android.database.widget.WidgetBackgroundType
import io.homeassistant.companion.android.util.getAttribute
import io.homeassistant.companion.android.widgets.common.WidgetAuthenticationActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
Expand All @@ -43,8 +44,10 @@ import io.homeassistant.companion.android.common.R as commonR
class ButtonWidget : AppWidgetProvider() {
companion object {
private const val TAG = "ButtonWidget"
private const val CALL_SERVICE =
public const val CALL_SERVICE =
"io.homeassistant.companion.android.widgets.button.ButtonWidget.CALL_SERVICE"
private const val CALL_SERVICE_AUTH =
"io.homeassistant.companion.android.widgets.button.ButtonWidget.CALL_SERVICE_AUTH"
internal const val RECEIVE_DATA =
"io.homeassistant.companion.android.widgets.button.ButtonWidget.RECEIVE_DATA"

Expand All @@ -55,6 +58,7 @@ class ButtonWidget : AppWidgetProvider() {
internal const val EXTRA_ICON = "EXTRA_ICON"
internal const val EXTRA_BACKGROUND_TYPE = "EXTRA_BACKGROUND_TYPE"
internal const val EXTRA_TEXT_COLOR = "EXTRA_TEXT_COLOR"
internal const val EXTRA_REQUIRE_AUTHENTICATION = "EXTRA_REQUIRE_AUTHENTICATION"

// Vector icon rendering resolution fallback (if we can't infer via AppWidgetManager for some reason)
private const val DEFAULT_MAX_ICON_SIZE = 512
Expand Down Expand Up @@ -141,25 +145,36 @@ class ButtonWidget : AppWidgetProvider() {

super.onReceive(context, intent)
when (action) {
CALL_SERVICE_AUTH -> authThenCallConfiguredService(context, appWidgetId)
CALL_SERVICE -> callConfiguredService(context, appWidgetId)
RECEIVE_DATA -> saveServiceCallConfiguration(context, intent.extras, appWidgetId)
Intent.ACTION_SCREEN_ON -> updateAllWidgets(context)
}
}

private fun authThenCallConfiguredService(context: Context, appWidgetId: Int) {
Log.d(TAG, "Calling authentication, then configured service")

val intent = Intent(context, WidgetAuthenticationActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
context.startActivity(intent)
}

private fun getWidgetRemoteViews(context: Context, appWidgetId: Int): RemoteViews {
// Every time AppWidgetManager.updateAppWidget(...) is called, the button listener
// and label need to be re-assigned, or the next time the layout updates
// (e.g home screen rotation) the widget will fall back on its default layout
// without any click listener being applied

val widget = buttonWidgetDao.get(appWidgetId)
val auth = widget?.requireAuthentication == true

val intent = Intent(context, ButtonWidget::class.java).apply {
action = CALL_SERVICE
action = if (auth) CALL_SERVICE_AUTH else CALL_SERVICE
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}

val widget = buttonWidgetDao.get(appWidgetId)

// Create an icon pack and load all drawables.
if (iconPack == null) {
val loader = IconPackLoader(context)
Expand Down Expand Up @@ -339,6 +354,7 @@ class ButtonWidget : AppWidgetProvider() {
val service: String? = extras.getString(EXTRA_SERVICE)
val serviceData: String? = extras.getString(EXTRA_SERVICE_DATA)
val label: String? = extras.getString(EXTRA_LABEL)
val requireAuthentication: Boolean = extras.getBoolean(EXTRA_REQUIRE_AUTHENTICATION)
val icon: Int = extras.getInt(EXTRA_ICON)
val backgroundType: WidgetBackgroundType = extras.getSerializable(EXTRA_BACKGROUND_TYPE) as WidgetBackgroundType
val textColor: String? = extras.getString(EXTRA_TEXT_COLOR)
Expand All @@ -355,10 +371,11 @@ class ButtonWidget : AppWidgetProvider() {
"domain: " + domain + System.lineSeparator() +
"service: " + service + System.lineSeparator() +
"service_data: " + serviceData + System.lineSeparator() +
"require_authentication: " + requireAuthentication + System.lineSeparator() +
"label: " + label
)

val widget = ButtonWidgetEntity(appWidgetId, icon, domain, service, serviceData, label, backgroundType, textColor)
val widget = ButtonWidgetEntity(appWidgetId, icon, domain, service, serviceData, label, backgroundType, textColor, requireAuthentication)
buttonWidgetDao.add(widget)

// It is the responsibility of the configuration activity to update the app widget
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ class ButtonWidgetConfigureActivity : BaseWidgetConfigureActivity(), IconDialog.
buttonWidget.textColor?.let { it.toColorInt() == ContextCompat.getColor(this, commonR.color.colorWidgetButtonLabelBlack) } ?: false

binding.addButton.setText(commonR.string.update_widget)

binding.widgetCheckboxRequireAuthentication.isChecked = buttonWidget.requireAuthentication
} else {
binding.backgroundType.setSelection(0)
}
Expand Down Expand Up @@ -463,6 +465,8 @@ class ButtonWidgetConfigureActivity : BaseWidgetConfigureActivity(), IconDialog.
else null
)

intent.putExtra(ButtonWidget.EXTRA_REQUIRE_AUTHENTICATION, binding.widgetCheckboxRequireAuthentication.isChecked)

context.sendBroadcast(intent)

// Make sure we pass back the original appWidgetId
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.homeassistant.companion.android.widgets.common

import android.appwidget.AppWidgetManager
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import io.homeassistant.companion.android.authenticator.Authenticator
import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.widgets.button.ButtonWidget

class WidgetAuthenticationActivity : AppCompatActivity() {
companion object {
private const val TAG = "WidgetAuthenticationA"
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate in WidgetAuthenticationActivity called")

val authenticator = Authenticator(this, this, ::authenticationResult)
authenticator.authenticate(getString(R.string.biometric_set_title))
}

private fun authenticationResult(result: Int) {
when (result) {
Authenticator.SUCCESS -> {
Log.d(TAG, "Authentication successful, calling requested service")
val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
if (appWidgetId > -1) {
val intent = Intent(applicationContext, ButtonWidget::class.java).apply {
action = ButtonWidget.CALL_SERVICE
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
sendBroadcast(intent)
}
finishAffinity()
}
Authenticator.CANCELED -> {
Log.d(TAG, "Authentication canceled by user, closing activity")
finishAffinity()
}
else -> {
Log.d(TAG, "Authentication failed, retry attempts allowed")
Toast.makeText(applicationContext, getString(R.string.widget_error_authenticating), Toast.LENGTH_LONG).show()
finishAffinity()
}
}
}
}
6 changes: 6 additions & 0 deletions app/src/main/res/layout/widget_button_configure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@
</RadioGroup>
</LinearLayout>

<CheckBox
android:id="@+id/widget_checkbox_require_authentication"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/widget_checkbox_require_authentication" />

<androidx.appcompat.widget.AppCompatButton
android:id="@+id/add_button"
android:layout_width="wrap_content"
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,13 @@
<style name="Alert.Button.Negative" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:textColor">@color/colorAccent</item>
</style>

<style name="Theme.Transparent" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources>
Loading

0 comments on commit 6b06e0d

Please sign in to comment.