Android 13 notification changes + target (#2682)

* Request notification permission during onboarding

 - When using Android 13 or the minimal version, add a screen to onboarding to request to enable notifications on the device. This screen is based on iOS onboarding. When tapping Continue, on Android 13 this requests the system permission and/or on the minimal version this changes the websocket setting.
 - Compile with and target SDK 33 (targeting required to be able to request notification system permission on demand)

* Add notification settings link in settings when off

 - Show a new setting in settings for the user to enable notifications for the Home Assistant app, either because the permission was denied (Android 13) or the user manually turned off all notifications (Android 8-12).
 - This new setting replaces the notification channels setting when everything is off; in this case there is no point managing individual channels. If at least one channel is on, the existing notification channels setting is shown instead.

* Compose tweaks

 - Remove redundant content description for icon
 - Change to a simpler scroll modifier

* Fix skipping notifications on minimal version

 - Ensure that the value is actually saved to the database

* Post Bluetooth on/off commands as if invalid

 - When using Android 13, post notification commands to control Bluetooth on/off as a normal notification as if they were invalid, as the function has been deprecated for the app

* Remove type that can be inferred
This commit is contained in:
Joris Pelgröm 2022-10-19 20:43:15 +02:00 committed by GitHub
parent 72681ca632
commit 4a9dd8ba04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 360 additions and 28 deletions

View file

@ -23,7 +23,7 @@ android {
defaultConfig {
applicationId = "io.homeassistant.companion.android"
minSdk = 21
targetSdk = 31
targetSdk = 33
versionName = System.getenv("VERSION") ?: "LOCAL"
versionCode = System.getenv("VERSION_CODE")?.toIntOrNull() ?: 1

View file

@ -175,13 +175,15 @@ class SettingsWearViewModel @Inject constructor(
url: String,
authCode: String,
deviceName: String,
deviceTrackingEnabled: Boolean
deviceTrackingEnabled: Boolean,
notificationsEnabled: Boolean
) {
val putDataRequest = PutDataMapRequest.create("/authenticate").run {
dataMap.putString("URL", url)
dataMap.putString("AuthCode", authCode)
dataMap.putString("DeviceName", deviceName)
dataMap.putBoolean("LocationTracking", deviceTrackingEnabled)
dataMap.putBoolean("Notifications", notificationsEnabled)
setUrgent()
asPutDataRequest()
}

View file

@ -74,15 +74,16 @@ class SettingsWearMainView : AppCompatActivity() {
OnboardApp.Input(
url = registerUrl,
defaultDeviceName = currentNodes.firstOrNull()?.displayName ?: "unknown",
locationTrackingPossible = false
)
locationTrackingPossible = false,
notificationsPossible = false
) // While notifications are technically possible, the app can't handle this for the Wear device
)
}
private fun onOnboardingComplete(result: OnboardApp.Output?) {
if (result != null) {
val (url, authCode, deviceName, deviceTrackingEnabled) = result
settingsWearViewModel.sendAuthToWear(url, authCode, deviceName, deviceTrackingEnabled)
val (url, authCode, deviceName, deviceTrackingEnabled, _) = result
settingsWearViewModel.sendAuthToWear(url, authCode, deviceName, deviceTrackingEnabled, true)
} else
Log.e(TAG, "onOnboardingComplete: Activity result returned null intent data")
}

View file

@ -39,6 +39,7 @@
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions"/>

View file

@ -3,6 +3,7 @@ package io.homeassistant.companion.android.launch
import android.os.Bundle
import android.util.Log
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Box
@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import com.google.android.material.composethemeadapter.MdcTheme
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.BuildConfig
@ -18,9 +20,11 @@ import io.homeassistant.companion.android.common.data.integration.DeviceRegistra
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.url.UrlRepository
import io.homeassistant.companion.android.database.sensor.SensorDao
import io.homeassistant.companion.android.database.settings.WebsocketSetting
import io.homeassistant.companion.android.onboarding.OnboardApp
import io.homeassistant.companion.android.onboarding.getMessagingToken
import io.homeassistant.companion.android.sensors.LocationSensorManager
import io.homeassistant.companion.android.settings.SettingViewModel
import io.homeassistant.companion.android.webview.WebViewActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -56,6 +60,8 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
private val mainScope = CoroutineScope(Dispatchers.Main + Job())
private val settingViewModel: SettingViewModel by viewModels()
private val registerActivityResult = registerForActivityResult(
OnboardApp(),
this::onOnboardingComplete
@ -95,7 +101,7 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
private fun onOnboardingComplete(result: OnboardApp.Output?) {
mainScope.launch {
if (result != null) {
val (url, authCode, deviceName, deviceTrackingEnabled) = result
val (url, authCode, deviceName, deviceTrackingEnabled, notificationsEnabled) = result
val messagingToken = getMessagingToken()
if (messagingToken.isBlank() && BuildConfig.FLAVOR == "full") {
AlertDialog.Builder(this@LaunchActivity)
@ -108,7 +114,8 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
authCode,
deviceName,
messagingToken,
deviceTrackingEnabled
deviceTrackingEnabled,
notificationsEnabled
)
}
}
@ -119,7 +126,8 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
authCode,
deviceName,
messagingToken,
deviceTrackingEnabled
deviceTrackingEnabled,
notificationsEnabled
)
}
} else
@ -132,7 +140,8 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
authCode: String,
deviceName: String,
messagingToken: String,
deviceTrackingEnabled: Boolean
deviceTrackingEnabled: Boolean,
notificationsEnabled: Boolean
) {
try {
urlRepository.saveUrl(url)
@ -176,6 +185,7 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
return
}
setLocationTracking(deviceTrackingEnabled)
setNotifications(notificationsEnabled)
displayWebview()
}
@ -189,4 +199,16 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
enabled = enabled
)
}
private fun setNotifications(enabled: Boolean) {
// Full: this only refers to the system permission on Android 13+ so no changes are necessary.
// Minimal: change persistent connection setting to reflect preference.
if (BuildConfig.FLAVOR != "full") {
settingViewModel.getSetting(0) // Required to create initial value
settingViewModel.updateWebsocketSetting(
0,
if (enabled) WebsocketSetting.ALWAYS else WebsocketSetting.NEVER
)
}
}
}

View file

@ -386,7 +386,11 @@ class MessagingManager @Inject constructor(
}
}
COMMAND_BLUETOOTH -> {
if (!jsonData[COMMAND].isNullOrEmpty() && jsonData[COMMAND] in ENABLE_COMMANDS)
if (
!jsonData[COMMAND].isNullOrEmpty() &&
jsonData[COMMAND] in ENABLE_COMMANDS &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
)
handleDeviceCommands(jsonData)
else {
mainScope.launch {
@ -770,9 +774,10 @@ class MessagingManager @Inject constructor(
}
}
}
@Suppress("DEPRECATION")
if (command == TURN_OFF)
bluetoothAdapter?.disable()
if (command == TURN_ON)
else if (command == TURN_ON)
bluetoothAdapter?.enable()
}
COMMAND_BLE_TRANSMITTER -> {

View file

@ -2,7 +2,6 @@ package io.homeassistant.companion.android.onboarding
import android.content.Context
import android.content.Intent
import android.icu.util.Output
import android.os.Build
import androidx.activity.result.contract.ActivityResultContract
import io.homeassistant.companion.android.BuildConfig
@ -13,25 +12,29 @@ class OnboardApp : ActivityResultContract<OnboardApp.Input, OnboardApp.Output?>(
private const val EXTRA_URL = "extra_url"
private const val EXTRA_DEFAULT_DEVICE_NAME = "extra_default_device_name"
private const val EXTRA_LOCATION_TRACKING_POSSIBLE = "location_tracking_possible"
private const val EXTRA_NOTIFICATIONS_POSSIBLE = "notifications_possible"
fun parseInput(intent: Intent): Input = Input(
url = intent.getStringExtra(EXTRA_URL),
defaultDeviceName = intent.getStringExtra(EXTRA_DEFAULT_DEVICE_NAME) ?: Build.MODEL,
locationTrackingPossible = intent.getBooleanExtra(EXTRA_LOCATION_TRACKING_POSSIBLE, false),
notificationsPossible = intent.getBooleanExtra(EXTRA_NOTIFICATIONS_POSSIBLE, true)
)
}
data class Input(
val url: String? = null,
val defaultDeviceName: String = Build.MODEL,
val locationTrackingPossible: Boolean = BuildConfig.FLAVOR == "full"
val locationTrackingPossible: Boolean = BuildConfig.FLAVOR == "full",
val notificationsPossible: Boolean = true
)
data class Output(
val url: String,
val authCode: String,
val deviceName: String,
val deviceTrackingEnabled: Boolean
val deviceTrackingEnabled: Boolean,
val notificationsEnabled: Boolean
) {
fun toIntent(): Intent {
return Intent().apply {
@ -39,6 +42,7 @@ class OnboardApp : ActivityResultContract<OnboardApp.Input, OnboardApp.Output?>(
putExtra("AuthCode", authCode)
putExtra("DeviceName", deviceName)
putExtra("LocationTracking", deviceTrackingEnabled)
putExtra("Notifications", notificationsEnabled)
}
}
}
@ -48,6 +52,7 @@ class OnboardApp : ActivityResultContract<OnboardApp.Input, OnboardApp.Output?>(
putExtra(EXTRA_URL, input.url)
putExtra(EXTRA_DEFAULT_DEVICE_NAME, input.defaultDeviceName)
putExtra(EXTRA_LOCATION_TRACKING_POSSIBLE, input.locationTrackingPossible)
putExtra(EXTRA_NOTIFICATIONS_POSSIBLE, input.notificationsPossible)
}
}
@ -60,11 +65,13 @@ class OnboardApp : ActivityResultContract<OnboardApp.Input, OnboardApp.Output?>(
val authCode = intent.getStringExtra("AuthCode").toString()
val deviceName = intent.getStringExtra("DeviceName").toString()
val deviceTrackingEnabled = intent.getBooleanExtra("LocationTracking", false)
val notificationsEnabled = intent.getBooleanExtra("Notifications", true)
return Output(
url = url,
authCode = authCode,
deviceName = deviceName,
deviceTrackingEnabled = deviceTrackingEnabled
deviceTrackingEnabled = deviceTrackingEnabled,
notificationsEnabled = notificationsEnabled
)
}
}

View file

@ -5,8 +5,10 @@ import android.os.Bundle
import android.view.KeyEvent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.commit
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.onboarding.authentication.AuthenticationFragment
import io.homeassistant.companion.android.onboarding.discovery.DiscoveryFragment
@ -29,6 +31,14 @@ class OnboardingActivity : AppCompatActivity() {
val input = OnboardApp.parseInput(intent)
viewModel.deviceName.value = input.defaultDeviceName
viewModel.locationTrackingPossible.value = input.locationTrackingPossible
viewModel.notificationsPossible.value = input.notificationsPossible
viewModel.notificationsEnabled = if (input.notificationsPossible) {
BuildConfig.FLAVOR == "full" &&
(
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
NotificationManagerCompat.from(this).areNotificationsEnabled()
)
} else false
if (savedInstanceState == null) {
supportFragmentManager.commit {

View file

@ -52,6 +52,8 @@ class OnboardingViewModel @Inject constructor(
val deviceName = mutableStateOf("")
val locationTrackingPossible = mutableStateOf(false)
var locationTrackingEnabled by mutableStateOf(false)
val notificationsPossible = mutableStateOf(true)
var notificationsEnabled by mutableStateOf(false)
fun onManualUrlUpdated(url: String) {
manualUrl.value = url
@ -80,6 +82,18 @@ class OnboardingViewModel @Inject constructor(
locationTrackingEnabled = enabled
}
fun setNotifications(enabled: Boolean) {
notificationsEnabled = enabled
}
fun getOutput() = OnboardApp.Output(
url = manualUrl.value,
authCode = authCode,
deviceName = deviceName.value,
deviceTrackingEnabled = locationTrackingEnabled,
notificationsEnabled = notificationsEnabled
)
override fun onCleared() {
_homeAssistantSearcher.stopSearch()
}

View file

@ -20,9 +20,10 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.composethemeadapter.MdcTheme
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.util.DisabledLocationHandler
import io.homeassistant.companion.android.onboarding.OnboardApp
import io.homeassistant.companion.android.onboarding.OnboardingViewModel
import io.homeassistant.companion.android.onboarding.notifications.NotificationPermissionFragment
import io.homeassistant.companion.android.sensors.LocationSensorManager
import io.homeassistant.companion.android.common.R as commonR
@ -151,14 +152,16 @@ class MobileAppIntegrationFragment : Fragment() {
}
private fun onComplete() {
val retData = OnboardApp.Output(
url = viewModel.manualUrl.value,
authCode = viewModel.authCode,
deviceName = viewModel.deviceName.value,
deviceTrackingEnabled = viewModel.locationTrackingEnabled
)
activity?.setResult(Activity.RESULT_OK, retData.toIntent())
activity?.finish()
if (viewModel.notificationsPossible.value != viewModel.notificationsEnabled) {
parentFragmentManager
.beginTransaction()
.replace(R.id.content, NotificationPermissionFragment::class.java, null)
.addToBackStack(null)
.commit()
} else { // Complete onboarding
activity?.setResult(Activity.RESULT_OK, viewModel.getOutput().toIntent())
activity?.finish()
}
}
private fun openPrivacyPolicy() {

View file

@ -88,7 +88,7 @@ fun MobileAppIntegrationView(
onClick = onFinishClicked,
modifier = Modifier.align(Alignment.End)
) {
Text(stringResource(id = commonR.string.finish))
Text(stringResource(id = commonR.string.continue_connect))
}
}
}

View file

@ -0,0 +1,84 @@
package io.homeassistant.companion.android.onboarding.notifications
import android.Manifest
import android.app.Activity
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.compose.ui.platform.ComposeView
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.composethemeadapter.MdcTheme
import io.homeassistant.companion.android.onboarding.OnboardingViewModel
import io.homeassistant.companion.android.common.R as commonR
class NotificationPermissionFragment : Fragment() {
companion object {
private const val TAG = "NotificationPermFrag"
}
private val viewModel by activityViewModels<OnboardingViewModel>()
private val permissionsRequest = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
Log.i(TAG, "Notification permission was ${if (isGranted) "granted" else "not granted"}")
viewModel.setNotifications(isGranted)
if (isGranted) onComplete()
else showPermissionDeniedDialog()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
MdcTheme {
NotificationPermissionView(
onSetNotificationsEnabled = ::setNotifications
)
}
}
}
}
private fun setNotifications(enabled: Boolean) {
if (enabled) {
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
!NotificationManagerCompat.from(requireContext()).areNotificationsEnabled()
) {
permissionsRequest.launch(Manifest.permission.POST_NOTIFICATIONS)
} else {
viewModel.setNotifications(true)
onComplete()
}
} else {
viewModel.setNotifications(false)
onComplete()
}
}
private fun showPermissionDeniedDialog() {
AlertDialog.Builder(requireContext())
.setMessage(commonR.string.onboarding_notifications_denied)
.setPositiveButton(commonR.string.continue_connect) { _, _ ->
onComplete()
}
.setCancelable(false)
.show()
}
private fun onComplete() {
activity?.setResult(Activity.RESULT_OK, viewModel.getOutput().toIntent())
activity?.finish()
}
}

View file

@ -0,0 +1,126 @@
package io.homeassistant.companion.android.onboarding.notifications
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Notifications
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.android.material.composethemeadapter.MdcTheme
import com.mikepenz.iconics.compose.Image
import com.mikepenz.iconics.typeface.IIcon
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import io.homeassistant.companion.android.common.R as commonR
@Composable
fun NotificationPermissionView(
onSetNotificationsEnabled: (Boolean) -> Unit
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.padding(16.dp)
) {
Column(
modifier = Modifier
.verticalScroll(scrollState)
.fillMaxWidth()
.padding(top = 32.dp)
.weight(1f)
) {
Icon(
imageVector = Icons.Outlined.Notifications,
contentDescription = null,
tint = colorResource(id = commonR.color.colorAccent),
modifier = Modifier
.size(48.dp)
.align(Alignment.CenterHorizontally)
)
Text(
text = stringResource(id = commonR.string.notifications),
style = MaterialTheme.typography.h5,
modifier = Modifier
.padding(vertical = 16.dp)
.align(Alignment.CenterHorizontally)
)
Text(
text = stringResource(id = commonR.string.onboarding_notifications_subtitle),
textAlign = TextAlign.Center,
modifier = Modifier
.padding(bottom = 48.dp)
.align(Alignment.CenterHorizontally)
)
NotificationPermissionBullet(
icon = CommunityMaterial.Icon.cmd_alert_decagram,
text = stringResource(id = commonR.string.onboarding_notifications_bullet_alert)
)
NotificationPermissionBullet(
icon = CommunityMaterial.Icon3.cmd_text,
text = stringResource(id = commonR.string.onboarding_notifications_bullet_commands)
)
}
Row(modifier = Modifier.padding(top = 16.dp)) {
TextButton(onClick = { onSetNotificationsEnabled(false) }) {
Text(stringResource(id = commonR.string.skip))
}
Spacer(modifier = Modifier.weight(1f))
Button(onClick = { onSetNotificationsEnabled(true) }) {
Text(stringResource(id = commonR.string.continue_connect))
}
}
}
}
@Composable
fun NotificationPermissionBullet(
icon: IIcon,
text: String
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 12.dp)
) {
Image(
asset = icon,
colorFilter = ColorFilter.tint(MaterialTheme.colors.onSurface),
contentDescription = null
)
Text(
text = text,
modifier = Modifier
.padding(start = 16.dp)
.fillMaxWidth()
)
}
}
@Preview(showSystemUi = true)
@Composable
fun NotificationPermissionViewPreview() {
MdcTheme {
NotificationPermissionView(
onSetNotificationsEnabled = {}
)
}
}

View file

@ -18,6 +18,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.content.res.AppCompatResources
import androidx.biometric.BiometricManager
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.lifecycleScope
@ -73,6 +74,10 @@ class SettingsFragment constructor(
updateBackgroundAccessPref()
}
private val requestNotificationPermissionResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
updateNotificationChannelPrefs()
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
presenter.init(this)
@ -245,10 +250,17 @@ class SettingsFragment constructor(
}
}
updateNotificationChannelPrefs()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
findPreference<Preference>("notification_permission")?.let {
it.setOnPreferenceClickListener {
openNotificationSettings()
return@setOnPreferenceClickListener true
}
}
findPreference<Preference>("notification_channels")?.let { pref ->
val uiManager = requireContext().getSystemService<UiModeManager>()
pref.isVisible = uiManager?.currentModeType != Configuration.UI_MODE_TYPE_TELEVISION
pref.setOnPreferenceClickListener {
parentFragmentManager
.beginTransaction()
@ -517,6 +529,23 @@ class SettingsFragment constructor(
}
}
private fun updateNotificationChannelPrefs() {
val notificationsEnabled =
Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
NotificationManagerCompat.from(requireContext()).areNotificationsEnabled()
findPreference<Preference>("notification_permission")?.let {
it.isVisible = !notificationsEnabled
}
findPreference<Preference>("notification_channels")?.let {
val uiManager = requireContext().getSystemService<UiModeManager>()
it.isVisible =
notificationsEnabled &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
uiManager?.currentModeType != Configuration.UI_MODE_TYPE_TELEVISION
}
}
@SuppressLint("BatteryLife")
private fun requestBackgroundAccess() {
if (!isIgnoringBatteryOptimizations()) {
@ -529,6 +558,16 @@ class SettingsFragment constructor(
}
}
private fun openNotificationSettings() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
requestNotificationPermissionResult.launch(
Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
}
)
}
}
private fun checkAndRequestPermissions(permissions: Array<String>, requestCode: Int, requestPermissions: Array<String>? = null, forceRequest: Boolean = false): Boolean {
val permissionsNeeded = mutableListOf<String>()
for (permission in permissions) {

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="@color/colorAccent"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,18.69L7.84,6.14 5.27,3.49 4,4.76l2.8,2.8v0.01c-0.52,0.99 -0.8,2.16 -0.8,3.42v5l-2,2v1h13.73l2,2L21,19.72l-1,-1.03zM12,22c1.11,0 2,-0.89 2,-2h-4c0,1.11 0.89,2 2,2zM18,14.68L18,11c0,-3.08 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68c-0.15,0.03 -0.29,0.08 -0.42,0.12 -0.1,0.03 -0.2,0.07 -0.3,0.11h-0.01c-0.01,0 -0.01,0 -0.02,0.01 -0.23,0.09 -0.46,0.2 -0.68,0.31 0,0 -0.01,0 -0.01,0.01L18,14.68z"/>
</vector>

View file

@ -129,6 +129,12 @@
android:title="@string/notifications"
android:key="notifications"
app:isPreferenceVisible="false">
<Preference
android:key="notification_permission"
android:title="@string/notification_permission"
android:summary="@string/notification_permission_summary"
app:isPreferenceVisible="false"
android:icon="@drawable/ic_notification_off" />
<Preference
android:key="notification_channels"
android:title="@string/notification_channels"

View file

@ -390,6 +390,10 @@
<string name="notification_source">Notification Source</string>
<string name="notifications">Notifications</string>
<string name="ok">OK</string>
<string name="onboarding_notifications_denied">If you want to enable notifications in the future, you can change this in Settings.</string>
<string name="onboarding_notifications_bullet_alert">Get alerted from notifications</string>
<string name="onboarding_notifications_bullet_commands">Send commands to your device</string>
<string name="onboarding_notifications_subtitle">Enable notifications to create a notify service for your device</string>
<string name="other_settings">Other Settings</string>
<string name="other">Other</string>
<string name="password">Password</string>
@ -852,6 +856,8 @@
<string name="websocket_notification_issues">Persistent Connection Issues</string>
<string name="notification_channels">Notification Channels</string>
<string name="notification_channels_summary">Manage all notification channels configured on the device. Channels control the behavior of its notifications including visibility and sound.</string>
<string name="notification_permission">Notification Permission</string>
<string name="notification_permission_summary">Home Assistant does not have permission to send you notifications. Click here to enable sending notifications.</string>
<string name="info">Information</string>
<string name="show_changelog">Show Change Log</string>
<string name="show_changelog_summary">Show the change log dialog from when the app was updated</string>

View file

@ -114,6 +114,7 @@ class PhoneSettingsListener : WearableListenerService(), DataClient.OnDataChange
val authCode = dataMap.getString("AuthCode", "")
val deviceName = dataMap.getString("DeviceName")
val deviceTrackingEnabled = dataMap.getBoolean("LocationTracking")
val notificationsEnabled = dataMap.getString("Notifications")
urlRepository.saveUrl(url)
authenticationRepository.registerAuthorizationCode(authCode)