1 /* 2 * Copyright (C) 2015 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.messaging.sms; 18 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.net.Uri; 22 import android.provider.BaseColumns; 23 import android.text.TextUtils; 24 import android.util.Patterns; 25 26 import com.android.messaging.mmslib.SqliteWrapper; 27 import com.android.messaging.util.LogUtil; 28 29 import java.util.HashSet; 30 import java.util.Set; 31 import java.util.regex.Matcher; 32 import java.util.regex.Pattern; 33 34 /** 35 * Utility functions for the Messaging Service 36 */ 37 public class MmsSmsUtils { MmsSmsUtils()38 private MmsSmsUtils() { 39 // Forbidden being instantiated. 40 } 41 42 // An alias (or commonly called "nickname") is: 43 // Nickname must begin with a letter. 44 // Only letters a-z, numbers 0-9, or . are allowed in Nickname field. isAlias(final String string, final int subId)45 public static boolean isAlias(final String string, final int subId) { 46 if (!MmsConfig.get(subId).isAliasEnabled()) { 47 return false; 48 } 49 50 final int len = string == null ? 0 : string.length(); 51 52 if (len < MmsConfig.get(subId).getAliasMinChars() || 53 len > MmsConfig.get(subId).getAliasMaxChars()) { 54 return false; 55 } 56 57 if (!Character.isLetter(string.charAt(0))) { // Nickname begins with a letter 58 return false; 59 } 60 for (int i = 1; i < len; i++) { 61 final char c = string.charAt(i); 62 if (!(Character.isLetterOrDigit(c) || c == '.')) { 63 return false; 64 } 65 } 66 67 return true; 68 } 69 70 /** 71 * mailbox = name-addr 72 * name-addr = [display-name] angle-addr 73 * angle-addr = [CFWS] "<" addr-spec ">" [CFWS] 74 */ 75 public static final Pattern NAME_ADDR_EMAIL_PATTERN = 76 Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*"); 77 extractAddrSpec(final String address)78 public static String extractAddrSpec(final String address) { 79 final Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address); 80 81 if (match.matches()) { 82 return match.group(2); 83 } 84 return address; 85 } 86 87 /** 88 * Returns true if the address is an email address 89 * 90 * @param address the input address to be tested 91 * @return true if address is an email address 92 */ isEmailAddress(final String address)93 public static boolean isEmailAddress(final String address) { 94 if (TextUtils.isEmpty(address)) { 95 return false; 96 } 97 98 final String s = extractAddrSpec(address); 99 final Matcher match = Patterns.EMAIL_ADDRESS.matcher(s); 100 return match.matches(); 101 } 102 103 /** 104 * Returns true if the number is a Phone number 105 * 106 * @param number the input number to be tested 107 * @return true if number is a Phone number 108 */ isPhoneNumber(final String number)109 public static boolean isPhoneNumber(final String number) { 110 if (TextUtils.isEmpty(number)) { 111 return false; 112 } 113 114 final Matcher match = Patterns.PHONE.matcher(number); 115 return match.matches(); 116 } 117 118 /** 119 * Check if MMS is required when sending to email address 120 * 121 * @param destinationHasEmailAddress destination includes an email address 122 * @return true if MMS is required. 123 */ getRequireMmsForEmailAddress(final boolean destinationHasEmailAddress, final int subId)124 public static boolean getRequireMmsForEmailAddress(final boolean destinationHasEmailAddress, 125 final int subId) { 126 if (!TextUtils.isEmpty(MmsConfig.get(subId).getEmailGateway())) { 127 return false; 128 } else { 129 return destinationHasEmailAddress; 130 } 131 } 132 133 /** 134 * Helper functions for the "threads" table used by MMS and SMS. 135 */ 136 public static final class Threads implements android.provider.Telephony.ThreadsColumns { 137 private static final String[] ID_PROJECTION = { BaseColumns._ID }; 138 private static final Uri THREAD_ID_CONTENT_URI = Uri.parse( 139 "content://mms-sms/threadID"); 140 public static final Uri CONTENT_URI = Uri.withAppendedPath( 141 android.provider.Telephony.MmsSms.CONTENT_URI, "conversations"); 142 143 // No one should construct an instance of this class. Threads()144 private Threads() { 145 } 146 147 /** 148 * This is a single-recipient version of 149 * getOrCreateThreadId. It's convenient for use with SMS 150 * messages. 151 */ getOrCreateThreadId(final Context context, final String recipient)152 public static long getOrCreateThreadId(final Context context, final String recipient) { 153 final Set<String> recipients = new HashSet<String>(); 154 155 recipients.add(recipient); 156 return getOrCreateThreadId(context, recipients); 157 } 158 159 /** 160 * Given the recipients list and subject of an unsaved message, 161 * return its thread ID. If the message starts a new thread, 162 * allocate a new thread ID. Otherwise, use the appropriate 163 * existing thread ID. 164 * 165 * Find the thread ID of the same set of recipients (in 166 * any order, without any additions). If one 167 * is found, return it. Otherwise, return a unique thread ID. 168 */ getOrCreateThreadId( final Context context, final Set<String> recipients)169 public static long getOrCreateThreadId( 170 final Context context, final Set<String> recipients) { 171 final Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon(); 172 173 for (String recipient : recipients) { 174 if (isEmailAddress(recipient)) { 175 recipient = extractAddrSpec(recipient); 176 } 177 178 uriBuilder.appendQueryParameter("recipient", recipient); 179 } 180 181 final Uri uri = uriBuilder.build(); 182 //if (DEBUG) Rlog.v(TAG, "getOrCreateThreadId uri: " + uri); 183 184 final Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(), 185 uri, ID_PROJECTION, null, null, null); 186 if (cursor != null) { 187 try { 188 if (cursor.moveToFirst()) { 189 return cursor.getLong(0); 190 } else { 191 LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, 192 "getOrCreateThreadId returned no rows!"); 193 } 194 } finally { 195 cursor.close(); 196 } 197 } 198 199 LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "getOrCreateThreadId failed with " 200 + LogUtil.sanitizePII(recipients.toString())); 201 throw new IllegalArgumentException("Unable to find or allocate a thread ID."); 202 } 203 } 204 } 205