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