1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.messenger; 18 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothMapClient; 21 import android.content.Intent; 22 import com.android.car.messenger.log.L; 23 24 import androidx.annotation.Nullable; 25 26 /** 27 * Represents a message obtained via MAP service from a connected Bluetooth device. 28 */ 29 class MapMessage { 30 private static final String TAG = "CM.MapMessage"; 31 private String mDeviceAddress; 32 private String mHandle; 33 private String mSenderName; 34 @Nullable 35 private String mSenderContactUri; 36 private String mMessageText; 37 private long mReceiveTime; 38 private boolean mIsReadOnPhone; 39 private boolean mShouldInclude; 40 41 /** 42 * Constructs a {@link MapMessage} from {@code intent} that was received from MAP service via 43 * {@link BluetoothMapClient#ACTION_MESSAGE_RECEIVED} broadcast. 44 * 45 * @param intent intent received from MAP service 46 * @return message constructed from extras in {@code intent}, or null if this is a group 47 * conversation. 48 * @throws NullPointerException if {@code intent} is missing the device extra 49 * @throws IllegalArgumentException if {@code intent} is missing any other required extras 50 */ 51 @Nullable parseFrom(Intent intent)52 public static MapMessage parseFrom(Intent intent) { 53 if (intent.getStringArrayExtra(Intent.EXTRA_CC) != null 54 && intent.getStringArrayExtra(Intent.EXTRA_CC).length > 0) { 55 L.i(TAG, "Skipping group conversation message"); 56 return null; 57 } 58 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 59 String handle = intent.getStringExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE); 60 String senderUri = intent.getStringExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_URI); 61 String senderName = intent.getStringExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME); 62 String messageText = intent.getStringExtra(android.content.Intent.EXTRA_TEXT); 63 long receiveTime = intent.getLongExtra(BluetoothMapClient.EXTRA_MESSAGE_TIMESTAMP, 64 System.currentTimeMillis()); 65 boolean isRead = intent.getBooleanExtra(BluetoothMapClient.EXTRA_MESSAGE_READ_STATUS, 66 false); 67 68 return new MapMessage( 69 device.getAddress(), 70 handle, 71 senderName, 72 senderUri, 73 messageText, 74 receiveTime, 75 isRead 76 ); 77 } 78 MapMessage(String deviceAddress, String handle, String senderName, @Nullable String senderContactUri, String messageText, long receiveTime, boolean isRead)79 private MapMessage(String deviceAddress, 80 String handle, 81 String senderName, 82 @Nullable String senderContactUri, 83 String messageText, 84 long receiveTime, 85 boolean isRead) { 86 boolean missingDevice = (deviceAddress == null); 87 boolean missingHandle = (handle == null); 88 boolean missingSenderName = (senderName == null); 89 boolean missingText = (messageText == null); 90 if (missingDevice || missingHandle || missingSenderName || missingText) { 91 StringBuilder builder = new StringBuilder("Missing required fields:"); 92 if (missingDevice) { 93 builder.append(" device"); 94 } 95 if (missingHandle) { 96 builder.append(" handle"); 97 } 98 if (missingSenderName) { 99 builder.append(" senderName"); 100 } 101 if (missingText) { 102 builder.append(" messageText"); 103 } 104 throw new IllegalArgumentException(builder.toString()); 105 } 106 mDeviceAddress = deviceAddress; 107 mHandle = handle; 108 mMessageText = messageText; 109 mSenderContactUri = senderContactUri; 110 mSenderName = senderName; 111 mReceiveTime = receiveTime; 112 mIsReadOnPhone = isRead; 113 mShouldInclude = true; 114 } 115 116 /** 117 * Returns the bluetooth address of the device from which this message was received. 118 */ getDeviceAddress()119 public String getDeviceAddress() { 120 return mDeviceAddress; 121 } 122 123 /** 124 * Returns a unique handle for this message. 125 * Note: The handle is only required to be unique for the lifetime of a single MAP session. 126 */ getHandle()127 public String getHandle() { 128 return mHandle; 129 } 130 131 /** 132 * Returns the milliseconds since epoch at which this message notification was received on the 133 * head-unit. 134 */ getReceiveTime()135 public long getReceiveTime() { 136 return mReceiveTime; 137 } 138 139 /** 140 * Returns the contact name as obtained from the device. 141 * If contact is in the device's address-book, this is typically the contact name. 142 * Otherwise it will be the phone number. 143 */ getSenderName()144 public String getSenderName() { 145 return mSenderName; 146 } 147 148 /** 149 * Returns the sender's phone number available as a URI string. 150 * Note: iPhone's don't provide these. 151 */ 152 @Nullable getSenderContactUri()153 public String getSenderContactUri() { 154 return mSenderContactUri; 155 } 156 157 /** 158 * Returns the actual content of the message. 159 */ getMessageText()160 public String getMessageText() { 161 return mMessageText; 162 } 163 164 /** 165 * Sets the message to be excluded from the notification. Messages that have been read aloud on 166 * the car, or that have been dismissed by the user should be excluded from the notification if/ 167 * when the notification gets updated. Note: this state will not be propagated to the phone. 168 */ excludeFromNotification()169 public void excludeFromNotification() { 170 mShouldInclude = false; 171 } 172 173 /** 174 * Returns {@code true} if message was read on the phone before it was received on the car. 175 */ isReadOnPhone()176 public boolean isReadOnPhone() { 177 return mIsReadOnPhone; 178 } 179 180 /** 181 * Returns {@code true} if message should be included in the notification. Messages that 182 * have been read aloud on the car, or that have been dismissed by the user should be excluded 183 * from the notification if/when the notification gets updated. 184 */ shouldIncludeInNotification()185 public boolean shouldIncludeInNotification() { 186 return mShouldInclude; 187 } 188 189 @Override toString()190 public String toString() { 191 return "MapMessage{" + 192 "mDeviceAddress=" + mDeviceAddress + 193 ", mHandle='" + mHandle + '\'' + 194 ", mMessageText='" + mMessageText + '\'' + 195 ", mSenderContactUri='" + mSenderContactUri + '\'' + 196 ", mSenderName='" + mSenderName + '\'' + 197 ", mReceiveTime=" + mReceiveTime + '\'' + 198 ", mIsReadOnPhone= " + mIsReadOnPhone + '\'' + 199 ", mShouldInclude= " + mShouldInclude + 200 "}"; 201 } 202 } 203