Loading android/app/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java +1 −1 Original line number Diff line number Diff line Loading @@ -46,7 +46,7 @@ public class BluetoothMapbMessageMime extends BluetoothMapbMessage { * jpeg data or the text.getBytes("utf-8") */ String getDataAsString() { public String getDataAsString() { String result = null; String charset = mCharsetName; // Figure out if we support the charset, else fall back to UTF-8, as this is what Loading android/app/src/com/android/bluetooth/mapclient/MapClientContent.java 0 → 100644 +499 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.mapclient; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothMapClient; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.provider.Telephony; import android.provider.Telephony.Mms; import android.provider.Telephony.MmsSms; import android.provider.Telephony.Sms; import android.telephony.PhoneNumberUtils; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.util.ArraySet; import android.util.Log; import com.android.bluetooth.map.BluetoothMapbMessageMime; import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart; import com.android.vcard.VCardConstants; import com.android.vcard.VCardEntry; import com.android.vcard.VCardProperty; import com.google.android.mms.pdu.PduHeaders; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Set; class MapClientContent { private static final String INBOX_PATH = "telecom/msg/inbox"; private static final String TAG = "MapClientContent"; private static final int DEFAULT_CHARSET = 106; private static final int ORIGINATOR_ADDRESS_TYPE = 137; private static final int RECIPIENT_ADDRESS_TYPE = 151; final BluetoothDevice mDevice; private final Context mContext; private final Callbacks mCallbacks; private final ContentResolver mResolver; ContentObserver mContentObserver; String mPhoneNumber = null; private int mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private SubscriptionManager mSubscriptionManager; private HashMap<String, Uri> mHandleToUriMap = new HashMap<>(); private HashMap<Uri, MessageStatus> mUriToHandleMap = new HashMap<>(); /** * Callbacks * API to notify about statusChanges as observed from the content provider */ interface Callbacks { void onMessageStatusChanged(String handle, int status); } /** * MapClientContent manages all interactions between Bluetooth and the messaging provider. * * Changes to the database are mirrored between the remote and local providers, specifically new * messages, changes to read status, and removal of messages. * * context: the context that all content provider interactions are conducted * MceStateMachine: the interface to send outbound updates such as when a message is read * locally * device: the associated Bluetooth device used for associating messages with a subscription */ MapClientContent(Context context, Callbacks callbacks, BluetoothDevice device) { mContext = context; mDevice = device; mCallbacks = callbacks; mResolver = mContext.getContentResolver(); mSubscriptionManager = (SubscriptionManager) mContext .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); mSubscriptionManager .addSubscriptionInfoRecord(device.getAddress(), /*device.getName()*/"TEST", 0, SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM); SubscriptionInfo info = mSubscriptionManager .getActiveSubscriptionInfoForIcc(mDevice.getAddress()); if (info != null) { mSubscriptionId = info.getSubscriptionId(); mSubscriptionManager.setDisplayNumber(mPhoneNumber, mSubscriptionId); } mContentObserver = new ContentObserver(null) { @Override public boolean deliverSelfNotifications() { return false; } @Override public void onChange(boolean selfChange) { logV("onChange"); findChangeInDatabase(); } @Override public void onChange(boolean selfChange, Uri uri) { logV("onChange" + uri.toString()); findChangeInDatabase(); } }; clearMessages(); mResolver.registerContentObserver(Sms.CONTENT_URI, true, mContentObserver); mResolver.registerContentObserver(Mms.CONTENT_URI, true, mContentObserver); mResolver.registerContentObserver(MmsSms.CONTENT_URI, true, mContentObserver); } private static void logD(String message) { if (MapClientService.DBG) { Log.d(TAG, message); } } private static void logV(String message) { if (MapClientService.VDBG) { Log.v(TAG, message); } } /** * parseLocalNumber * * Determine the connected phone's number by extracting it from an inbound or outbound mms * message. This number is necessary such that group messages can be displayed correctly. */ void parseLocalNumber(Bmessage message) { if (mPhoneNumber != null) { return; } if (INBOX_PATH.equals(message.getFolder())) { ArrayList<VCardEntry> recipients = message.getRecipients(); if (recipients != null && !recipients.isEmpty()) { mPhoneNumber = PhoneNumberUtils.extractNetworkPortion( recipients.get(0).getPhoneList().get(0).getNumber()); } } else { mPhoneNumber = PhoneNumberUtils.extractNetworkPortion(getOriginatorNumber(message)); } logV("Found phone number: " + mPhoneNumber); } /** * storeMessage * * Store a message in database with the associated handle and timestamp. * The handle is used to associate the local message with the remote message. */ void storeMessage(Bmessage message, String handle, Long timestamp) { switch (message.getType()) { case MMS: storeMms(message, handle, timestamp); return; case SMS_CDMA: case SMS_GSM: storeSms(message, handle, timestamp); return; default: logD("Request to store unsupported message type: " + message.getType()); } } private void storeSms(Bmessage message, String handle, Long timestamp) { logD("storeSms"); logV(message.toString()); VCardEntry originator = message.getOriginator(); String recipients; if (INBOX_PATH.equals(message.getFolder())) { recipients = getOriginatorNumber(message); } else { recipients = message.getRecipients().get(0).getPhoneList().get(0).getNumber(); } logV("Received SMS from Number " + recipients); String messageContent; Uri contentUri = INBOX_PATH.equalsIgnoreCase(message.getFolder()) ? Sms.Inbox.CONTENT_URI : Sms.Sent.CONTENT_URI; ContentValues values = new ContentValues(); long threadId = getThreadId(message); int readStatus = message.getStatus() == Bmessage.Status.READ ? 1 : 0; values.put(Sms.THREAD_ID, threadId); values.put(Sms.ADDRESS, recipients); values.put(Sms.BODY, message.getBodyContent()); values.put(Sms.SUBSCRIPTION_ID, mSubscriptionId); values.put(Sms.DATE, timestamp); values.put(Sms.READ, readStatus); Uri results = mResolver.insert(contentUri, values); mHandleToUriMap.put(handle, results); mUriToHandleMap.put(results, new MessageStatus(handle, readStatus)); logD("Map InsertedThread" + results); } /** * deleteMessage * remove a message from the local provider based on a remote change */ void deleteMessage(String handle) { logD("deleting handle" + handle); Uri messageToChange = mHandleToUriMap.get(handle); if (messageToChange != null) { mResolver.delete(messageToChange, null); } } /** * markRead * mark a message read in the local provider based on a remote change */ void markRead(String handle) { logD("marking read " + handle); Uri messageToChange = mHandleToUriMap.get(handle); if (messageToChange != null) { ContentValues values = new ContentValues(); values.put(Sms.READ, 1); mResolver.update(messageToChange, values, null); } } /** * findChangeInDatabase * compare the current state of the local content provider to the expected state and propagate * changes to the remote. */ private void findChangeInDatabase() { HashMap<Uri, MessageStatus> originalUriToHandleMap; HashMap<Uri, MessageStatus> duplicateUriToHandleMap; originalUriToHandleMap = mUriToHandleMap; duplicateUriToHandleMap = new HashMap<>(originalUriToHandleMap); for (Uri uri : new Uri[]{Mms.CONTENT_URI, Sms.CONTENT_URI}) { Cursor cursor = mResolver.query(uri, null, null, null, null); while (cursor.moveToNext()) { Uri index = Uri .withAppendedPath(uri, cursor.getString(cursor.getColumnIndex("_id"))); int readStatus = cursor.getInt(cursor.getColumnIndex(Sms.READ)); MessageStatus currentMessage = duplicateUriToHandleMap.remove(index); if (currentMessage != null && currentMessage.mRead != readStatus) { logV(currentMessage.mHandle); currentMessage.mRead = readStatus; mCallbacks.onMessageStatusChanged(currentMessage.mHandle, BluetoothMapClient.READ); } } } for (HashMap.Entry record : duplicateUriToHandleMap.entrySet()) { logV("Deleted " + ((MessageStatus) record.getValue()).mHandle); originalUriToHandleMap.remove(record.getKey()); mCallbacks.onMessageStatusChanged(((MessageStatus) record.getValue()).mHandle, BluetoothMapClient.DELETED); } } private void storeMms(Bmessage message, String handle, Long timestamp) { logD("storeMms"); logV(message.toString()); try { parseLocalNumber(message); ContentValues values = new ContentValues(); long threadId = getThreadId(message); BluetoothMapbMessageMime mmsBmessage = new BluetoothMapbMessageMime(); mmsBmessage.parseMsgPart(message.getBodyContent()); int read = message.getStatus() == Bmessage.Status.READ ? 1 : 0; logD("Parsed"); values.put(Mms.SUBSCRIPTION_ID, mSubscriptionId); values.put(Mms.THREAD_ID, threadId); values.put(Mms.DATE, timestamp / 1000L); values.put(Mms.TEXT_ONLY, true); values.put(Mms.MESSAGE_BOX, Mms.MESSAGE_BOX_INBOX); values.put(Mms.READ, read); values.put(Mms.SEEN, 0); values.put(Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ); values.put(Mms.MMS_VERSION, PduHeaders.CURRENT_MMS_VERSION); values.put(Mms.PRIORITY, PduHeaders.PRIORITY_NORMAL); values.put(Mms.READ_REPORT, PduHeaders.VALUE_NO); values.put(Mms.TRANSACTION_ID, "T" + Long.toHexString(System.currentTimeMillis())); values.put(Mms.DELIVERY_REPORT, PduHeaders.VALUE_NO); values.put(Mms.LOCKED, 0); values.put(Mms.CONTENT_TYPE, "application/vnd.wap.multipart.related"); values.put(Mms.MESSAGE_CLASS, PduHeaders.MESSAGE_CLASS_PERSONAL_STR); values.put(Mms.MESSAGE_SIZE, mmsBmessage.getSize()); Uri results = mResolver.insert(Mms.CONTENT_URI, values); logD("Map InsertedThread" + results); for (MimePart part : mmsBmessage.getMimeParts()) { storeMmsPart(part, results); } storeAddressPart(message, results); Uri contentUri = INBOX_PATH.equalsIgnoreCase(message.getFolder()) ? Mms.Inbox.CONTENT_URI : Mms.Sent.CONTENT_URI; String messageContent = mmsBmessage.getMessageAsText(); values.put(Mms.Part.CONTENT_TYPE, "plain/text"); values.put(Mms.SUBSCRIPTION_ID, mSubscriptionId); mUriToHandleMap.put(results, new MessageStatus(handle, read)); } catch (Exception e) { Log.e(TAG, e.toString()); throw e; } } private Uri storeMmsPart(MimePart messagePart, Uri messageUri) { ContentValues values = new ContentValues(); values.put(Mms.Part.CONTENT_TYPE, "text/plain"); values.put(Mms.Part.CHARSET, DEFAULT_CHARSET); values.put(Mms.Part.FILENAME, "text_1.txt"); values.put(Mms.Part.NAME, "text_1.txt"); values.put(Mms.Part.CONTENT_ID, messagePart.mContentId); values.put(Mms.Part.CONTENT_LOCATION, messagePart.mContentLocation); values.put(Mms.Part.TEXT, messagePart.getDataAsString()); Uri contentUri = Uri.parse(messageUri.toString() + "/part"); Uri results = mResolver.insert(contentUri, values); logD("Inserted" + results); return results; } private void storeAddressPart(Bmessage message, Uri messageUri) { ContentValues values = new ContentValues(); Uri contentUri = Uri.parse(messageUri.toString() + "/addr"); String originator = getOriginatorNumber(message); values.put(Mms.Addr.CHARSET, DEFAULT_CHARSET); values.put(Mms.Addr.ADDRESS, originator); values.put(Mms.Addr.TYPE, ORIGINATOR_ADDRESS_TYPE); mResolver.insert(contentUri, values); Set<String> messageContacts = new ArraySet<>(); getRecipientsFromMessage(message, messageContacts); for (String recipient : messageContacts) { values.put(Mms.Addr.ADDRESS, recipient); values.put(Mms.Addr.TYPE, RECIPIENT_ADDRESS_TYPE); mResolver.insert(contentUri, values); } } private Uri insertIntoMmsTable(String subject) { ContentValues mmsValues = new ContentValues(); mmsValues.put(Mms.TEXT_ONLY, 1); mmsValues.put(Mms.MESSAGE_TYPE, 128); mmsValues.put(Mms.SUBJECT, subject); return mResolver.insert(Mms.CONTENT_URI, mmsValues); } /** * clearMessages * clean up the content provider on startup and shutdown */ void clearMessages() { mResolver.unregisterContentObserver(mContentObserver); mResolver.delete(Sms.CONTENT_URI, Sms.SUBSCRIPTION_ID + " =? ", new String[]{Integer.toString(mSubscriptionId)}); mResolver.delete(Mms.CONTENT_URI, Mms.SUBSCRIPTION_ID + " =? ", new String[]{Integer.toString(mSubscriptionId)}); } /** * getThreadId * utilize the originator and recipients to obtain the thread id */ private long getThreadId(Bmessage message) { Set<String> messageContacts = new ArraySet<>(); String originator = PhoneNumberUtils.extractNetworkPortion(getOriginatorNumber(message)); if (originator != null) { messageContacts.add(originator); } getRecipientsFromMessage(message, messageContacts); messageContacts.remove(mPhoneNumber); logV("Contacts = " + messageContacts.toString()); return Telephony.Threads.getOrCreateThreadId(mContext, messageContacts); } private void getRecipientsFromMessage(Bmessage message, Set<String> messageContacts) { List<VCardEntry> recipients = message.getRecipients(); for (VCardEntry recipient : recipients) { List<VCardEntry.PhoneData> phoneData = recipient.getPhoneList(); if (phoneData != null && phoneData.size() > 0) { messageContacts .add(PhoneNumberUtils.extractNetworkPortion(phoneData.get(0).getNumber())); } } } private String getOriginatorNumber(Bmessage message) { VCardEntry originator = message.getOriginator(); if (originator != null) { List<VCardEntry.PhoneData> phoneData = originator.getPhoneList(); if (phoneData != null && phoneData.size() > 0) { return phoneData.get(0).getNumber(); } } return null; } /** * addThreadContactToEntries * utilizing the thread id fill in the appropriate fields of bmsg with the intended recipients */ boolean addThreadContactsToEntries(Bmessage bmsg, String thread) { String threadId = Uri.parse(thread).getLastPathSegment(); logD("MATCHING THREAD" + threadId); Cursor cursor = mResolver .query(Uri.withAppendedPath(MmsSms.CONTENT_CONVERSATIONS_URI, threadId + "/recipients"), null, null, null, null); if (cursor.moveToNext()) { logD("Columns" + Arrays.toString(cursor.getColumnNames())); logV("CONTACT LIST: " + cursor.getString(cursor.getColumnIndex("recipient_ids"))); addRecipientsToEntries(bmsg, cursor.getString(cursor.getColumnIndex("recipient_ids")).split(" ")); return true; } else { Log.w(TAG, "Thread Not Found"); return false; } } private void addRecipientsToEntries(Bmessage bmsg, String[] recipients) { logV("CONTACT LIST: " + Arrays.toString(recipients)); for (String recipient : recipients) { Cursor cursor = mResolver .query(Uri.parse("content://mms-sms/canonical-address/" + recipient), null, null, null, null); while (cursor.moveToNext()) { String number = cursor.getString(cursor.getColumnIndex(Mms.Addr.ADDRESS)); logV("CONTACT number: " + number); VCardEntry destEntry = new VCardEntry(); VCardProperty destEntryPhone = new VCardProperty(); destEntryPhone.setName(VCardConstants.PROPERTY_TEL); destEntryPhone.addValues(number); destEntry.addProperty(destEntryPhone); bmsg.addRecipient(destEntry); } } } /** * MessageStatus * * Helper class to store associations between remote and local provider based on message handle * and read status */ class MessageStatus { String mHandle; int mRead; MessageStatus(String handle, int read) { mHandle = handle; mRead = read; } @Override public boolean equals(Object other) { return ((other instanceof MessageStatus) && ((MessageStatus) other).mHandle .equals(mHandle)); } } } android/app/src/com/android/bluetooth/mapclient/MapClientService.java +2 −2 Original line number Diff line number Diff line Loading @@ -456,8 +456,8 @@ public class MapClientService extends ProfileService { } private MapClientService getService() { if (!Utils.checkCaller()) { Log.w(TAG, "MAP call not allowed for non-active user"); if (!Utils.checkCaller() && !MapUtils.isSystemUser()) { Log.w(TAG, "MAP call not allowed for non-active and non-system user."); return null; } Loading android/app/src/com/android/bluetooth/mapclient/MapUtils.java +5 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.bluetooth.mapclient; import android.os.SystemProperties; import android.os.UserHandle; import com.android.bluetooth.Utils; import com.android.internal.annotations.VisibleForTesting; Loading @@ -32,6 +33,10 @@ class MapUtils { sMnsService = service; } static boolean isSystemUser() { return UserHandle.getCallingUserId() == UserHandle.USER_SYSTEM; } static MnsService newMnsServiceInstance(MapClientService mapClientService) { return (sMnsService == null) ? new MnsService(mapClientService) : sMnsService; } Loading android/app/src/com/android/bluetooth/mapclient/MceStateMachine.java +35 −13 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
android/app/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java +1 −1 Original line number Diff line number Diff line Loading @@ -46,7 +46,7 @@ public class BluetoothMapbMessageMime extends BluetoothMapbMessage { * jpeg data or the text.getBytes("utf-8") */ String getDataAsString() { public String getDataAsString() { String result = null; String charset = mCharsetName; // Figure out if we support the charset, else fall back to UTF-8, as this is what Loading
android/app/src/com/android/bluetooth/mapclient/MapClientContent.java 0 → 100644 +499 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.mapclient; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothMapClient; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.provider.Telephony; import android.provider.Telephony.Mms; import android.provider.Telephony.MmsSms; import android.provider.Telephony.Sms; import android.telephony.PhoneNumberUtils; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.util.ArraySet; import android.util.Log; import com.android.bluetooth.map.BluetoothMapbMessageMime; import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart; import com.android.vcard.VCardConstants; import com.android.vcard.VCardEntry; import com.android.vcard.VCardProperty; import com.google.android.mms.pdu.PduHeaders; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Set; class MapClientContent { private static final String INBOX_PATH = "telecom/msg/inbox"; private static final String TAG = "MapClientContent"; private static final int DEFAULT_CHARSET = 106; private static final int ORIGINATOR_ADDRESS_TYPE = 137; private static final int RECIPIENT_ADDRESS_TYPE = 151; final BluetoothDevice mDevice; private final Context mContext; private final Callbacks mCallbacks; private final ContentResolver mResolver; ContentObserver mContentObserver; String mPhoneNumber = null; private int mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private SubscriptionManager mSubscriptionManager; private HashMap<String, Uri> mHandleToUriMap = new HashMap<>(); private HashMap<Uri, MessageStatus> mUriToHandleMap = new HashMap<>(); /** * Callbacks * API to notify about statusChanges as observed from the content provider */ interface Callbacks { void onMessageStatusChanged(String handle, int status); } /** * MapClientContent manages all interactions between Bluetooth and the messaging provider. * * Changes to the database are mirrored between the remote and local providers, specifically new * messages, changes to read status, and removal of messages. * * context: the context that all content provider interactions are conducted * MceStateMachine: the interface to send outbound updates such as when a message is read * locally * device: the associated Bluetooth device used for associating messages with a subscription */ MapClientContent(Context context, Callbacks callbacks, BluetoothDevice device) { mContext = context; mDevice = device; mCallbacks = callbacks; mResolver = mContext.getContentResolver(); mSubscriptionManager = (SubscriptionManager) mContext .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); mSubscriptionManager .addSubscriptionInfoRecord(device.getAddress(), /*device.getName()*/"TEST", 0, SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM); SubscriptionInfo info = mSubscriptionManager .getActiveSubscriptionInfoForIcc(mDevice.getAddress()); if (info != null) { mSubscriptionId = info.getSubscriptionId(); mSubscriptionManager.setDisplayNumber(mPhoneNumber, mSubscriptionId); } mContentObserver = new ContentObserver(null) { @Override public boolean deliverSelfNotifications() { return false; } @Override public void onChange(boolean selfChange) { logV("onChange"); findChangeInDatabase(); } @Override public void onChange(boolean selfChange, Uri uri) { logV("onChange" + uri.toString()); findChangeInDatabase(); } }; clearMessages(); mResolver.registerContentObserver(Sms.CONTENT_URI, true, mContentObserver); mResolver.registerContentObserver(Mms.CONTENT_URI, true, mContentObserver); mResolver.registerContentObserver(MmsSms.CONTENT_URI, true, mContentObserver); } private static void logD(String message) { if (MapClientService.DBG) { Log.d(TAG, message); } } private static void logV(String message) { if (MapClientService.VDBG) { Log.v(TAG, message); } } /** * parseLocalNumber * * Determine the connected phone's number by extracting it from an inbound or outbound mms * message. This number is necessary such that group messages can be displayed correctly. */ void parseLocalNumber(Bmessage message) { if (mPhoneNumber != null) { return; } if (INBOX_PATH.equals(message.getFolder())) { ArrayList<VCardEntry> recipients = message.getRecipients(); if (recipients != null && !recipients.isEmpty()) { mPhoneNumber = PhoneNumberUtils.extractNetworkPortion( recipients.get(0).getPhoneList().get(0).getNumber()); } } else { mPhoneNumber = PhoneNumberUtils.extractNetworkPortion(getOriginatorNumber(message)); } logV("Found phone number: " + mPhoneNumber); } /** * storeMessage * * Store a message in database with the associated handle and timestamp. * The handle is used to associate the local message with the remote message. */ void storeMessage(Bmessage message, String handle, Long timestamp) { switch (message.getType()) { case MMS: storeMms(message, handle, timestamp); return; case SMS_CDMA: case SMS_GSM: storeSms(message, handle, timestamp); return; default: logD("Request to store unsupported message type: " + message.getType()); } } private void storeSms(Bmessage message, String handle, Long timestamp) { logD("storeSms"); logV(message.toString()); VCardEntry originator = message.getOriginator(); String recipients; if (INBOX_PATH.equals(message.getFolder())) { recipients = getOriginatorNumber(message); } else { recipients = message.getRecipients().get(0).getPhoneList().get(0).getNumber(); } logV("Received SMS from Number " + recipients); String messageContent; Uri contentUri = INBOX_PATH.equalsIgnoreCase(message.getFolder()) ? Sms.Inbox.CONTENT_URI : Sms.Sent.CONTENT_URI; ContentValues values = new ContentValues(); long threadId = getThreadId(message); int readStatus = message.getStatus() == Bmessage.Status.READ ? 1 : 0; values.put(Sms.THREAD_ID, threadId); values.put(Sms.ADDRESS, recipients); values.put(Sms.BODY, message.getBodyContent()); values.put(Sms.SUBSCRIPTION_ID, mSubscriptionId); values.put(Sms.DATE, timestamp); values.put(Sms.READ, readStatus); Uri results = mResolver.insert(contentUri, values); mHandleToUriMap.put(handle, results); mUriToHandleMap.put(results, new MessageStatus(handle, readStatus)); logD("Map InsertedThread" + results); } /** * deleteMessage * remove a message from the local provider based on a remote change */ void deleteMessage(String handle) { logD("deleting handle" + handle); Uri messageToChange = mHandleToUriMap.get(handle); if (messageToChange != null) { mResolver.delete(messageToChange, null); } } /** * markRead * mark a message read in the local provider based on a remote change */ void markRead(String handle) { logD("marking read " + handle); Uri messageToChange = mHandleToUriMap.get(handle); if (messageToChange != null) { ContentValues values = new ContentValues(); values.put(Sms.READ, 1); mResolver.update(messageToChange, values, null); } } /** * findChangeInDatabase * compare the current state of the local content provider to the expected state and propagate * changes to the remote. */ private void findChangeInDatabase() { HashMap<Uri, MessageStatus> originalUriToHandleMap; HashMap<Uri, MessageStatus> duplicateUriToHandleMap; originalUriToHandleMap = mUriToHandleMap; duplicateUriToHandleMap = new HashMap<>(originalUriToHandleMap); for (Uri uri : new Uri[]{Mms.CONTENT_URI, Sms.CONTENT_URI}) { Cursor cursor = mResolver.query(uri, null, null, null, null); while (cursor.moveToNext()) { Uri index = Uri .withAppendedPath(uri, cursor.getString(cursor.getColumnIndex("_id"))); int readStatus = cursor.getInt(cursor.getColumnIndex(Sms.READ)); MessageStatus currentMessage = duplicateUriToHandleMap.remove(index); if (currentMessage != null && currentMessage.mRead != readStatus) { logV(currentMessage.mHandle); currentMessage.mRead = readStatus; mCallbacks.onMessageStatusChanged(currentMessage.mHandle, BluetoothMapClient.READ); } } } for (HashMap.Entry record : duplicateUriToHandleMap.entrySet()) { logV("Deleted " + ((MessageStatus) record.getValue()).mHandle); originalUriToHandleMap.remove(record.getKey()); mCallbacks.onMessageStatusChanged(((MessageStatus) record.getValue()).mHandle, BluetoothMapClient.DELETED); } } private void storeMms(Bmessage message, String handle, Long timestamp) { logD("storeMms"); logV(message.toString()); try { parseLocalNumber(message); ContentValues values = new ContentValues(); long threadId = getThreadId(message); BluetoothMapbMessageMime mmsBmessage = new BluetoothMapbMessageMime(); mmsBmessage.parseMsgPart(message.getBodyContent()); int read = message.getStatus() == Bmessage.Status.READ ? 1 : 0; logD("Parsed"); values.put(Mms.SUBSCRIPTION_ID, mSubscriptionId); values.put(Mms.THREAD_ID, threadId); values.put(Mms.DATE, timestamp / 1000L); values.put(Mms.TEXT_ONLY, true); values.put(Mms.MESSAGE_BOX, Mms.MESSAGE_BOX_INBOX); values.put(Mms.READ, read); values.put(Mms.SEEN, 0); values.put(Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ); values.put(Mms.MMS_VERSION, PduHeaders.CURRENT_MMS_VERSION); values.put(Mms.PRIORITY, PduHeaders.PRIORITY_NORMAL); values.put(Mms.READ_REPORT, PduHeaders.VALUE_NO); values.put(Mms.TRANSACTION_ID, "T" + Long.toHexString(System.currentTimeMillis())); values.put(Mms.DELIVERY_REPORT, PduHeaders.VALUE_NO); values.put(Mms.LOCKED, 0); values.put(Mms.CONTENT_TYPE, "application/vnd.wap.multipart.related"); values.put(Mms.MESSAGE_CLASS, PduHeaders.MESSAGE_CLASS_PERSONAL_STR); values.put(Mms.MESSAGE_SIZE, mmsBmessage.getSize()); Uri results = mResolver.insert(Mms.CONTENT_URI, values); logD("Map InsertedThread" + results); for (MimePart part : mmsBmessage.getMimeParts()) { storeMmsPart(part, results); } storeAddressPart(message, results); Uri contentUri = INBOX_PATH.equalsIgnoreCase(message.getFolder()) ? Mms.Inbox.CONTENT_URI : Mms.Sent.CONTENT_URI; String messageContent = mmsBmessage.getMessageAsText(); values.put(Mms.Part.CONTENT_TYPE, "plain/text"); values.put(Mms.SUBSCRIPTION_ID, mSubscriptionId); mUriToHandleMap.put(results, new MessageStatus(handle, read)); } catch (Exception e) { Log.e(TAG, e.toString()); throw e; } } private Uri storeMmsPart(MimePart messagePart, Uri messageUri) { ContentValues values = new ContentValues(); values.put(Mms.Part.CONTENT_TYPE, "text/plain"); values.put(Mms.Part.CHARSET, DEFAULT_CHARSET); values.put(Mms.Part.FILENAME, "text_1.txt"); values.put(Mms.Part.NAME, "text_1.txt"); values.put(Mms.Part.CONTENT_ID, messagePart.mContentId); values.put(Mms.Part.CONTENT_LOCATION, messagePart.mContentLocation); values.put(Mms.Part.TEXT, messagePart.getDataAsString()); Uri contentUri = Uri.parse(messageUri.toString() + "/part"); Uri results = mResolver.insert(contentUri, values); logD("Inserted" + results); return results; } private void storeAddressPart(Bmessage message, Uri messageUri) { ContentValues values = new ContentValues(); Uri contentUri = Uri.parse(messageUri.toString() + "/addr"); String originator = getOriginatorNumber(message); values.put(Mms.Addr.CHARSET, DEFAULT_CHARSET); values.put(Mms.Addr.ADDRESS, originator); values.put(Mms.Addr.TYPE, ORIGINATOR_ADDRESS_TYPE); mResolver.insert(contentUri, values); Set<String> messageContacts = new ArraySet<>(); getRecipientsFromMessage(message, messageContacts); for (String recipient : messageContacts) { values.put(Mms.Addr.ADDRESS, recipient); values.put(Mms.Addr.TYPE, RECIPIENT_ADDRESS_TYPE); mResolver.insert(contentUri, values); } } private Uri insertIntoMmsTable(String subject) { ContentValues mmsValues = new ContentValues(); mmsValues.put(Mms.TEXT_ONLY, 1); mmsValues.put(Mms.MESSAGE_TYPE, 128); mmsValues.put(Mms.SUBJECT, subject); return mResolver.insert(Mms.CONTENT_URI, mmsValues); } /** * clearMessages * clean up the content provider on startup and shutdown */ void clearMessages() { mResolver.unregisterContentObserver(mContentObserver); mResolver.delete(Sms.CONTENT_URI, Sms.SUBSCRIPTION_ID + " =? ", new String[]{Integer.toString(mSubscriptionId)}); mResolver.delete(Mms.CONTENT_URI, Mms.SUBSCRIPTION_ID + " =? ", new String[]{Integer.toString(mSubscriptionId)}); } /** * getThreadId * utilize the originator and recipients to obtain the thread id */ private long getThreadId(Bmessage message) { Set<String> messageContacts = new ArraySet<>(); String originator = PhoneNumberUtils.extractNetworkPortion(getOriginatorNumber(message)); if (originator != null) { messageContacts.add(originator); } getRecipientsFromMessage(message, messageContacts); messageContacts.remove(mPhoneNumber); logV("Contacts = " + messageContacts.toString()); return Telephony.Threads.getOrCreateThreadId(mContext, messageContacts); } private void getRecipientsFromMessage(Bmessage message, Set<String> messageContacts) { List<VCardEntry> recipients = message.getRecipients(); for (VCardEntry recipient : recipients) { List<VCardEntry.PhoneData> phoneData = recipient.getPhoneList(); if (phoneData != null && phoneData.size() > 0) { messageContacts .add(PhoneNumberUtils.extractNetworkPortion(phoneData.get(0).getNumber())); } } } private String getOriginatorNumber(Bmessage message) { VCardEntry originator = message.getOriginator(); if (originator != null) { List<VCardEntry.PhoneData> phoneData = originator.getPhoneList(); if (phoneData != null && phoneData.size() > 0) { return phoneData.get(0).getNumber(); } } return null; } /** * addThreadContactToEntries * utilizing the thread id fill in the appropriate fields of bmsg with the intended recipients */ boolean addThreadContactsToEntries(Bmessage bmsg, String thread) { String threadId = Uri.parse(thread).getLastPathSegment(); logD("MATCHING THREAD" + threadId); Cursor cursor = mResolver .query(Uri.withAppendedPath(MmsSms.CONTENT_CONVERSATIONS_URI, threadId + "/recipients"), null, null, null, null); if (cursor.moveToNext()) { logD("Columns" + Arrays.toString(cursor.getColumnNames())); logV("CONTACT LIST: " + cursor.getString(cursor.getColumnIndex("recipient_ids"))); addRecipientsToEntries(bmsg, cursor.getString(cursor.getColumnIndex("recipient_ids")).split(" ")); return true; } else { Log.w(TAG, "Thread Not Found"); return false; } } private void addRecipientsToEntries(Bmessage bmsg, String[] recipients) { logV("CONTACT LIST: " + Arrays.toString(recipients)); for (String recipient : recipients) { Cursor cursor = mResolver .query(Uri.parse("content://mms-sms/canonical-address/" + recipient), null, null, null, null); while (cursor.moveToNext()) { String number = cursor.getString(cursor.getColumnIndex(Mms.Addr.ADDRESS)); logV("CONTACT number: " + number); VCardEntry destEntry = new VCardEntry(); VCardProperty destEntryPhone = new VCardProperty(); destEntryPhone.setName(VCardConstants.PROPERTY_TEL); destEntryPhone.addValues(number); destEntry.addProperty(destEntryPhone); bmsg.addRecipient(destEntry); } } } /** * MessageStatus * * Helper class to store associations between remote and local provider based on message handle * and read status */ class MessageStatus { String mHandle; int mRead; MessageStatus(String handle, int read) { mHandle = handle; mRead = read; } @Override public boolean equals(Object other) { return ((other instanceof MessageStatus) && ((MessageStatus) other).mHandle .equals(mHandle)); } } }
android/app/src/com/android/bluetooth/mapclient/MapClientService.java +2 −2 Original line number Diff line number Diff line Loading @@ -456,8 +456,8 @@ public class MapClientService extends ProfileService { } private MapClientService getService() { if (!Utils.checkCaller()) { Log.w(TAG, "MAP call not allowed for non-active user"); if (!Utils.checkCaller() && !MapUtils.isSystemUser()) { Log.w(TAG, "MAP call not allowed for non-active and non-system user."); return null; } Loading
android/app/src/com/android/bluetooth/mapclient/MapUtils.java +5 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.bluetooth.mapclient; import android.os.SystemProperties; import android.os.UserHandle; import com.android.bluetooth.Utils; import com.android.internal.annotations.VisibleForTesting; Loading @@ -32,6 +33,10 @@ class MapUtils { sMnsService = service; } static boolean isSystemUser() { return UserHandle.getCallingUserId() == UserHandle.USER_SYSTEM; } static MnsService newMnsServiceInstance(MapClientService mapClientService) { return (sMnsService == null) ? new MnsService(mapClientService) : sMnsService; } Loading
android/app/src/com/android/bluetooth/mapclient/MceStateMachine.java +35 −13 File changed.Preview size limit exceeded, changes collapsed. Show changes