| package com.google.android.connecteddevice.notificationmsg; |
| |
| import static com.google.android.connecteddevice.notificationmsg.common.BaseNotificationDelegate.ACTION_DISMISS_NOTIFICATION; |
| import static com.google.android.connecteddevice.notificationmsg.common.BaseNotificationDelegate.ACTION_MARK_AS_READ; |
| import static com.google.android.connecteddevice.notificationmsg.common.BaseNotificationDelegate.ACTION_REPLY; |
| import static com.google.android.connecteddevice.notificationmsg.common.BaseNotificationDelegate.EXTRA_CONVERSATION_KEY; |
| import static com.google.android.connecteddevice.notificationmsg.common.BaseNotificationDelegate.EXTRA_REMOTE_INPUT_KEY; |
| import static com.google.android.connecteddevice.util.SafeLog.logw; |
| |
| import android.content.Intent; |
| import android.content.res.TypedArray; |
| import android.graphics.Color; |
| import android.graphics.Typeface; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import androidx.core.app.RemoteInput; |
| import androidx.annotation.Nullable; |
| import com.google.android.connecteddevice.model.ConnectedDevice; |
| import com.google.android.connecteddevice.notificationmsg.common.ConversationKey; |
| import com.google.android.connecteddevice.notificationmsg.proto.NotificationMsg; |
| import com.google.android.connecteddevice.service.MetaDataService; |
| |
| /** |
| * Service responsible for handling {@link NotificationMsg} messaging events from the active user's |
| * securely paired {@link ConnectedDevice}s. |
| */ |
| public class NotificationMsgService extends MetaDataService { |
| private static final String TAG = "NotificationMsgService"; |
| |
| // TODO(b/172590500) Add proper documentation to these values. |
| private static final String META_BITMAP_SIZE = |
| "com.google.android.connecteddevice.notificationmsg.notification_contact_photo_size"; |
| private static final String META_CORNER_RADIUS = |
| "com.google.android.connecteddevice.notificationmsg.contact_avatar_corner_radius_percent"; |
| private static final String META_AVATAR_LETTERS = |
| "com.google.android.connecteddevice.notificationmsg.letters_shown_for_avatar"; |
| private static final String META_DEFAULT_ICON = |
| "com.google.android.connecteddevice.notificationmsg.default_message_icon"; |
| private static final String META_TILE_DEFAULT_COLOR = |
| "com.google.android.connecteddevice.notificationmsg.letter_tile_default_color"; |
| private static final String META_TILE_COLORS = |
| "com.google.android.connecteddevice.notificationmsg.letter_tile_colors"; |
| private static final String META_FONT_COLOR = |
| "com.google.android.connecteddevice.notificationmsg.font_color"; |
| private static final String META_FONT_FAMILY = |
| "com.google.android.connecteddevice.notificationmsg.font_family"; |
| private static final String META_TEXT_STYLE = |
| "com.google.android.connecteddevice.notificationmsg.text_style"; |
| private static final String META_DEFAULT_AVATAR = |
| "com.google.android.connecteddevice.notificationmsg.default_avatar"; |
| private static final String META_DEFAULT_DISPLAY_NAME = |
| "com.google.android.connecteddevice.notificationmsg.default_display_name"; |
| private static final String META_GROUP_TITLE_SEPARATOR = |
| "com.google.android.connecteddevice.notificationmsg.group_title_separator"; |
| private static final String META_LETTER_TO_TILE_RATIO = |
| "com.google.android.connecteddevice.notificationmsg.letter_to_tile_ratio"; |
| private static final String META_CONTENT_TEXT = |
| "com.google.android.connecteddevice.notificationmsg.content_text"; |
| |
| private static final float DEFAULT_CORNER_RADIUS = 0.5f; |
| private static final int DEFAULT_AVATAR_LETTERS = 1; |
| private static final int DEFAULT_TILE_COLOR = Color.parseColor("#cccccc"); |
| private static final int[] DEFAULT_TILE_COLORS = new int[] { |
| Color.parseColor("#db4437"), |
| Color.parseColor("#e91e63"), |
| Color.parseColor("#9c27b0"), |
| Color.parseColor("#673ab7"), |
| Color.parseColor("#3f51b5"), |
| Color.parseColor("#4285f4"), |
| Color.parseColor("#039be5"), |
| Color.parseColor("#0097a7"), |
| Color.parseColor("#008577"), |
| Color.parseColor("#0f9d58"), |
| Color.parseColor("#689f38"), |
| Color.parseColor("#ef6c00"), |
| Color.parseColor("#ff5722"), |
| Color.parseColor("#757575") |
| }; |
| private static final int DEFAULT_FONT_COLOR = Color.parseColor("#ffffff"); |
| private static final String DEFAULT_FONT_FAMILY = "sans-serif-light"; |
| private static final int DEFAULT_TEXT_STYLE = 0; |
| private static final String DEFAULT_GROUP_TITLE_SEPARATOR = " • "; |
| private static final float DEFAULT_LETTER_TO_TILE_RATIO = 0.67f; |
| |
| /* NOTIFICATIONS */ |
| static final String NOTIFICATION_MSG_CHANNEL_ID = "NOTIFICATION_MSG_CHANNEL_ID"; |
| |
| private NotificationMsgDelegate notificationMsgDelegate; |
| private NotificationMsgFeature notificationMsgFeature; |
| private final IBinder binder = new LocalBinder(); |
| |
| /** API for interacting with {@link NotificationMsgService}. */ |
| public class LocalBinder extends Binder { |
| NotificationMsgService getService() { |
| return NotificationMsgService.this; |
| } |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| return binder; |
| } |
| |
| @Override |
| public void onCreate() { |
| super.onCreate(); |
| |
| notificationMsgDelegate = createNotificationMsgDelegate(); |
| notificationMsgFeature = new NotificationMsgFeature(this, notificationMsgDelegate); |
| notificationMsgFeature.start(); |
| } |
| |
| @Override |
| public void onDestroy() { |
| notificationMsgFeature.stop(); |
| super.onDestroy(); |
| } |
| |
| @Override |
| public int onStartCommand(Intent intent, int flags, int startId) { |
| if (intent == null || intent.getAction() == null) { |
| return START_STICKY; |
| } |
| |
| String action = intent.getAction(); |
| |
| switch (action) { |
| case ACTION_REPLY: |
| handleReplyIntent(intent); |
| break; |
| case ACTION_DISMISS_NOTIFICATION: |
| handleDismissNotificationIntent(intent); |
| break; |
| case ACTION_MARK_AS_READ: |
| handleMarkAsReadIntent(intent); |
| break; |
| default: |
| logw(TAG, "Unsupported action: " + action); |
| } |
| |
| return START_STICKY; |
| } |
| |
| private void handleDismissNotificationIntent(Intent intent) { |
| ConversationKey key = getConversationKey(intent); |
| if (key == null) { |
| logw(TAG, "Dropping dismiss intent. Received null conversation key."); |
| return; |
| } |
| notificationMsgFeature.sendData(key.getDeviceId(), |
| notificationMsgDelegate.dismiss(key).toByteArray()); |
| } |
| |
| private void handleMarkAsReadIntent(Intent intent) { |
| ConversationKey key = getConversationKey(intent); |
| if (key == null) { |
| logw(TAG, "Dropping mark as read intent. Received null conversation key."); |
| return; |
| } |
| notificationMsgFeature.sendData(key.getDeviceId(), |
| notificationMsgDelegate.markAsRead(key).toByteArray()); |
| } |
| |
| private void handleReplyIntent(Intent intent) { |
| ConversationKey key = getConversationKey(intent); |
| Bundle bundle = RemoteInput.getResultsFromIntent(intent); |
| if (bundle == null || key == null) { |
| logw(TAG, "Dropping voice reply intent. Received null arguments."); |
| return; |
| } |
| CharSequence message = bundle.getCharSequence(EXTRA_REMOTE_INPUT_KEY); |
| notificationMsgFeature.sendData(key.getDeviceId(), |
| notificationMsgDelegate.reply(key, message.toString()).toByteArray()); |
| } |
| |
| @Nullable |
| private static ConversationKey getConversationKey(Intent intent) { |
| return intent.getParcelableExtra(EXTRA_CONVERSATION_KEY); |
| } |
| |
| private NotificationMsgDelegate createNotificationMsgDelegate() { |
| return new NotificationMsgDelegate( |
| this, |
| getResources().getDimensionPixelSize(requireMetaResourceId(META_BITMAP_SIZE)), |
| getMetaFloat(META_CORNER_RADIUS, DEFAULT_CORNER_RADIUS), |
| getMetaInt(META_AVATAR_LETTERS, DEFAULT_AVATAR_LETTERS), |
| requireMetaResourceId(META_DEFAULT_ICON), |
| getMetaInt(META_TILE_DEFAULT_COLOR, DEFAULT_TILE_COLOR), |
| getLetterTileColors(), |
| getMetaInt(META_FONT_COLOR, DEFAULT_FONT_COLOR), |
| Typeface.create( |
| getMetaString(META_FONT_FAMILY, DEFAULT_FONT_FAMILY), |
| getMetaInt(META_TEXT_STYLE, DEFAULT_TEXT_STYLE)), |
| getResources().getDrawable( |
| requireMetaResourceId(META_DEFAULT_AVATAR), /* theme= */ null), |
| requireMetaString(META_DEFAULT_DISPLAY_NAME), |
| getMetaString(META_GROUP_TITLE_SEPARATOR, DEFAULT_GROUP_TITLE_SEPARATOR), |
| getMetaFloat(META_LETTER_TO_TILE_RATIO, DEFAULT_LETTER_TO_TILE_RATIO), |
| requireMetaResourceId(META_CONTENT_TEXT)); |
| } |
| |
| private int[] getLetterTileColors() { |
| int resId = getMetaResourceId(META_TILE_COLORS, -1); |
| if (resId == -1) { |
| return DEFAULT_TILE_COLORS; |
| } |
| TypedArray ta = getResources().obtainTypedArray(resId); |
| int defaultColor = getMetaInt(META_TILE_DEFAULT_COLOR, DEFAULT_TILE_COLOR); |
| if (ta.length() == 0) { |
| // Looks like robolectric shadow doesn't currently support |
| // obtainTypedArray and always returns length 0 array, which will make some code |
| // below that does a division by length of sColors choke. Workaround by creating |
| // an array of length 1. |
| return new int[] {defaultColor}; |
| } |
| |
| int[] colors = new int[ta.length()]; |
| for (int i = ta.length() - 1; i >= 0; i--) { |
| colors[i] = ta.getColor(i, defaultColor); |
| } |
| ta.recycle(); |
| return colors; |
| } |
| } |