private SmsDatum GetSms(global::Android.Net.Uri uri)
        {
            SmsDatum smsDatum     = null;
            ICursor  queryResults = null;

            try
            {
                queryResults = Application.Context.ContentResolver.Query(uri, null, null, null, null);

                if (queryResults != null && queryResults.MoveToNext())
                {
                    string protocol = queryResults.GetString(queryResults.GetColumnIndex("protocol"));
                    var    type     = queryResults.GetInt(queryResults.GetColumnIndex("type"));

                    int sentMessageType;

                    // see the Backwards Compatibility article for more information
#if __ANDROID_19__
                    if (Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat)
                    {
                        sentMessageType = (int)SmsMessageType.Sent;  // API level 19
                    }
                    else
#endif
                    {
                        sentMessageType = 2;
                    }

                    if (type != sentMessageType) //note:protocol is never coming in null for me
                    {
                        return(null);
                    }

                    string         toNumber   = queryResults.GetString(queryResults.GetColumnIndexOrThrow("address"));
                    long           unixTimeMS = queryResults.GetLong(queryResults.GetColumnIndexOrThrow("date"));
                    string         body       = queryResults.GetString(queryResults.GetColumnIndexOrThrow("body"));
                    DateTimeOffset timestamp  = DateTimeOffset.FromUnixTimeMilliseconds(unixTimeMS);

                    if (!string.IsNullOrWhiteSpace(body))
                    {
                        Contact contact   = SensusServiceHelper.GetContactAsync(toNumber).Result;
                        bool    isContact = contact != null;

                        smsDatum = new SmsDatum(timestamp, null, toNumber, body, true, isContact, contact?.Name, contact?.Email);
                    }
                }
            }
            finally
            {
                // always close cursor
                try
                {
                    queryResults.Close();
                }
                catch
                {
                }
            }
            return(smsDatum);
        }
        public override void OnChange(bool selfChange, global::Android.Net.Uri uri)
        {
            try
            {
                // for some reason, we get multiple calls to OnChange for the same outgoing text. ignore repeats.
                if (_mostRecentlyObservedSmsURI != null && uri.ToString() == _mostRecentlyObservedSmsURI)
                {
                    return;
                }

                SmsDatum datum = null;

                // process MMS:  https://stackoverflow.com/questions/3012287/how-to-read-mms-data-in-android
                bool isMMS = uri.ToString().StartsWith("content://sms/raw") || uri.ToString().StartsWith("content://mms-sms");
                if (isMMS)
                {
                    datum = GetMostRecentMMS();
                }
                else
                {
                    datum = GetSms(uri);
                }

                if (!string.IsNullOrWhiteSpace(datum?.Message))
                {
                    _outgoingSmsCallback?.Invoke(datum);

                    if (isMMS)
                    {
                        _mostRecentMmsTimestamp = datum.Timestamp;
                    }
                    else
                    {
                        _mostRecentlyObservedSmsURI = uri.ToString();
                    }
                }
            }
            catch (Exception ex)
            {
                // something is probably wrong with our implementation. each manufacturer does things a bit different.
                SensusServiceHelper.Get().Logger.Log("Exception in " + nameof(OnChange) + ":  " + ex.Message, LoggingLevel.Normal, GetType());
            }
        }
        private SmsDatum GetMostRecentMMS()
        {
            SmsDatum mmsDatum = null;

            ICursor queryResults = null;

            try
            {
                // get the most recent conversation
                queryResults = Application.Context.ContentResolver.Query(global::Android.Net.Uri.Parse("content://mms-sms/conversations/"), null, null, null, "_id");

                if (queryResults.MoveToLast())
                {
                    long unixTimeMS = queryResults.GetLong(queryResults.GetColumnIndexOrThrow("date")) * 1000;
                    int  messageId  = queryResults.GetInt(queryResults.GetColumnIndexOrThrow("_id"));

                    ICursor innerQueryResults = Application.Context.ContentResolver.Query(global::Android.Net.Uri.Parse("content://mms/part"), null, "mid=" + messageId, null, null);

                    try
                    {
                        if (innerQueryResults.MoveToFirst())
                        {
                            while (true)
                            {
                                if (innerQueryResults.GetString(innerQueryResults.GetColumnIndexOrThrow("ct")) == "text/plain")
                                {
                                    string         toNumber  = GetAddressNumber(messageId, 151); // 137 is the from and 151 is the to
                                    DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeMilliseconds(unixTimeMS);

                                    // get the message body
                                    string data = innerQueryResults.GetString(innerQueryResults.GetColumnIndexOrThrow("_data"));
                                    string body;
                                    if (data == null)
                                    {
                                        body = innerQueryResults.GetString(innerQueryResults.GetColumnIndexOrThrow("text"));
                                    }
                                    else
                                    {
                                        int partId = innerQueryResults.GetInt(innerQueryResults.GetColumnIndexOrThrow("_id"));
                                        body = GetMmsText(partId);
                                    }

                                    // only keep if we have a message body
                                    if (!string.IsNullOrWhiteSpace(body) && _mostRecentMmsTimestamp != timestamp)
                                    {
                                        mmsDatum = new SmsDatum(timestamp, null, toNumber, body, true);
                                    }

                                    break;
                                }
                                else
                                {
                                    if (!innerQueryResults.MoveToNext())
                                    {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    finally
                    {
                        // always close cursor
                        try
                        {
                            innerQueryResults.Close();
                        }
                        catch
                        {
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                string message = "Exception while getting MMS:  " + ex.Message;

                SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType());

                // throw a NotSupportedException, as our implementation is probably not correct -- and
                // will never work for -- this device. this message will cause the probe to be disabled
                // if thrown on protocol startup; otherwise, it will simply be ignored. one reason this
                // is important is that sensus will continue to foreground the app in an attempt to get
                // the probe started. but it will always fail, and this will really irritate the user.
                throw new NotSupportedException(message, ex);
            }
            finally
            {
                // always close cursor
                try
                {
                    queryResults.Close();
                }
                catch
                {
                }
            }

            return(mmsDatum);
        }