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