Skip to content

Commit

Permalink
Merge pull request #64 from rymorale/api21-issues
Browse files Browse the repository at this point in the history
Low level API fixes (MOB-21261, MOB-21262, MOB-21447, and MOB-21460)
  • Loading branch information
rymorale authored Aug 7, 2024
2 parents 0f1e0e9 + de8a0f7 commit b7c6746
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ internal object AEPPushNotificationBuilder {
)
.setNotificationDeleteAction(context, trackerActivityClass)

// API21 specific fixes
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
// intent handling fix, see MOB-21261 for more info
builder.setOnlyAlertOnce(true)
// heads up display fix, see MOB-21447 for more info
builder.setCustomHeadsUpContentView(expandedLayout)
}

// API22 and 23 heads up display fix, see MOB-21447 for more info
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M || Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) {
builder.setCustomHeadsUpContentView(smallLayout)
}

// if not from intent, set custom sound, note this applies to API 25 and lower only as
// API 26 and up set the sound on the notification channel
if (!pushTemplate.isFromIntent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.os.Build
import android.widget.RemoteViews
import androidx.annotation.VisibleForTesting
import androidx.core.app.NotificationCompat
Expand Down Expand Up @@ -47,7 +48,14 @@ internal object BasicNotificationBuilder {
Log.trace(LOG_TAG, SELF_TAG, "Building a basic template push notification.")
val packageName = context.packageName
val smallLayout = RemoteViews(packageName, R.layout.push_template_collapsed)
val expandedLayout = RemoteViews(packageName, R.layout.push_template_expanded)
var expandedLayout = RemoteViews(packageName, R.layout.push_template_expanded)

// API23 and below have a limited notification display area. the notification elements
// must use a smaller area to fix buttons not showing on expanded notification.
// see MOB-21262 for more info
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
expandedLayout = RemoteViews(packageName, R.layout.push_template_expanded_api23)
}

val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import androidx.core.app.RemoteInput
Expand Down Expand Up @@ -44,6 +45,11 @@ internal object InputBoxNotificationBuilder {
trackerActivityClass: Class<out Activity>?,
broadcastReceiverClass: Class<out BroadcastReceiver>?
): NotificationCompat.Builder {

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
throw NotificationConstructionFailedException("Input box push notification on devices below Android N is not supported.")
}

Log.trace(LOG_TAG, SELF_TAG, "Building an input box template push notification.")
val packageName = context.packageName
val smallLayout = RemoteViews(packageName, R.layout.push_template_collapsed)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/basic_expanded_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:clipChildren="false"
android:orientation="vertical"
android:theme="@style/DayNightTheme">

<ImageView
android:id="@+id/large_icon"
android:layout_height="@dimen/large_icon_height"
android:layout_width="@dimen/large_icon_width"
android:layout_alignParentRight="true" />

<TextView
android:id="@+id/notification_title"
style="@style/Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:textAlignment="viewStart"
android:layout_toLeftOf="@+id/large_icon"/>

<TextView
android:id="@+id/notification_body_expanded"
style="@style/Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/notification_title"
android:layout_gravity="start"
android:textAlignment="viewStart"
android:layout_toLeftOf="@+id/large_icon"/>

<ImageView
android:id="@+id/expanded_template_image"
android:layout_width="match_parent"
android:layout_height="@dimen/basic_expanded_image_height_api23"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:layout_below="@id/notification_body_expanded"/>
</RelativeLayout>
1 change: 1 addition & 0 deletions code/notificationbuilder/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<resources>
<dimen name="large_icon_height">35dp</dimen>
<dimen name="large_icon_width">35dp</dimen>
<dimen name="basic_expanded_image_height_api23">160dp</dimen>
<dimen name="basic_expanded_image_height">200dp</dimen>
<dimen name="carousel_item_layout_height">200dp</dimen>
<dimen name="carousel_item_layout_width">250dp</dimen>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.util.IntentData
import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData
import io.mockk.unmockkAll
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertNull
import junit.framework.TestCase.assertTrue
import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
Expand Down Expand Up @@ -126,6 +129,97 @@ class AEPPushNotificationBuilderTest {
verifyNotificationViewDataAndColors(pushTemplate)
}

@Config(sdk = [21])
@Test
fun `construct should set alert only once for API level below 22`() {
val pushTemplate = BasicPushTemplate(MapData(dataMap))
val notification =
AEPPushNotificationBuilder.construct(
context,
pushTemplate,
CHANNEL_ID_TO_USE,
trackerActivityClass,
smallLayout,
expandedLayout,
CONTAINER_LAYOUT_VIEW_ID
).build()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
assertTrue(NotificationCompat.getOnlyAlertOnce(notification))
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
assertFalse(NotificationCompat.getOnlyAlertOnce(notification))
}
}

@Config(sdk = [21])
@Test
fun `construct should set the expanded layout as the custom heads up content view for API level below 22`() {
val pushTemplate = BasicPushTemplate(MapData(dataMap))
val notificationBuilder =
AEPPushNotificationBuilder.construct(
context,
pushTemplate,
CHANNEL_ID_TO_USE,
trackerActivityClass,
smallLayout,
expandedLayout,
CONTAINER_LAYOUT_VIEW_ID
)
assertEquals(expandedLayout, notificationBuilder.headsUpContentView)
}

@Config(sdk = [22])
@Test
fun `construct should set the small layout as the custom heads up content view for API 22`() {
val pushTemplate = BasicPushTemplate(MapData(dataMap))
val notificationBuilder =
AEPPushNotificationBuilder.construct(
context,
pushTemplate,
CHANNEL_ID_TO_USE,
trackerActivityClass,
smallLayout,
expandedLayout,
CONTAINER_LAYOUT_VIEW_ID
)
assertEquals(smallLayout, notificationBuilder.headsUpContentView)
}

@Config(sdk = [23])
@Test
fun `construct should set the small layout as the custom heads up content view for API 23`() {
val pushTemplate = BasicPushTemplate(MapData(dataMap))
val notificationBuilder =
AEPPushNotificationBuilder.construct(
context,
pushTemplate,
CHANNEL_ID_TO_USE,
trackerActivityClass,
smallLayout,
expandedLayout,
CONTAINER_LAYOUT_VIEW_ID
)
assertEquals(smallLayout, notificationBuilder.headsUpContentView)
}

@Config(sdk = [24])
@Test
fun `construct should not set a custom heads up content view for API 24 or higher`() {
val pushTemplate = BasicPushTemplate(MapData(dataMap))
val notificationBuilder =
AEPPushNotificationBuilder.construct(
context,
pushTemplate,
CHANNEL_ID_TO_USE,
trackerActivityClass,
smallLayout,
expandedLayout,
CONTAINER_LAYOUT_VIEW_ID
)
assertNotEquals(smallLayout, notificationBuilder.headsUpContentView)
assertNotEquals(expandedLayout, notificationBuilder.headsUpContentView)
}

@Test
fun `construct should not set notification sound if pushTemplate sound is invalid`() {
dataMap.replaceValueInMap(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockAEP
import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedBasicPushTemplateWithAllKeys
import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedBasicPushTemplateWithRequiredData
import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData
import com.google.common.base.Verify.verify
import io.mockk.every
import io.mockk.mockkConstructor
import io.mockk.mockkObject
Expand Down Expand Up @@ -102,6 +101,19 @@ class BasicNotificationBuilderTest {
verify(exactly = 1) { any<RemoteViews>().setRemoteViewImage(MOCKED_IMAGE_URI, R.id.expanded_template_image) }
}

@Config(sdk = [23])
@Test
fun `construct should use the api23 expanded layout for API level below 24`() {
val pushTemplate = provideMockedBasicPushTemplateWithAllKeys()
val notificationBuilder = BasicNotificationBuilder.construct(
context,
pushTemplate,
trackerActivityClass,
broadcastReceiverClass
)
assertEquals(R.layout.push_template_expanded_api23, notificationBuilder.bigContentView.layoutId)
}

@Test
fun `construct should set parameters for notification builder properly`() {
val pushTemplate = provideMockedBasicPushTemplateWithAllKeys()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ package com.adobe.marketing.mobile.notificationbuilder.internal.builders
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.os.Build
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException
import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants
import com.adobe.marketing.mobile.notificationbuilder.R
import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteImage
Expand All @@ -35,7 +37,6 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provide
import com.adobe.marketing.mobile.notificationbuilder.internal.templates.removeKeysFromMap
import com.adobe.marketing.mobile.notificationbuilder.internal.templates.replaceValueInMap
import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData
import com.google.common.base.Verify.verify
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
Expand All @@ -52,6 +53,7 @@ import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.Shadows
import org.robolectric.annotation.Config
import org.robolectric.util.ReflectionHelpers
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
Expand Down Expand Up @@ -93,6 +95,19 @@ class InputBoxNotificationBuilderTest {
assertEquals(NotificationCompat.Builder::class.java, notificationBuilder.javaClass)
}

@Test(expected = NotificationConstructionFailedException::class)
fun `construct throws exception when API is less than 24`() {
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 23)
val pushTemplate = provideMockedInputBoxPushTemplateWithRequiredData(true)
val result = InputBoxNotificationBuilder.construct(
context,
pushTemplate,
trackerActivityClass,
broadcastReceiverClass
)
assertNull(result)
}

@Test
fun `construct should not have any inputText action if the template is created from intent`() {
val pushTemplate = provideMockedInputBoxPushTemplateWithRequiredData(true)
Expand Down
2 changes: 1 addition & 1 deletion code/testapp/src/main/assets/basic/basic.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"adb_body_ex": "Basic push template with action buttons.",
"adb_a_type": "WEBURL",
"adb_uri": "https://chess.com/games",
"adb_image": "https://i.ibb.co/QN078XB/Resize-image-project.jpg",
"adb_image": "https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
"adb_act": "[{\"label\":\"Go to chess.com\",\"uri\":\"https:\/\/chess.com\/games\/552\",\"type\":\"DEEPLINK\"},{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"OPENAPP\"}]",
"adb_sound": "bells",
"adb_channel_id": "2024",
Expand Down
2 changes: 1 addition & 1 deletion code/testapp/src/main/assets/basic/basic_colors.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"adb_body_ex": "Basic push template with action buttons.",
"adb_a_type": "WEBURL",
"adb_uri": "https://chess.com/games",
"adb_image": "https://i.ibb.co/QN078XB/Resize-image-project.jpg",
"adb_image": "https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
"adb_act": "[{\"label\":\"Go to chess.com\",\"uri\":\"https:\/\/chess.com\/games\/552\",\"type\":\"DEEPLINK\"},{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"OPENAPP\"}]",
"adb_small_icon": "chat_bubble",
"adb_large_icon": "https://cdn-icons-png.flaticon.com/128/864/864639.png",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,21 @@ import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants
import com.adobe.marketing.mobile.services.Log
import com.adobe.marketing.mobile.services.ServiceProvider

class NotificationTrackerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
when (intent?.action) {
val intent = intent
when (intent.action) {
"Input Received" -> {
val results = RemoteInput.getResultsFromIntent(intent)
val quickReplyResult = results?.getCharSequence("developer intent action name")
Log.debug("MyApp", "NotificationTrackerActivity", "input box quick reply result: $quickReplyResult")
}
PushTemplateConstants.NotificationAction.CLICKED -> executePushAction(intent)
else -> {}
}
Expand Down

0 comments on commit b7c6746

Please sign in to comment.