Skip to content

Commit

Permalink
#8: drafted notification for at least Android 13 playing default noti…
Browse files Browse the repository at this point in the history
…fication ringtone
  • Loading branch information
arburk committed Jul 2, 2023
1 parent a0c1411 commit ba0c87f
Show file tree
Hide file tree
Showing 16 changed files with 184 additions and 43 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ Project was launched in order to learn more about native Android application dev

## Credits

### Icons & Graphics
Icons are downloaded by [flaticon](https://www.flaticon.com/free-icons/right-chevron) and created by
### Icons & Graphics
Icons are downloaded by [flaticon](https://www.flaticon.com/) and created by
- gungyoga04
- kliwir art
- Fathema Khanom
- Fathema Khanom
- justicon
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package com.github.arburk.vscp.app

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import junit.framework.TestCase.assertEquals

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<application
android:usesCleartextTraffic="false"
Expand Down Expand Up @@ -34,7 +35,6 @@
android:theme="@style/Theme.VSCPApp.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
Expand Down
67 changes: 59 additions & 8 deletions app/src/main/java/com/github/arburk/vscp/app/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
package com.github.arburk.vscp.app

import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import com.github.arburk.vscp.app.activity.PokerTimer
import com.github.arburk.vscp.app.databinding.ActivityMainBinding
import com.github.arburk.vscp.app.service.TimerService
import com.github.arburk.vscp.app.settings.AppSettingsActivity
import kotlin.system.exitProcess

const val vscp_url = "http://vscp.ch"

class MainActivity : AppCompatActivity() {

Expand All @@ -47,13 +56,21 @@ class MainActivity : AppCompatActivity() {
Intent(this, TimerService::class.java).also { intent ->
bindService(intent, timerServiceConnection, Context.BIND_AUTO_CREATE)
}
createNotificationChannel()
deepNavigationHandler(navController)
}

private fun deepNavigationHandler(navController: NavController) {
val menuFragment = intent.getStringExtra("targetFragment")
if (menuFragment != null && menuFragment == PokerTimer::class.java.simpleName) {
navController.navigate(R.id.action_MainScreen_to_Timer)
}
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
Log.i("Lifecycle", "onCreateOptionsMenu $menu")
menuInflater.inflate(R.menu.menu_main, menu)

return true
}

Expand All @@ -68,17 +85,20 @@ class MainActivity : AppCompatActivity() {
return true
}

R.id.action_webpage -> openVscpWebsite()
R.id.action_webpage -> {
openVscpWebsite()
return true
}

R.id.action_exit -> exitProcess(0)
else -> super.onOptionsItemSelected(item)
}
}

private fun openVscpWebsite(): Boolean {
val vscpIntent = Intent(Intent.ACTION_VIEW)
vscpIntent.data = Uri.parse(vscp_url)
startActivity(vscpIntent)
return true
private fun openVscpWebsite() {
startActivity(Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(getString(R.string.vscp_url))
})
}

override fun onSupportNavigateUp(): Boolean {
Expand Down Expand Up @@ -106,4 +126,35 @@ class MainActivity : AppCompatActivity() {
Log.v("MainActivity", "I say goodbye.")
unbindService(timerServiceConnection)
}

private fun createNotificationChannel() {
Log.v("MainActivity", "createNotificationChannel")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = getString(R.string.notification_channel_id)
NotificationChannel(
channelId,
getString(R.string.channel_name),
NotificationManager.IMPORTANCE_HIGH
).apply {
description = getString(R.string.channel_description)
}.also {
// Register the channel with the system. You can't change the importance
// or other notification behaviors after this.
ContextCompat.getSystemService(this, NotificationManager::class.java)!!
.createNotificationChannel(it)
}
Log.v("MainActivity", "$channelId created")

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Ask for permission to post notifications
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED
) {
registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import androidx.navigation.fragment.findNavController
import com.github.arburk.vscp.app.R
import com.github.arburk.vscp.app.databinding.MainScreenBinding

/**
* A simple [Fragment] subclass as the default destination in the navigation.
*/
class MainScreen : Fragment() {

private var _binding: MainScreenBinding? = null
Expand All @@ -20,19 +17,13 @@ class MainScreen : Fragment() {
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View {
_binding = MainScreenBinding.inflate(inflater, container, false)
return binding.root

}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

binding.pokerTimer.setOnClickListener {
findNavController().navigate(R.id.action_MainScreen_to_Timer)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import android.widget.BaseAdapter
import android.widget.EditText
import android.widget.ImageButton
import android.widget.ListView
import android.widget.TextView
import androidx.lifecycle.LiveData
import com.github.arburk.vscp.app.MainActivity
import com.github.arburk.vscp.app.R
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
package com.github.arburk.vscp.app.service

import android.Manifest
import android.app.Notification
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.media.RingtoneManager
import android.os.Binder
import android.os.Build
import android.os.HandlerThread
import android.os.IBinder
import android.os.Process.THREAD_PRIORITY_FOREGROUND
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.TaskStackBuilder
import com.github.arburk.vscp.app.MainActivity
import com.github.arburk.vscp.app.R
import com.github.arburk.vscp.app.activity.PokerTimer
import com.github.arburk.vscp.app.activity.PokerTimerViewModel
import com.github.arburk.vscp.app.model.Blind
import com.github.arburk.vscp.app.model.ConfigModel
Expand All @@ -31,7 +44,8 @@ class TimerService : Service(), SharedPreferences.OnSharedPreferenceChangeListen
private var timerTask: TimerTask? = null

private var viewModels: List<PokerTimerViewModel> = arrayListOf()
private val timerServiceThread: HandlerThread = HandlerThread(TimerService::class.simpleName, THREAD_PRIORITY_FOREGROUND)
private val timerServiceThread: HandlerThread =
HandlerThread(TimerService::class.simpleName, THREAD_PRIORITY_FOREGROUND)

inner class TimerServiceBinder : Binder() {
fun getService(): TimerService = this@TimerService
Expand Down Expand Up @@ -111,6 +125,7 @@ class TimerService : Service(), SharedPreferences.OnSharedPreferenceChangeListen
remainingSeconds++
} else {
jumpLevel(1)
postNotification()
}
}
}
Expand All @@ -119,6 +134,70 @@ class TimerService : Service(), SharedPreferences.OnSharedPreferenceChangeListen
Log.v("TimerService", "remainingSeconds: $remainingSeconds")
}

private fun postNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notifyMgr = NotificationManagerCompat.from(this)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// check permissions to prevent exception to not yet opted in by user
notifyMgr.apply {
ActivityCompat.checkSelfPermission(this@TimerService, Manifest.permission.POST_NOTIFICATIONS)
.also {
if (it != PackageManager.PERMISSION_GRANTED) {
Log.v("TimerService", "Skip notification due to missing permissions")
return
}
}
}

val notification = buildNotification()
notifyMgr.notify(notification.hashCode(), notification)

return
}
}
// TODO: handle lower versions
}

private fun buildNotification(): Notification {
/* refactore timerService first to use follwoing appraach
val timerIntent = NavDeepLinkBuilder(this)
.setComponentName(MainActivity::class.java)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.Timer)
//.setArguments(bundle)
.createPendingIntent()*/
val mainActivity = Intent(this, MainActivity::class.java).apply {
putExtra("targetFragment", PokerTimer::class.java.simpleName)
}

val pendingIntentTimer: PendingIntent? = TaskStackBuilder.create(this).run {
// Add the intent, which inflates the back stack
addNextIntentWithParentStack(mainActivity)
// Get the PendingIntent containing the entire back stack
getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}


return NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
.setContentTitle("Next level ${currentRound + 1}")
.setContentText("${getCurrentBlind().small} / ${getCurrentBlind().getBig()}")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setTimeoutAfter(config.minPerRound * 60L * 1000)
.setSmallIcon(R.mipmap.icon_webp) // TODO: add proper icon, see also issue #10
// TODO apply custom selected ringtone
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setVibrate(LongArray(1) { 500L })
// TOODO: Fix issue with correct timer handling
// .setContentIntent(pendingIntentTimer)
.build()
}


fun pauseTimer() {
Log.v("TimerService", "pause timer was requested")
if (running) {
Expand All @@ -135,7 +214,7 @@ class TimerService : Service(), SharedPreferences.OnSharedPreferenceChangeListen
}

private fun resetTimerTaskToMaxTime() {
remainingSeconds = config.minPerRound * 60
remainingSeconds = config.minPerRound * 60 - 50
updateViewModels()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.github.arburk.vscp.app.settings

import android.app.NotificationChannel
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.util.AttributeSet
import android.util.Log
import androidx.preference.Preference
import com.github.arburk.vscp.app.R


class NotificationSoundPreference (context: Context, attrs: AttributeSet?) : Preference(context, attrs),
Expand All @@ -26,13 +29,29 @@ class NotificationSoundPreference (context: Context, attrs: AttributeSet?) : Pre

override fun onPreferenceClick(preference: Preference): Boolean {
Log.v(javaClass.simpleName, "onPreferenceClick for '${preference.key}'")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
putExtra(Settings.EXTRA_APP_PACKAGE, context.getText(R.string.package_name))
putExtra(Settings.EXTRA_CHANNEL_ID, context.getText(R.string.notification_channel_id))
}

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
intent.putExtra(Settings.EXTRA_CHANNEL_FILTER_LIST, NotificationChannel.EDIT_SOUND)
}

context.startActivity(intent)
}



/*
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, RingtoneManager.TITLE_COLUMN_INDEX)
context.startActivity(intent)
*/
return true
}

Expand Down
Binary file added app/src/main/res/mipmap-hdpi/icon_webp.webp
Binary file not shown.
Binary file added app/src/main/res/mipmap-mdpi/icon_webp.webp
Binary file not shown.
Empty file.
Binary file added app/src/main/res/mipmap-xxhdpi/icon_webp.webp
Binary file not shown.
Binary file added app/src/main/res/mipmap-xxxhdpi/icon_webp.webp
Binary file not shown.
6 changes: 6 additions & 0 deletions app/src/main/res/values/constants.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="package_name">com.github.arburk.vscp.app</string>
<string name="vscp_url">http://vscp.ch</string>
<string name="notification_channel_id">NOTIFY_CHANNEL_TS_ID</string>
</resources>
Loading

0 comments on commit ba0c87f

Please sign in to comment.