1 /* 2 * Copyright (C) 2013 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.internal.telephony; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.content.BroadcastReceiver; 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.database.Cursor; 26 import android.database.SQLException; 27 import android.os.PersistableBundle; 28 import android.os.UserManager; 29 import android.telephony.CarrierConfigManager; 30 import android.telephony.SubscriptionManager; 31 32 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler; 33 import com.android.internal.telephony.gsm.GsmInboundSmsHandler; 34 import com.android.internal.telephony.metrics.TelephonyMetrics; 35 import com.android.telephony.Rlog; 36 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.Map; 40 41 /** 42 * Called when the credential-encrypted storage is unlocked, collecting all acknowledged messages 43 * and deleting any partial message segments older than 7 days. Called from a worker thread to 44 * avoid delaying phone app startup. The last step is to broadcast the first pending message from 45 * the main thread, then the remaining pending messages will be broadcast after the previous 46 * ordered broadcast completes. 47 */ 48 public class SmsBroadcastUndelivered { 49 private static final String TAG = "SmsBroadcastUndelivered"; 50 private static final boolean DBG = InboundSmsHandler.DBG; 51 52 /** Delete any partial message segments older than 7 days. */ 53 static final long DEFAULT_PARTIAL_SEGMENT_EXPIRE_AGE = (long) (60 * 60 * 1000) * 24 * 7; 54 55 /** 56 * Query projection for dispatching pending messages at boot time. 57 * Column order must match the {@code *_COLUMN} constants in {@link InboundSmsHandler}. 58 */ 59 private static final String[] PDU_PENDING_MESSAGE_PROJECTION = { 60 "pdu", 61 "sequence", 62 "destination_port", 63 "date", 64 "reference_number", 65 "count", 66 "address", 67 "_id", 68 "message_body", 69 "display_originating_addr", 70 "sub_id" 71 }; 72 73 /** Mapping from DB COLUMN to PDU_PENDING_MESSAGE_PROJECTION index */ 74 static final Map<Integer, Integer> PDU_PENDING_MESSAGE_PROJECTION_INDEX_MAPPING = 75 new HashMap<Integer, Integer>() {{ 76 put(InboundSmsHandler.PDU_COLUMN, 0); 77 put(InboundSmsHandler.SEQUENCE_COLUMN, 1); 78 put(InboundSmsHandler.DESTINATION_PORT_COLUMN, 2); 79 put(InboundSmsHandler.DATE_COLUMN, 3); 80 put(InboundSmsHandler.REFERENCE_NUMBER_COLUMN, 4); 81 put(InboundSmsHandler.COUNT_COLUMN, 5); 82 put(InboundSmsHandler.ADDRESS_COLUMN, 6); 83 put(InboundSmsHandler.ID_COLUMN, 7); 84 put(InboundSmsHandler.MESSAGE_BODY_COLUMN, 8); 85 put(InboundSmsHandler.DISPLAY_ADDRESS_COLUMN, 9); 86 put(InboundSmsHandler.SUBID_COLUMN, 10); 87 }}; 88 89 90 private static SmsBroadcastUndelivered instance; 91 92 /** Content resolver to use to access raw table from SmsProvider. */ 93 private final ContentResolver mResolver; 94 95 /** Handler for 3GPP-format messages (may be null). */ 96 private final GsmInboundSmsHandler mGsmInboundSmsHandler; 97 98 /** Handler for 3GPP2-format messages (may be null). */ 99 private final CdmaInboundSmsHandler mCdmaInboundSmsHandler; 100 101 /** Broadcast receiver that processes the raw table when the user unlocks the phone for the 102 * first time after reboot and the credential-encrypted storage is available. 103 */ 104 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 105 @Override 106 public void onReceive(final Context context, Intent intent) { 107 Rlog.d(TAG, "Received broadcast " + intent.getAction()); 108 if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { 109 new ScanRawTableThread(context).start(); 110 } 111 } 112 }; 113 114 private class ScanRawTableThread extends Thread { 115 private final Context context; 116 ScanRawTableThread(Context context)117 private ScanRawTableThread(Context context) { 118 this.context = context; 119 } 120 121 @Override run()122 public void run() { 123 scanRawTable(context, mCdmaInboundSmsHandler, mGsmInboundSmsHandler, 124 System.currentTimeMillis() - getUndeliveredSmsExpirationTime(context)); 125 InboundSmsHandler.cancelNewMessageNotification(context); 126 } 127 } 128 initialize(Context context, GsmInboundSmsHandler gsmInboundSmsHandler, CdmaInboundSmsHandler cdmaInboundSmsHandler)129 public static void initialize(Context context, GsmInboundSmsHandler gsmInboundSmsHandler, 130 CdmaInboundSmsHandler cdmaInboundSmsHandler) { 131 if (instance == null) { 132 instance = new SmsBroadcastUndelivered( 133 context, gsmInboundSmsHandler, cdmaInboundSmsHandler); 134 } 135 136 // Tell handlers to start processing new messages and transit from the startup state to the 137 // idle state. This method may be called multiple times for multi-sim devices. We must make 138 // sure the state transition happen to all inbound sms handlers. 139 if (gsmInboundSmsHandler != null) { 140 gsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_START_ACCEPTING_SMS); 141 } 142 if (cdmaInboundSmsHandler != null) { 143 cdmaInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_START_ACCEPTING_SMS); 144 } 145 } 146 147 @UnsupportedAppUsage SmsBroadcastUndelivered(Context context, GsmInboundSmsHandler gsmInboundSmsHandler, CdmaInboundSmsHandler cdmaInboundSmsHandler)148 private SmsBroadcastUndelivered(Context context, GsmInboundSmsHandler gsmInboundSmsHandler, 149 CdmaInboundSmsHandler cdmaInboundSmsHandler) { 150 mResolver = context.getContentResolver(); 151 mGsmInboundSmsHandler = gsmInboundSmsHandler; 152 mCdmaInboundSmsHandler = cdmaInboundSmsHandler; 153 154 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 155 156 if (userManager.isUserUnlocked()) { 157 new ScanRawTableThread(context).start(); 158 } else { 159 IntentFilter userFilter = new IntentFilter(); 160 userFilter.addAction(Intent.ACTION_USER_UNLOCKED); 161 context.registerReceiver(mBroadcastReceiver, userFilter); 162 } 163 } 164 165 /** 166 * Scan the raw table for complete SMS messages to broadcast, and old PDUs to delete. 167 */ scanRawTable(Context context, CdmaInboundSmsHandler cdmaInboundSmsHandler, GsmInboundSmsHandler gsmInboundSmsHandler, long oldMessageTimestamp)168 static void scanRawTable(Context context, CdmaInboundSmsHandler cdmaInboundSmsHandler, 169 GsmInboundSmsHandler gsmInboundSmsHandler, long oldMessageTimestamp) { 170 if (DBG) Rlog.d(TAG, "scanning raw table for undelivered messages"); 171 long startTime = System.nanoTime(); 172 ContentResolver contentResolver = context.getContentResolver(); 173 HashMap<SmsReferenceKey, Integer> multiPartReceivedCount = 174 new HashMap<SmsReferenceKey, Integer>(4); 175 HashSet<SmsReferenceKey> oldMultiPartMessages = new HashSet<SmsReferenceKey>(4); 176 Cursor cursor = null; 177 try { 178 // query only non-deleted ones 179 cursor = contentResolver.query(InboundSmsHandler.sRawUri, 180 PDU_PENDING_MESSAGE_PROJECTION, "deleted = 0", null, null); 181 if (cursor == null) { 182 Rlog.e(TAG, "error getting pending message cursor"); 183 return; 184 } 185 186 boolean isCurrentFormat3gpp2 = InboundSmsHandler.isCurrentFormat3gpp2(); 187 while (cursor.moveToNext()) { 188 InboundSmsTracker tracker; 189 try { 190 tracker = TelephonyComponentFactory.getInstance() 191 .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker( 192 context, 193 cursor, 194 isCurrentFormat3gpp2); 195 } catch (IllegalArgumentException e) { 196 Rlog.e(TAG, "error loading SmsTracker: " + e); 197 continue; 198 } 199 200 if (tracker.getMessageCount() == 1) { 201 // deliver single-part message 202 broadcastSms(tracker, cdmaInboundSmsHandler, gsmInboundSmsHandler); 203 } else { 204 SmsReferenceKey reference = new SmsReferenceKey(tracker); 205 Integer receivedCount = multiPartReceivedCount.get(reference); 206 if (receivedCount == null) { 207 multiPartReceivedCount.put(reference, 1); // first segment seen 208 if (tracker.getTimestamp() < oldMessageTimestamp) { 209 // older than oldMessageTimestamp; delete if we don't find all the 210 // segments 211 oldMultiPartMessages.add(reference); 212 } 213 } else { 214 int newCount = receivedCount + 1; 215 if (newCount == tracker.getMessageCount()) { 216 // looks like we've got all the pieces; send a single tracker 217 // to state machine which will find the other pieces to broadcast 218 if (DBG) Rlog.d(TAG, "found complete multi-part message"); 219 broadcastSms(tracker, cdmaInboundSmsHandler, gsmInboundSmsHandler); 220 // don't delete this old message until after we broadcast it 221 oldMultiPartMessages.remove(reference); 222 } else { 223 multiPartReceivedCount.put(reference, newCount); 224 } 225 } 226 } 227 } 228 // Retrieve the phone id, required for metrics 229 int phoneId = getPhoneId(gsmInboundSmsHandler, cdmaInboundSmsHandler); 230 231 // Delete old incomplete message segments 232 for (SmsReferenceKey message : oldMultiPartMessages) { 233 // delete permanently 234 int rows = contentResolver.delete(InboundSmsHandler.sRawUriPermanentDelete, 235 message.getDeleteWhere(), message.getDeleteWhereArgs()); 236 if (rows == 0) { 237 Rlog.e(TAG, "No rows were deleted from raw table!"); 238 } else if (DBG) { 239 Rlog.d(TAG, "Deleted " + rows + " rows from raw table for incomplete " 240 + message.mMessageCount + " part message"); 241 } 242 // Update metrics with dropped SMS 243 if (rows > 0) { 244 TelephonyMetrics metrics = TelephonyMetrics.getInstance(); 245 metrics.writeDroppedIncomingMultipartSms(phoneId, message.mFormat, rows, 246 message.mMessageCount); 247 } 248 } 249 } catch (SQLException e) { 250 Rlog.e(TAG, "error reading pending SMS messages", e); 251 } finally { 252 if (cursor != null) { 253 cursor.close(); 254 } 255 if (DBG) Rlog.d(TAG, "finished scanning raw table in " 256 + ((System.nanoTime() - startTime) / 1000000) + " ms"); 257 } 258 } 259 260 /** 261 * Retrieve the phone id for the GSM or CDMA Inbound SMS handler 262 */ getPhoneId(GsmInboundSmsHandler gsmInboundSmsHandler, CdmaInboundSmsHandler cdmaInboundSmsHandler)263 private static int getPhoneId(GsmInboundSmsHandler gsmInboundSmsHandler, 264 CdmaInboundSmsHandler cdmaInboundSmsHandler) { 265 int phoneId = SubscriptionManager.INVALID_PHONE_INDEX; 266 if (gsmInboundSmsHandler != null) { 267 phoneId = gsmInboundSmsHandler.getPhone().getPhoneId(); 268 } else if (cdmaInboundSmsHandler != null) { 269 phoneId = cdmaInboundSmsHandler.getPhone().getPhoneId(); 270 } 271 return phoneId; 272 } 273 274 /** 275 * Send tracker to appropriate (3GPP or 3GPP2) inbound SMS handler for broadcast. 276 */ broadcastSms(InboundSmsTracker tracker, CdmaInboundSmsHandler cdmaInboundSmsHandler, GsmInboundSmsHandler gsmInboundSmsHandler)277 private static void broadcastSms(InboundSmsTracker tracker, 278 CdmaInboundSmsHandler cdmaInboundSmsHandler, 279 GsmInboundSmsHandler gsmInboundSmsHandler) { 280 InboundSmsHandler handler; 281 if (tracker.is3gpp2()) { 282 handler = cdmaInboundSmsHandler; 283 } else { 284 handler = gsmInboundSmsHandler; 285 } 286 if (handler != null) { 287 handler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS, tracker); 288 } else { 289 Rlog.e(TAG, "null handler for " + tracker.getFormat() + " format, can't deliver."); 290 } 291 } 292 getUndeliveredSmsExpirationTime(Context context)293 private long getUndeliveredSmsExpirationTime(Context context) { 294 int subId = SubscriptionManager.getDefaultSmsSubscriptionId(); 295 CarrierConfigManager configManager = 296 (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE); 297 PersistableBundle bundle = configManager.getConfigForSubId(subId); 298 299 if (bundle != null) { 300 return bundle.getLong(CarrierConfigManager.KEY_UNDELIVERED_SMS_MESSAGE_EXPIRATION_TIME, 301 DEFAULT_PARTIAL_SEGMENT_EXPIRE_AGE); 302 } else { 303 return DEFAULT_PARTIAL_SEGMENT_EXPIRE_AGE; 304 } 305 } 306 307 /** 308 * Used as the HashMap key for matching concatenated message segments. 309 */ 310 private static class SmsReferenceKey { 311 final String mAddress; 312 final int mReferenceNumber; 313 final int mMessageCount; 314 final String mQuery; 315 final String mFormat; 316 SmsReferenceKey(InboundSmsTracker tracker)317 SmsReferenceKey(InboundSmsTracker tracker) { 318 mAddress = tracker.getAddress(); 319 mReferenceNumber = tracker.getReferenceNumber(); 320 mMessageCount = tracker.getMessageCount(); 321 mQuery = tracker.getQueryForSegments(); 322 mFormat = tracker.getFormat(); 323 } 324 getDeleteWhereArgs()325 String[] getDeleteWhereArgs() { 326 return new String[]{mAddress, Integer.toString(mReferenceNumber), 327 Integer.toString(mMessageCount)}; 328 } 329 getDeleteWhere()330 String getDeleteWhere() { 331 return mQuery; 332 } 333 334 @Override hashCode()335 public int hashCode() { 336 return ((mReferenceNumber * 31) + mMessageCount) * 31 + mAddress.hashCode(); 337 } 338 339 @Override equals(Object o)340 public boolean equals(Object o) { 341 if (o instanceof SmsReferenceKey) { 342 SmsReferenceKey other = (SmsReferenceKey) o; 343 return other.mAddress.equals(mAddress) 344 && (other.mReferenceNumber == mReferenceNumber) 345 && (other.mMessageCount == mMessageCount); 346 } 347 return false; 348 } 349 } 350 } 351