Skip to content
13 changes: 12 additions & 1 deletion app/src/org/commcare/activities/DispatchActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import static org.commcare.commcaresupportlibrary.CommCareLauncher.SESSION_ENDPOINT_APP_ID;
import static org.commcare.connect.ConnectAppUtils.IS_LAUNCH_FROM_CONNECT;
import static org.commcare.connect.ConnectConstants.CONNECT_MANAGED_LOGIN;
import static org.commcare.connect.ConnectConstants.NOTIFICATION_ID;
import static org.commcare.connect.ConnectConstants.PERSONALID_MANAGED_LOGIN;
import static org.commcare.connect.ConnectConstants.REDIRECT_ACTION;

import android.content.Intent;
import android.os.Bundle;
Expand All @@ -20,6 +22,8 @@
import org.commcare.connect.ConnectJobHelper;
import org.commcare.connect.ConnectNavHelper;
import org.commcare.dalvik.R;
import org.commcare.google.services.analytics.AnalyticsParamValue;
import org.commcare.google.services.analytics.FirebaseAnalyticsUtil;
import org.commcare.preferences.DeveloperPreferences;
import org.commcare.recovery.measures.ExecuteRecoveryMeasuresActivity;
import org.commcare.recovery.measures.RecoveryMeasuresHelper;
Expand Down Expand Up @@ -163,7 +167,14 @@ private void dispatch() {
CommCareApp currentApp = CommCareApplication.instance().getCurrentApp();

Intent pnIntent = checkIfAnyPNIntentPresent();
if(pnIntent!=null) {
if (pnIntent != null) {
String actionType = pnIntent.getStringExtra(REDIRECT_ACTION);
FirebaseAnalyticsUtil.reportNotificationEvent(
AnalyticsParamValue.NOTIFICATION_EVENT_TYPE_CLICK,
AnalyticsParamValue.REPORT_NOTIFICATION_CLICK_NOTIFICATION_TRAY,
actionType,
pnIntent.getStringExtra(NOTIFICATION_ID)
);
startActivity(pnIntent);
}else if (currentApp == null) {
if (MultipleAppsUtil.usableAppsPresent()) {
Expand Down
49 changes: 31 additions & 18 deletions app/src/org/commcare/activities/PushNotificationActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import org.commcare.adapters.PushNotificationAdapter
import org.commcare.android.database.connect.models.PushNotificationRecord
import org.commcare.dalvik.R
import org.commcare.dalvik.databinding.ActivityPushNotificationBinding
import org.commcare.google.services.analytics.AnalyticsParamValue
import org.commcare.google.services.analytics.CCAnalyticsParam
import org.commcare.google.services.analytics.FirebaseAnalyticsUtil
import org.commcare.preferences.NotificationPrefs
import org.commcare.utils.FirebaseMessagingUtil.getIntentForPNClick

Expand Down Expand Up @@ -40,10 +43,12 @@ class PushNotificationActivity : AppCompatActivity() {
binding.rvNotifications.visibility = View.VISIBLE
binding.tvNoNotifications.visibility = View.GONE
}

isLoading -> {
binding.rvNotifications.visibility = View.GONE
binding.tvNoNotifications.visibility = View.GONE
}

else -> {
binding.rvNotifications.visibility = View.GONE
binding.tvNoNotifications.visibility = View.VISIBLE
Expand All @@ -62,21 +67,30 @@ class PushNotificationActivity : AppCompatActivity() {
setDisplayShowHomeEnabled(true)
setDisplayHomeAsUpEnabled(true)
}
pushNotificationViewModel = ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory.getInstance(application)
)[PushNotificationViewModel::class.java]
pushNotificationAdapter = PushNotificationAdapter(
listener = object :
PushNotificationAdapter.OnNotificationClickListener {
override fun onNotificationClick(notificationRecord: PushNotificationRecord) {
val activityIntent = getIntentForPNClick(application, notificationRecord)
if (activityIntent != null) {
startActivity(activityIntent)
}
}
}
)
pushNotificationViewModel =
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory.getInstance(application),
)[PushNotificationViewModel::class.java]
pushNotificationAdapter =
PushNotificationAdapter(
listener =
object :
PushNotificationAdapter.OnNotificationClickListener {
override fun onNotificationClick(notificationRecord: PushNotificationRecord) {
FirebaseAnalyticsUtil.reportNotificationEvent(
AnalyticsParamValue.NOTIFICATION_EVENT_TYPE_CLICK,
AnalyticsParamValue.REPORT_NOTIFICATION_CLICK_NOTIFICATION_HISTORY,
notificationRecord.action,
notificationRecord.notificationId,
)
val activityIntent = getIntentForPNClick(application, notificationRecord)
if (activityIntent != null) {
startActivity(activityIntent)
}
}
},
)
binding.rvNotifications.adapter = pushNotificationAdapter
}

Expand All @@ -85,8 +99,8 @@ class PushNotificationActivity : AppCompatActivity() {
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
android.R.id.home -> {
onBackPressedDispatcher.onBackPressed()
true
Expand All @@ -99,5 +113,4 @@ class PushNotificationActivity : AppCompatActivity() {

else -> super.onOptionsItemSelected(item)
}
}
}
23 changes: 23 additions & 0 deletions app/src/org/commcare/activities/connect/ConnectActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,15 @@
import org.commcare.connect.database.NotificationRecordDatabaseHelper;
import org.commcare.dalvik.R;
import org.commcare.fragments.RefreshableFragment;
import org.commcare.google.services.analytics.AnalyticsParamValue;
import org.commcare.google.services.analytics.FirebaseAnalyticsUtil;
import org.commcare.personalId.PersonalIdFeatureFlagChecker;
import org.commcare.pn.helper.NotificationBroadcastHelper;
import org.commcare.utils.FirebaseMessagingUtil;
import org.commcare.views.dialogs.CustomProgressDialog;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import javax.annotation.Nullable;
Expand All @@ -61,6 +65,7 @@ public class ConnectActivity extends NavigationHostCommCareActivity<ConnectActiv
private MenuItem notificationsMenuItem = null;

private static final int REQUEST_CODE_PERSONAL_ID_ACTIVITY = 1000;
private Map<Integer, String> menuIdToAnalyticsParam;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
Expand Down Expand Up @@ -141,6 +146,12 @@ private int handleSecureRedirect(Bundle startArgs) {
String notificationId = getIntent().getStringExtra(NOTIFICATION_ID);
if (!TextUtils.isEmpty(notificationId)) {
NotificationRecordDatabaseHelper.INSTANCE.updateReadStatus(this, notificationId, true);
FirebaseAnalyticsUtil.reportNotificationEvent(
AnalyticsParamValue.NOTIFICATION_EVENT_TYPE_CLICK,
AnalyticsParamValue.REPORT_NOTIFICATION_CLICK_NOTIFICATION_TRAY,
redirectionAction,
notificationId
);
}
startArgs.putString(REDIRECT_ACTION, redirectionAction);
startArgs.putBoolean(SHOW_LAUNCH_BUTTON, getIntent().getBooleanExtra(SHOW_LAUNCH_BUTTON, true));
Expand All @@ -165,6 +176,7 @@ public boolean onCreateOptionsMenu(Menu menu) {
notificationsMenuItem.setVisible(PersonalIdFeatureFlagChecker.isFeatureEnabled(NOTIFICATIONS));
updateNotificationIcon();

menuIdToAnalyticsParam = createMenuItemToAnalyticsParamMapping();
return super.onCreateOptionsMenu(menu);
}
private void updateNotificationIcon() {
Expand All @@ -180,6 +192,15 @@ public boolean onPrepareOptionsMenu(Menu menu) {
return super.onPrepareOptionsMenu(menu);
}

private static Map<Integer, String> createMenuItemToAnalyticsParamMapping() {
Map<Integer, String> menuIdToAnalyticsEvent = new HashMap<>();
menuIdToAnalyticsEvent.put(R.id.action_sync,
AnalyticsParamValue.ITEM_CONNECT_SYNC);
menuIdToAnalyticsEvent.put(R.id.action_bell,
AnalyticsParamValue.ITEM_NOTIFICATIONS_BELL);
return menuIdToAnalyticsEvent;
}

public ConnectJobRecord getActiveJob() {
return job;
}
Expand All @@ -194,6 +215,8 @@ private void retrieveMessages(){

@Override
public boolean onOptionsItemSelected(MenuItem item) {
FirebaseAnalyticsUtil.reportOptionsMenuItemClick(this.getClass(),
menuIdToAnalyticsParam.get(item.getItemId()));
Comment on lines +218 to +219
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against null when menu item is not in the mapping.

If a menu item not present in menuIdToAnalyticsParam is clicked (e.g., android.R.id.home from the action bar), get() will return null, which is passed to reportOptionsMenuItemClick. This could cause issues since the itemLabel parameter is not marked @Nullable.

Consider adding a null check:

     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
-        FirebaseAnalyticsUtil.reportOptionsMenuItemClick(this.getClass(),
-                menuIdToAnalyticsParam.get(item.getItemId()));
+        String analyticsParam = menuIdToAnalyticsParam.get(item.getItemId());
+        if (analyticsParam != null) {
+            FirebaseAnalyticsUtil.reportOptionsMenuItemClick(this.getClass(), analyticsParam);
+        }
         if (item.getItemId() == R.id.action_bell) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FirebaseAnalyticsUtil.reportOptionsMenuItemClick(this.getClass(),
menuIdToAnalyticsParam.get(item.getItemId()));
@Override
public boolean onOptionsItemSelected(MenuItem item) {
String analyticsParam = menuIdToAnalyticsParam.get(item.getItemId());
if (analyticsParam != null) {
FirebaseAnalyticsUtil.reportOptionsMenuItemClick(this.getClass(), analyticsParam);
}
if (item.getItemId() == R.id.action_bell) {
🤖 Prompt for AI Agents
In app/src/org/commcare/activities/connect/ConnectActivity.java around lines 212
to 213, the call passes menuIdToAnalyticsParam.get(item.getItemId()) directly
which can return null for unmapped menu IDs (e.g., android.R.id.home); add a
null guard before calling FirebaseAnalyticsUtil.reportOptionsMenuItemClick:
retrieve the mapped value into a local variable, check for null, and only call
reportOptionsMenuItemClick when the value is non-null (optionally log or skip
analytics when null).

if (item.getItemId() == R.id.action_bell) {
updateNotificationIcon();
ConnectNavHelper.goToNotification(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import org.commcare.connect.database.ConnectMessagingDatabaseHelper;
import org.commcare.connect.database.NotificationRecordDatabaseHelper;
import org.commcare.dalvik.R;
import org.commcare.google.services.analytics.AnalyticsParamValue;
import org.commcare.google.services.analytics.FirebaseAnalyticsUtil;

import static org.commcare.connect.ConnectConstants.CCC_MESSAGE;
import static org.commcare.connect.ConnectConstants.NOTIFICATION_ID;
Expand Down Expand Up @@ -72,6 +74,12 @@ private void handleRedirectIfAny() {
String action = getIntent().getStringExtra(REDIRECT_ACTION);
if (CCC_MESSAGE.equals(action)) {
PersonalIdManager.getInstance().init(this);
FirebaseAnalyticsUtil.reportNotificationEvent(
AnalyticsParamValue.NOTIFICATION_EVENT_TYPE_CLICK,
AnalyticsParamValue.REPORT_NOTIFICATION_CLICK_NOTIFICATION_TRAY,
action,
getIntent().getStringExtra(NOTIFICATION_ID)
);
PersonalIdManager.getInstance().unlockConnect(this, success -> {
if (success) {
String channelId = getIntent().getStringExtra(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,42 @@ package org.commcare.connect.database

import android.content.Context
import org.commcare.android.database.connect.models.PushNotificationRecord
import org.commcare.google.services.analytics.AnalyticsParamValue
import org.commcare.google.services.analytics.FirebaseAnalyticsUtil
import org.commcare.models.database.SqlStorage

object NotificationRecordDatabaseHelper {

private fun getStorage(context: Context): SqlStorage<PushNotificationRecord> {
return ConnectDatabaseHelper.getConnectStorage(context, PushNotificationRecord::class.java)
}
private fun getStorage(context: Context): SqlStorage<PushNotificationRecord> =
ConnectDatabaseHelper.getConnectStorage(context, PushNotificationRecord::class.java)

/**
* Fetch all notifications
*/
fun getAllNotifications(context: Context): List<PushNotificationRecord>? {
return getStorage(context).getRecordsForValues(arrayOf(), arrayOf())
}
fun getAllNotifications(context: Context): List<PushNotificationRecord>? = getStorage(context).getRecordsForValues(arrayOf(), arrayOf())

/**
* Fetch a notification from notification_id
*/
fun getNotificationById(context: Context, notificationId: String): PushNotificationRecord? {
val records = getStorage(context).getRecordsForValues(
arrayOf(PushNotificationRecord.META_NOTIFICATION_ID),
arrayOf(notificationId)
)
fun getNotificationById(
context: Context,
notificationId: String,
): PushNotificationRecord? {
val records =
getStorage(context).getRecordsForValues(
arrayOf(PushNotificationRecord.META_NOTIFICATION_ID),
arrayOf(notificationId),
)
return records.firstOrNull()
}

/**
* Update the read status for a notification using notification_id
*/
fun updateReadStatus(context: Context, notificationId: String, isRead: Boolean) {
fun updateReadStatus(
context: Context,
notificationId: String,
isRead: Boolean,
) {
val record = getNotificationById(context, notificationId) ?: return
record.readStatus = isRead
getStorage(context).write(record)
Expand Down Expand Up @@ -59,14 +65,19 @@ object NotificationRecordDatabaseHelper {

// Otherwise, write new record
storage.write(incoming)
FirebaseAnalyticsUtil.reportNotificationEvent(
AnalyticsParamValue.NOTIFICATION_EVENT_TYPE_RECEIVED,
AnalyticsParamValue.REPORT_NOTIFICATION_METHOD_PERSONAL_ID_API,
incoming.action,
incoming.notificationId,
)

// Verify persistence
val savedRecord = getNotificationById(context, incoming.notificationId)
if (savedRecord != null) {
savedNotificationIds.add(incoming.notificationId)
}
}

return savedNotificationIds
}

Expand All @@ -78,7 +89,7 @@ object NotificationRecordDatabaseHelper {
fun updateColumnForNotifications(
context: Context,
notificationIds: List<String>,
updateAction: (PushNotificationRecord) -> Unit
updateAction: (PushNotificationRecord) -> Unit,
) {
val storage = getStorage(context)

Expand All @@ -90,4 +101,4 @@ object NotificationRecordDatabaseHelper {
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.commcare.google.services.analytics;

import org.jetbrains.annotations.Nullable;

/**
* Created by amstone326 on 10/13/17.
*/
Expand All @@ -20,6 +22,8 @@ public class AnalyticsParamValue {
public static final String SWIPE = "swipe";

// Param values for options menu items
public static final String ITEM_CONNECT_SYNC = "connect_sync";
public static final String ITEM_NOTIFICATIONS_BELL = "notifications_bell";
public static final String ITEM_SETTINGS = "settings";
public static final String ITEM_UPDATE_CC = "update_commcare";
public static final String ITEM_ABOUT_CC = "about_commcare";
Expand Down Expand Up @@ -191,4 +195,13 @@ public class AnalyticsParamValue {
public static final String FAILURE_UNLOCK_FAILED = "failure_unlock_failed";
public static final String CONTINUE_WITH_FINGERPRINT = "continue_with_fingerprint";
public static final String CONTINUE_WITH_PIN = "continue_with_pin";

public static final String REPORT_NOTIFICATION_METHOD_FIREBASE = "firebase";
public static final String REPORT_NOTIFICATION_METHOD_PERSONAL_ID_API = "personal_id_api";

public static final String REPORT_NOTIFICATION_CLICK_NOTIFICATION_HISTORY = "notification_history";
public static final String REPORT_NOTIFICATION_CLICK_NOTIFICATION_TRAY = "notification_tray";
public static final String NOTIFICATION_EVENT_TYPE_CLICK = "click_notification";
public static final String NOTIFICATION_EVENT_TYPE_RECEIVED = "receive_notification";
public static final String NOTIFICATION_EVENT_TYPE_SHOW = "show_notification";
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ public class CCAnalyticsEvent {
static final String PERSONAL_ID_CONTINUE_CLICKED = "personal_id_continue_clicked";
static final String PERSONAL_ID_MESSAGE_SENT = "personal_id_message_sent";
static final String PERSONAL_ID_LINKING = "personal_id_linking";

static final String PERSONAL_ID_NOTIFICATION_RECEIVED = "personal_id_notification_received";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd discussed limiting the number of unique events that we send to analytics since there's a lifetime limit. Given that, I wonder if we want a single personalid_notification event with parameters to specify additional info? Retrieving the additional info from BigQuery doesn't add much difficulty, and then we can continue to reuse this event for any additional notification-related events that we want to log in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, although these values could be cleaned up now.

static final String PERSONAL_ID_NOTIFICATION_CLICKED = "personal_id_notification_clicked";
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
public class CCAnalyticsParam {

public static final String USER_CID = "user_cid";

static final String BUILD_NUMBER = "build_number";
static final String CC_APP_ID = "cc_app_id";
static final String CC_APP_NAME = "cc_app_name";
Expand Down Expand Up @@ -42,5 +43,8 @@ public class CCAnalyticsParam {
static final String PARAM_API_TOTAL_JOBS = "ccc_api_total_jobs";
static final String PARAM_API_NEW_JOBS = "ccc_api_new_jobs";
public static final String PERSONAL_ID_CONTINUE_CLICKED_INFO = "personal_id_continue_button_clicked_info";

static final String NOTIFICATION_EVENT_TYPE = "event_type";
static final String NOTIFICATION_ACTION = "action";
static final String NOTIFICATION_ID = "notification_id";
static final String NOTIFICATION_CLICK_METHOD = "click_method";
}
Loading