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(cursor, 192 isCurrentFormat3gpp2); 193 } catch (IllegalArgumentException e) { 194 Rlog.e(TAG, "error loading SmsTracker: " + e); 195 continue; 196 } 197 198 if (tracker.getMessageCount() == 1) { 199 // deliver single-part message 200 broadcastSms(tracker, cdmaInboundSmsHandler, gsmInboundSmsHandler); 201 } else { 202 SmsReferenceKey reference = new SmsReferenceKey(tracker); 203 Integer receivedCount = multiPartReceivedCount.get(reference); 204 if (receivedCount == null) { 205 multiPartReceivedCount.put(reference, 1); // first segment seen 206 if (tracker.getTimestamp() < oldMessageTimestamp) { 207 // older than oldMessageTimestamp; delete if we don't find all the 208 // segments 209 oldMultiPartMessages.add(reference); 210 } 211 } else { 212 int newCount = receivedCount + 1; 213 if (newCount == tracker.getMessageCount()) { 214 // looks like we've got all the pieces; send a single tracker 215 // to state machine which will find the other pieces to broadcast 216 if (DBG) Rlog.d(TAG, "found complete multi-part message"); 217 broadcastSms(tracker, cdmaInboundSmsHandler, gsmInboundSmsHandler); 218 // don't delete this old message until after we broadcast it 219 oldMultiPartMessages.remove(reference); 220 } else { 221 multiPartReceivedCount.put(reference, newCount); 222 } 223 } 224 } 225 } 226 // Retrieve the phone id, required for metrics 227 int phoneId = getPhoneId(gsmInboundSmsHandler, cdmaInboundSmsHandler); 228 229 // Delete old incomplete message segments 230 for (SmsReferenceKey message : oldMultiPartMessages) { 231 // delete permanently 232 int rows = contentResolver.delete(InboundSmsHandler.sRawUriPermanentDelete, 233 message.getDeleteWhere(), message.getDeleteWhereArgs()); 234 if (rows == 0) { 235 Rlog.e(TAG, "No rows were deleted from raw table!"); 236 } else if (DBG) { 237 Rlog.d(TAG, "Deleted " + rows + " rows from raw table for incomplete " 238 + message.mMessageCount + " part message"); 239 } 240 // Update metrics with dropped SMS 241 if (rows > 0) { 242 TelephonyMetrics metrics = TelephonyMetrics.getInstance(); 243 metrics.writeDroppedIncomingMultipartSms(phoneId, message.mFormat, rows, 244 message.mMessageCount); 245 } 246 } 247 } catch (SQLException e) { 248 Rlog.e(TAG, "error reading pending SMS messages", e); 249 } finally { 250 if (cursor != null) { 251 cursor.close(); 252 } 253 if (DBG) Rlog.d(TAG, "finished scanning raw table in " 254 + ((System.nanoTime() - startTime) / 1000000) + " ms"); 255 } 256 } 257 258 /** 259 * Retrieve the phone id for the GSM or CDMA Inbound SMS handler 260 */ getPhoneId(GsmInboundSmsHandler gsmInboundSmsHandler, CdmaInboundSmsHandler cdmaInboundSmsHandler)261 private static int getPhoneId(GsmInboundSmsHandler gsmInboundSmsHandler, 262 CdmaInboundSmsHandler cdmaInboundSmsHandler) { 263 int phoneId = SubscriptionManager.INVALID_PHONE_INDEX; 264 if (gsmInboundSmsHandler != null) { 265 phoneId = gsmInboundSmsHandler.getPhone().getPhoneId(); 266 } else if (cdmaInboundSmsHandler != null) { 267 phoneId = cdmaInboundSmsHandler.getPhone().getPhoneId(); 268 } 269 return phoneId; 270 } 271 272 /** 273 * Send tracker to appropriate (3GPP or 3GPP2) inbound SMS handler for broadcast. 274 */ broadcastSms(InboundSmsTracker tracker, CdmaInboundSmsHandler cdmaInboundSmsHandler, GsmInboundSmsHandler gsmInboundSmsHandler)275 private static void broadcastSms(InboundSmsTracker tracker, 276 CdmaInboundSmsHandler cdmaInboundSmsHandler, 277 GsmInboundSmsHandler gsmInboundSmsHandler) { 278 InboundSmsHandler handler; 279 if (tracker.is3gpp2()) { 280 handler = cdmaInboundSmsHandler; 281 } else { 282 handler = gsmInboundSmsHandler; 283 } 284 if (handler != null) { 285 handler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS, tracker); 286 } else { 287 Rlog.e(TAG, "null handler for " + tracker.getFormat() + " format, can't deliver."); 288 } 289 } 290 getUndeliveredSmsExpirationTime(Context context)291 private long getUndeliveredSmsExpirationTime(Context context) { 292 int subId = SubscriptionManager.getDefaultSmsSubscriptionId(); 293 CarrierConfigManager configManager = 294 (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE); 295 PersistableBundle bundle = configManager.getConfigForSubId(subId); 296 297 if (bundle != null) { 298 return bundle.getLong(CarrierConfigManager.KEY_UNDELIVERED_SMS_MESSAGE_EXPIRATION_TIME, 299 DEFAULT_PARTIAL_SEGMENT_EXPIRE_AGE); 300 } else { 301 return DEFAULT_PARTIAL_SEGMENT_EXPIRE_AGE; 302 } 303 } 304 305 /** 306 * Used as the HashMap key for matching concatenated message segments. 307 */ 308 private static class SmsReferenceKey { 309 final String mAddress; 310 final int mReferenceNumber; 311 final int mMessageCount; 312 final String mQuery; 313 final String mFormat; 314 SmsReferenceKey(InboundSmsTracker tracker)315 SmsReferenceKey(InboundSmsTracker tracker) { 316 mAddress = tracker.getAddress(); 317 mReferenceNumber = tracker.getReferenceNumber(); 318 mMessageCount = tracker.getMessageCount(); 319 mQuery = tracker.getQueryForSegments(); 320 mFormat = tracker.getFormat(); 321 } 322 getDeleteWhereArgs()323 String[] getDeleteWhereArgs() { 324 return new String[]{mAddress, Integer.toString(mReferenceNumber), 325 Integer.toString(mMessageCount)}; 326 } 327 getDeleteWhere()328 String getDeleteWhere() { 329 return mQuery; 330 } 331 332 @Override hashCode()333 public int hashCode() { 334 return ((mReferenceNumber * 31) + mMessageCount) * 31 + mAddress.hashCode(); 335 } 336 337 @Override equals(Object o)338 public boolean equals(Object o) { 339 if (o instanceof SmsReferenceKey) { 340 SmsReferenceKey other = (SmsReferenceKey) o; 341 return other.mAddress.equals(mAddress) 342 && (other.mReferenceNumber == mReferenceNumber) 343 && (other.mMessageCount == mMessageCount); 344 } 345 return false; 346 } 347 } 348 } 349