• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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