Skip to content

Commit 4a945bf

Browse files
Implement notifications.
1 parent 23eff21 commit 4a945bf

File tree

17 files changed

+239
-0
lines changed

17 files changed

+239
-0
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
package="jp.co.optim.techblog_android_notification_sample">
44

5+
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
6+
57
<application
68
android:allowBackup="true"
79
android:icon="@mipmap/ic_launcher"
@@ -23,6 +25,9 @@
2325
android:launchMode="singleTop"
2426
android:screenOrientation="portrait"
2527
android:exported="false" />
28+
29+
<receiver android:name=".receiver.CallRefusedReceiver"
30+
android:exported="false" />
2631
</application>
2732

2833
</manifest>

app/src/main/java/jp/co/optim/techblog_android_notification_sample/activity/CallActivity.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,30 @@ import android.os.Bundle
44
import androidx.appcompat.app.AppCompatActivity
55
import androidx.fragment.app.Fragment
66
import jp.co.optim.techblog_android_notification_sample.R
7+
import jp.co.optim.techblog_android_notification_sample.constants.NotificationId
78
import jp.co.optim.techblog_android_notification_sample.extension.logD
89
import jp.co.optim.techblog_android_notification_sample.extension.logI
910
import jp.co.optim.techblog_android_notification_sample.extension.tag
1011
import jp.co.optim.techblog_android_notification_sample.fragment.CallingFragment
1112
import jp.co.optim.techblog_android_notification_sample.fragment.TalkingFragment
13+
import jp.co.optim.techblog_android_notification_sample.notification.NotificationPostman
1214

1315
class CallActivity : AppCompatActivity(), CallingFragment.Callback, TalkingFragment.Callback {
1416

1517
companion object {
1618
const val CALL_ACCEPTED = "call_accepted"
1719
}
1820

21+
private val notificationPostman = NotificationPostman()
22+
1923
private val fragmentManager = supportFragmentManager
2024

2125
override fun onCreate(savedInstanceState: Bundle?) {
2226
super.onCreate(savedInstanceState)
2327
setContentView(R.layout.activity_call)
2428

29+
notificationPostman.delete(this, NotificationId.CALL.id)
30+
2531
val callAccepted = intent.getBooleanExtra(CALL_ACCEPTED, false)
2632
logI("Is call accepted: $callAccepted")
2733
replaceFragment(if (callAccepted) TalkingFragment() else CallingFragment())

app/src/main/java/jp/co/optim/techblog_android_notification_sample/activity/MainActivity.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import androidx.appcompat.app.AppCompatActivity
66
import jp.co.optim.techblog_android_notification_sample.R
77
import jp.co.optim.techblog_android_notification_sample.extension.logI
88
import jp.co.optim.techblog_android_notification_sample.extension.tag
9+
import jp.co.optim.techblog_android_notification_sample.notification.NotificationPostman
910
import kotlinx.android.synthetic.main.activity_main.*
1011

1112
class MainActivity : AppCompatActivity() {
1213

14+
private val notificationPostman = NotificationPostman()
15+
1316
override fun onCreate(savedInstanceState: Bundle?) {
1417
super.onCreate(savedInstanceState)
1518
setContentView(R.layout.activity_main)
@@ -21,9 +24,11 @@ class MainActivity : AppCompatActivity() {
2124
}
2225
button_postNotification.setOnClickListener {
2326
logI("Clicked postNotification.")
27+
notificationPostman.post(this)
2428
}
2529
button_postCallNotification.setOnClickListener {
2630
logI("Clicked postCallNotification.")
31+
notificationPostman.postCall(this)
2732
}
2833
}
2934
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package jp.co.optim.techblog_android_notification_sample.constants
2+
3+
enum class ChannelType(val id: String) {
4+
5+
NORMAL("normal"),
6+
7+
CALL("call"),
8+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package jp.co.optim.techblog_android_notification_sample.constants
2+
3+
import kotlin.random.Random
4+
5+
enum class NotificationId(val id: Int) {
6+
7+
CALL(-1000),
8+
9+
CALL_ACCEPT(-1001),
10+
11+
CALL_REFUSE(-1010),
12+
;
13+
14+
companion object {
15+
fun generateRandomId(): Int = Random.nextInt()
16+
}
17+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package jp.co.optim.techblog_android_notification_sample.notification
2+
3+
import android.app.Notification
4+
import android.app.NotificationChannel
5+
import android.app.NotificationManager
6+
import android.app.PendingIntent
7+
import android.content.ContentResolver
8+
import android.content.Context
9+
import android.content.Intent
10+
import android.graphics.BitmapFactory
11+
import android.media.AudioAttributes
12+
import android.media.AudioManager
13+
import android.media.RingtoneManager
14+
import android.net.Uri
15+
import android.os.Build
16+
import androidx.annotation.ColorRes
17+
import androidx.annotation.StringRes
18+
import androidx.core.app.NotificationCompat
19+
import androidx.core.content.ContextCompat
20+
import androidx.core.text.HtmlCompat
21+
import jp.co.optim.techblog_android_notification_sample.R
22+
import jp.co.optim.techblog_android_notification_sample.activity.CallActivity
23+
import jp.co.optim.techblog_android_notification_sample.constants.ChannelType
24+
import jp.co.optim.techblog_android_notification_sample.constants.NotificationId
25+
import jp.co.optim.techblog_android_notification_sample.receiver.CallRefusedReceiver
26+
import java.util.*
27+
28+
class NotificationPostman {
29+
30+
fun post(
31+
context: Context,
32+
@StringRes titleResId: Int = R.string.app_name,
33+
@StringRes messageResId: Int = R.string.notification_message
34+
) {
35+
val title = context.getString(titleResId)
36+
val message = context.getString(messageResId)
37+
val notificationId = NotificationId.generateRandomId()
38+
39+
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
40+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
41+
val channel = NotificationChannel(ChannelType.NORMAL.id, title, NotificationManager.IMPORTANCE_HIGH)
42+
manager.createNotificationChannel(channel)
43+
}
44+
45+
val notification = NotificationCompat.Builder(context, ChannelType.NORMAL.id).apply {
46+
setSmallIcon(R.drawable.app_icon_small)
47+
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.app_icon))
48+
setContentTitle(title)
49+
setContentText(message)
50+
setContentIntent(defaultPendingIntent(context, notificationId))
51+
setPriority(NotificationCompat.PRIORITY_HIGH)
52+
setDefaults(NotificationCompat.DEFAULT_ALL)
53+
setAutoCancel(true)
54+
setStyle(NotificationCompat.BigTextStyle().bigText(message))
55+
}.build()
56+
57+
manager.notify(notificationId, notification)
58+
}
59+
60+
fun postCall(
61+
context: Context,
62+
@StringRes titleResId: Int = R.string.app_name,
63+
@StringRes messageResId: Int = R.string.notification_call_message
64+
) {
65+
val title = context.getString(titleResId)
66+
val message = context.getString(messageResId)
67+
val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
68+
// val ringtoneUri = Uri.parse(String.format(
69+
// Locale.US, "%s://%s/%d",
70+
// ContentResolver.SCHEME_ANDROID_RESOURCE, context.packageName, R.raw.custom_ringtone
71+
// ))
72+
73+
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
74+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
75+
val channel = NotificationChannel(ChannelType.CALL.id, title, NotificationManager.IMPORTANCE_HIGH).apply {
76+
val attributes = AudioAttributes.Builder().apply {
77+
setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
78+
setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
79+
}.build()
80+
setSound(ringtoneUri, attributes)
81+
}
82+
manager.createNotificationChannel(channel)
83+
}
84+
85+
val notification = NotificationCompat.Builder(context, ChannelType.CALL.id).apply {
86+
setSmallIcon(R.drawable.app_icon_small)
87+
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.app_icon))
88+
setContentTitle(title)
89+
setContentText(message)
90+
setPriority(NotificationCompat.PRIORITY_HIGH)
91+
setDefaults(NotificationCompat.DEFAULT_SOUND)
92+
setCategory(NotificationCompat.CATEGORY_CALL)
93+
setAutoCancel(true)
94+
setStyle(NotificationCompat.BigTextStyle().bigText(message))
95+
setSound(ringtoneUri, AudioManager.STREAM_RING)
96+
setFullScreenIntent(callPendingIntent(context, NotificationId.CALL.id), true)
97+
addAction(
98+
R.drawable.refuse_button,
99+
getColorString(context, R.string.button_refuse, R.color.colorRefuse),
100+
refusePendingIntent(context, NotificationId.CALL_REFUSE.id)
101+
)
102+
addAction(
103+
R.drawable.accept_button,
104+
getColorString(context, R.string.button_accept, R.color.colorAccept),
105+
callPendingIntent(context, NotificationId.CALL_ACCEPT.id, true)
106+
)
107+
}.build()
108+
109+
notification.flags = notification.flags or Notification.FLAG_NO_CLEAR or Notification.FLAG_INSISTENT
110+
manager.notify(NotificationId.CALL.id, notification)
111+
}
112+
113+
fun delete(context: Context, notificationId: Int) {
114+
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
115+
manager.cancel(notificationId)
116+
}
117+
118+
private fun defaultPendingIntent(
119+
context: Context, notificationId: Int
120+
): PendingIntent = PendingIntent.getActivity(
121+
context,
122+
notificationId,
123+
context.packageManager.getLaunchIntentForPackage(context.packageName),
124+
PendingIntent.FLAG_CANCEL_CURRENT
125+
)
126+
127+
private fun callPendingIntent(
128+
context: Context, notificationId: Int, accepted: Boolean = false
129+
): PendingIntent = PendingIntent.getActivity(
130+
context,
131+
notificationId,
132+
Intent(context, CallActivity::class.java).putExtra(CallActivity.CALL_ACCEPTED, accepted),
133+
PendingIntent.FLAG_UPDATE_CURRENT
134+
)
135+
136+
private fun refusePendingIntent(
137+
context: Context, notificationId: Int
138+
): PendingIntent = PendingIntent.getBroadcast(
139+
context,
140+
notificationId,
141+
Intent(context, CallRefusedReceiver::class.java),
142+
PendingIntent.FLAG_UPDATE_CURRENT
143+
)
144+
145+
private fun getColorString(
146+
context: Context, @StringRes stringResId: Int, @ColorRes colorResId: Int
147+
): CharSequence = HtmlCompat.fromHtml(
148+
String.format(
149+
Locale.US, "<font color=\"%d\">%s</font>",
150+
ContextCompat.getColor(context, colorResId),
151+
context.getString(stringResId)
152+
),
153+
HtmlCompat.FROM_HTML_MODE_LEGACY
154+
)
155+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package jp.co.optim.techblog_android_notification_sample.receiver
2+
3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import jp.co.optim.techblog_android_notification_sample.constants.NotificationId
7+
import jp.co.optim.techblog_android_notification_sample.extension.logD
8+
import jp.co.optim.techblog_android_notification_sample.notification.NotificationPostman
9+
10+
class CallRefusedReceiver: BroadcastReceiver() {
11+
12+
val notificationPostman =
13+
NotificationPostman();
14+
15+
override fun onReceive(context: Context?, intent: Intent?) {
16+
logD("onReceive()")
17+
18+
if (context != null) {
19+
notificationPostman.delete(context, NotificationId.CALL.id)
20+
}
21+
}
22+
}
3.51 KB
Loading
2.57 KB
Loading
4.81 KB
Loading

0 commit comments

Comments
 (0)