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.cellbroadcastservice;
18 
19 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
20 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
21 
22 import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_CDMA_MESSAGE_TYPE_FROM_FWK;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.app.Activity;
27 import android.content.BroadcastReceiver;
28 import android.content.ContentValues;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.content.pm.ResolveInfo;
35 import android.content.res.Resources;
36 import android.database.Cursor;
37 import android.location.Location;
38 import android.location.LocationManager;
39 import android.location.LocationRequest;
40 import android.net.Uri;
41 import android.os.Bundle;
42 import android.os.CancellationSignal;
43 import android.os.Handler;
44 import android.os.HandlerExecutor;
45 import android.os.Looper;
46 import android.os.Message;
47 import android.os.Process;
48 import android.os.SystemClock;
49 import android.os.SystemProperties;
50 import android.os.UserHandle;
51 import android.provider.Telephony;
52 import android.provider.Telephony.CellBroadcasts;
53 import android.telephony.CbGeoUtils.Geometry;
54 import android.telephony.CbGeoUtils.LatLng;
55 import android.telephony.CellBroadcastIntents;
56 import android.telephony.SmsCbMessage;
57 import android.telephony.SubscriptionManager;
58 import android.telephony.cdma.CdmaSmsCbProgramData;
59 import android.text.TextUtils;
60 import android.util.LocalLog;
61 import android.util.Log;
62 
63 import com.android.internal.annotations.VisibleForTesting;
64 
65 import java.io.File;
66 import java.io.FileDescriptor;
67 import java.io.PrintWriter;
68 import java.text.DateFormat;
69 import java.util.ArrayList;
70 import java.util.Arrays;
71 import java.util.HashMap;
72 import java.util.List;
73 import java.util.Map;
74 import java.util.Objects;
75 import java.util.concurrent.TimeUnit;
76 import java.util.stream.Collectors;
77 import java.util.stream.Stream;
78 
79 /**
80  * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
81  * completes and our result receiver is called.
82  */
83 public class CellBroadcastHandler extends WakeLockStateMachine {
84     private static final String TAG = "CellBroadcastHandler";
85 
86     private static final boolean VDBG = false;
87 
88     /**
89      * CellBroadcast apex name
90      */
91     private static final String CB_APEX_NAME = "com.android.cellbroadcast";
92 
93     /**
94      * Path where CB apex is mounted (/apex/com.android.cellbroadcast)
95      */
96     private static final String CB_APEX_PATH = new File("/apex", CB_APEX_NAME).getAbsolutePath();
97 
98     private static final String EXTRA_MESSAGE = "message";
99 
100     /**
101      * To disable cell broadcast duplicate detection for debugging purposes
102      * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
103      * --ez enable false</code>
104      *
105      * To enable cell broadcast duplicate detection for debugging purposes
106      * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
107      * --ez enable true</code>
108      */
109     private static final String ACTION_DUPLICATE_DETECTION =
110             "com.android.cellbroadcastservice.action.DUPLICATE_DETECTION";
111 
112     /**
113      * The extra for cell broadcast duplicate detection enable/disable
114      */
115     private static final String EXTRA_ENABLE = "enable";
116 
117     private final LocalLog mLocalLog = new LocalLog(100);
118 
119     private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
120 
121     /** Uses to request the location update. */
122     private final LocationRequester mLocationRequester;
123 
124     /** Timestamp of last airplane mode on */
125     protected long mLastAirplaneModeTime = 0;
126 
127     /** Resource cache */
128     private final Map<Integer, Resources> mResourcesCache = new HashMap<>();
129 
130     /** Whether performing duplicate detection or not. Note this is for debugging purposes only. */
131     private boolean mEnableDuplicateDetection = true;
132 
133     /**
134      * Service category equivalent map. The key is the GSM service category, the value is the CDMA
135      * service category.
136      */
137     private final Map<Integer, Integer> mServiceCategoryCrossRATMap;
138 
139     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
140         @Override
141         public void onReceive(Context context, Intent intent) {
142             switch (intent.getAction()) {
143                 case Intent.ACTION_AIRPLANE_MODE_CHANGED:
144                     boolean airplaneModeOn = intent.getBooleanExtra("state", false);
145                     if (airplaneModeOn) {
146                         mLastAirplaneModeTime = System.currentTimeMillis();
147                         log("Airplane mode on.");
148                     }
149                     break;
150                 case ACTION_DUPLICATE_DETECTION:
151                     mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE,
152                             true);
153                     log("Duplicate detection " + (mEnableDuplicateDetection
154                             ? "enabled" : "disabled"));
155                     break;
156                 default:
157                     log("Unhandled broadcast " + intent.getAction());
158             }
159         }
160     };
161 
CellBroadcastHandler(Context context)162     private CellBroadcastHandler(Context context) {
163         this(CellBroadcastHandler.class.getSimpleName(), context, Looper.myLooper());
164     }
165 
166     @VisibleForTesting
CellBroadcastHandler(String debugTag, Context context, Looper looper)167     public CellBroadcastHandler(String debugTag, Context context, Looper looper) {
168         super(debugTag, context, looper);
169         mLocationRequester = new LocationRequester(
170                 context,
171                 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
172                 getHandler());
173 
174         // Adding GSM / CDMA service category mapping.
175         mServiceCategoryCrossRATMap = Stream.of(new Integer[][] {
176                 // Presidential alert
177                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
178                         CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
179                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE,
180                         CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
181 
182                 // Extreme alert
183                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
184                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
185                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE,
186                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
187                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
188                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
189                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE,
190                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
191 
192                 // Severe alert
193                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
194                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
195                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE,
196                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
197                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
198                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
199                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE,
200                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
201                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED,
202                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
203                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE,
204                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
205                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
206                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
207                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE,
208                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
209                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
210                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
211                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE,
212                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
213                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
214                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
215                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE,
216                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
217 
218                 // Amber alert
219                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
220                         CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
221                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE,
222                         CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
223 
224                 // Monthly test alert
225                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
226                         CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
227                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE,
228                         CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
229         }).collect(Collectors.toMap(data -> data[0], data -> data[1]));
230 
231         IntentFilter intentFilter = new IntentFilter();
232         intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
233         if (IS_DEBUGGABLE) {
234             intentFilter.addAction(ACTION_DUPLICATE_DETECTION);
235         }
236 
237         mContext.registerReceiver(mReceiver, intentFilter);
238     }
239 
cleanup()240     public void cleanup() {
241         if (DBG) log("CellBroadcastHandler cleanup");
242         mContext.unregisterReceiver(mReceiver);
243     }
244 
245     /**
246      * Create a new CellBroadcastHandler.
247      * @param context the context to use for dispatching Intents
248      * @return the new handler
249      */
makeCellBroadcastHandler(Context context)250     public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
251         CellBroadcastHandler handler = new CellBroadcastHandler(context);
252         handler.start();
253         return handler;
254     }
255 
256     /**
257      * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
258      * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
259      *
260      * @param message the message to process
261      * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
262      */
263     @Override
handleSmsMessage(Message message)264     protected boolean handleSmsMessage(Message message) {
265         if (message.obj instanceof SmsCbMessage) {
266             if (!isDuplicate((SmsCbMessage) message.obj)) {
267                 handleBroadcastSms((SmsCbMessage) message.obj);
268                 return true;
269             } else {
270                 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_FILTERED,
271                         CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__TYPE__CDMA,
272                         CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__FILTER__DUPLICATE_MESSAGE);
273             }
274             return false;
275         } else {
276             final String errorMessage =
277                     "handleSmsMessage got object of type: " + message.obj.getClass().getName();
278             loge(errorMessage);
279             CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
280                     CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_CDMA_MESSAGE_TYPE_FROM_FWK,
281                     errorMessage);
282             return false;
283         }
284     }
285 
286     /**
287      * Get the maximum time for waiting location.
288      *
289      * @param message Cell broadcast message
290      * @return The maximum waiting time in second
291      */
getMaxLocationWaitingTime(SmsCbMessage message)292     protected int getMaxLocationWaitingTime(SmsCbMessage message) {
293         int maximumTime = message.getMaximumWaitingDuration();
294         if (maximumTime == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
295             Resources res = getResources(message.getSubscriptionId());
296             maximumTime = res.getInteger(R.integer.max_location_waiting_time);
297         }
298         return maximumTime;
299     }
300 
301     /**
302      * Dispatch a Cell Broadcast message to listeners.
303      * @param message the Cell Broadcast to broadcast
304      */
handleBroadcastSms(SmsCbMessage message)305     protected void handleBroadcastSms(SmsCbMessage message) {
306         int slotIndex = message.getSlotIndex();
307 
308         // TODO: Database inserting can be time consuming, therefore this should be changed to
309         // asynchronous.
310         ContentValues cv = message.getContentValues();
311         Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv);
312 
313         if (message.needGeoFencingCheck()) {
314             int maximumWaitingTime = getMaxLocationWaitingTime(message);
315             if (DBG) {
316                 log("Requesting location for geo-fencing. serialNumber = "
317                         + message.getSerialNumber() + ", maximumWaitingTime = "
318                         + maximumWaitingTime);
319             }
320 
321             requestLocationUpdate(location -> {
322                 if (location == null) {
323                     // Broadcast the message directly if the location is not available.
324                     broadcastMessage(message, uri, slotIndex);
325                 } else {
326                     performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
327                 }
328             }, maximumWaitingTime);
329         } else {
330             if (DBG) {
331                 log("Broadcast the message directly because no geo-fencing required, "
332                         + "serialNumber = " + message.getSerialNumber()
333                         + " needGeoFencing = " + message.needGeoFencingCheck());
334             }
335             broadcastMessage(message, uri, slotIndex);
336         }
337     }
338 
339     /**
340      * Check the location based on geographical scope defined in 3GPP TS 23.041 section 9.4.1.2.1.
341      *
342      * The Geographical Scope (GS) indicates the geographical area over which the Message Code
343      * is unique, and the display mode. The CBS message is not necessarily broadcast by all cells
344      * within the geographical area. When two CBS messages are received with identical Serial
345      * Numbers/Message Identifiers in two different cells, the Geographical Scope may be used to
346      * determine if the CBS messages are indeed identical.
347      *
348      * @param message The current message
349      * @param messageToCheck The older message in the database to be checked
350      * @return {@code true} if within the same area, otherwise {@code false}, which should be
351      * be considered as a new message.
352      */
isSameLocation(SmsCbMessage message, SmsCbMessage messageToCheck)353     private boolean isSameLocation(SmsCbMessage message,
354             SmsCbMessage messageToCheck) {
355         if (message.getGeographicalScope() != messageToCheck.getGeographicalScope()) {
356             return false;
357         }
358 
359         // only cell wide (which means that if a message is displayed it is desirable that the
360         // message is removed from the screen when the UE selects the next cell and if any CBS
361         // message is received in the next cell it is to be regarded as "new").
362         if (message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE
363                 || message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE) {
364             return message.getLocation().isInLocationArea(messageToCheck.getLocation());
365         }
366 
367         // Service Area wide (which means that a CBS message with the same Message Code and Update
368         // Number may or may not be "new" in the next cell according to whether the next cell is
369         // in the same Service Area as the current cell)
370         if (message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE) {
371             if (!message.getLocation().getPlmn().equals(messageToCheck.getLocation().getPlmn())) {
372                 return false;
373             }
374 
375             return message.getLocation().getLac() != -1
376                     && message.getLocation().getLac() == messageToCheck.getLocation().getLac();
377         }
378 
379         // PLMN wide (which means that the Message Code and/or Update Number must change in the
380         // next cell, of the PLMN, for the CBS message to be "new". The CBS message is only relevant
381         // to the PLMN in which it is broadcast, so any change of PLMN (including a change to
382         // another PLMN which is an ePLMN) means the CBS message is "new")
383         if (message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE) {
384             return !TextUtils.isEmpty(message.getLocation().getPlmn())
385                     && message.getLocation().getPlmn().equals(
386                             messageToCheck.getLocation().getPlmn());
387         }
388 
389         return false;
390     }
391 
392     /**
393      * Check if the message is a duplicate
394      *
395      * @param message Cell broadcast message
396      * @return {@code true} if this message is a duplicate
397      */
398     @VisibleForTesting
isDuplicate(SmsCbMessage message)399     public boolean isDuplicate(SmsCbMessage message) {
400         if (!mEnableDuplicateDetection) {
401             log("Duplicate detection was disabled for debugging purposes.");
402             return false;
403         }
404 
405         // Find the cell broadcast message identify by the message identifier and serial number
406         // and is not broadcasted.
407         String where = CellBroadcasts.RECEIVED_TIME + ">?";
408 
409         Resources res = getResources(message.getSubscriptionId());
410 
411         // Only consider cell broadcast messages received within certain period.
412         // By default it's 24 hours.
413         long expirationDuration = res.getInteger(R.integer.message_expiration_time);
414         long dupCheckTime = System.currentTimeMillis() - expirationDuration;
415 
416         // Some carriers require reset duplication detection after airplane mode or reboot.
417         if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) {
418             dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime);
419             dupCheckTime = Long.max(dupCheckTime,
420                     System.currentTimeMillis() - SystemClock.elapsedRealtime());
421         }
422 
423         List<SmsCbMessage> cbMessages = new ArrayList<>();
424 
425         try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI,
426                 CellBroadcastProvider.QUERY_COLUMNS,
427                 where,
428                 new String[] {Long.toString(dupCheckTime)},
429                 null)) {
430             if (cursor != null) {
431                 while (cursor.moveToNext()) {
432                     cbMessages.add(SmsCbMessage.createFromCursor(cursor));
433                 }
434             }
435         }
436 
437         boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body);
438 
439         log("Found " + cbMessages.size() + " messages since "
440                 + DateFormat.getDateTimeInstance().format(dupCheckTime));
441         for (SmsCbMessage messageToCheck : cbMessages) {
442             // If messages are from different slots, then we only compare the message body.
443             if (VDBG) log("Checking the message " + messageToCheck);
444             if (message.getSlotIndex() != messageToCheck.getSlotIndex()) {
445                 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
446                     log("Duplicate message detected from different slot. " + message);
447                     return true;
448                 }
449                 if (VDBG) log("Not from a same slot.");
450             } else {
451                 // Check serial number if message is from the same carrier.
452                 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
453                     if (VDBG) log("Serial number does not match.");
454                     // Not a dup. Check next one.
455                     continue;
456                 }
457 
458                 // ETWS primary / secondary should be treated differently.
459                 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
460                         && message.getEtwsWarningInfo().isPrimary()
461                         != messageToCheck.getEtwsWarningInfo().isPrimary()) {
462                     if (VDBG) log("ETWS primary/secondary does not match.");
463                     // Not a dup. Check next one.
464                     continue;
465                 }
466 
467                 // Check if the message category is different. Some carriers send cell broadcast
468                 // messages on different techs (i.e. GSM / CDMA), so we need to compare service
469                 // category cross techs.
470                 if (message.getServiceCategory() != messageToCheck.getServiceCategory()
471                         && !Objects.equals(mServiceCategoryCrossRATMap.get(
472                                 message.getServiceCategory()), messageToCheck.getServiceCategory())
473                         && !Objects.equals(mServiceCategoryCrossRATMap.get(
474                                 messageToCheck.getServiceCategory()),
475                         message.getServiceCategory())) {
476                     if (VDBG) log("GSM/CDMA category does not match.");
477                     // Not a dup. Check next one.
478                     continue;
479                 }
480 
481                 // Check if the message location is different
482                 if (!isSameLocation(message, messageToCheck)) {
483                     if (VDBG) log("Location does not match.");
484                     // Not a dup. Check next one.
485                     continue;
486                 }
487 
488                 // Compare message body if needed.
489                 if (!compareMessageBody || TextUtils.equals(
490                         message.getMessageBody(), messageToCheck.getMessageBody())) {
491                     log("Duplicate message detected. " + message);
492                     return true;
493                 } else {
494                     if (VDBG) log("Body does not match.");
495                 }
496             }
497         }
498 
499         log("Not a duplicate message. " + message);
500         return false;
501     }
502 
503     /**
504      * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
505      * {@code location} is inside the {@code broadcastArea}.
506      * @param message the message need to geo-fencing check
507      * @param uri the message's uri
508      * @param broadcastArea the broadcast area of the message
509      * @param location current location
510      */
performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea, LatLng location, int slotIndex)511     protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
512             LatLng location, int slotIndex) {
513 
514         if (DBG) {
515             logd("Perform geo-fencing check for message identifier = "
516                     + message.getServiceCategory()
517                     + " serialNumber = " + message.getSerialNumber());
518         }
519 
520         if (uri != null) {
521             ContentValues cv = new ContentValues();
522             cv.put(CellBroadcasts.LOCATION_CHECK_TIME, System.currentTimeMillis());
523             mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
524                     CellBroadcasts._ID + "=?", new String[] {uri.getLastPathSegment()});
525         }
526 
527         for (Geometry geo : broadcastArea) {
528             if (geo.contains(location)) {
529                 broadcastMessage(message, uri, slotIndex);
530                 return;
531             }
532         }
533 
534         if (DBG) {
535             logd("Device location is outside the broadcast area "
536                     + CbGeoUtils.encodeGeometriesToString(broadcastArea));
537         }
538         if (message.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
539             CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_FILTERED,
540                     CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__TYPE__GSM,
541                     CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__FILTER__GEOFENCED_MESSAGE);
542         } else if (message.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP2) {
543             CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_FILTERED,
544                     CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__TYPE__CDMA,
545                     CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__FILTER__GEOFENCED_MESSAGE);
546         }
547 
548         sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
549     }
550 
551     /**
552      * Request a single location update.
553      * @param callback a callback will be called when the location is available.
554      * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
555      * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
556      */
requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec)557     protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
558         mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
559     }
560 
561     /**
562      * Get the subscription ID for a phone ID, or INVALID_SUBSCRIPTION_ID if the phone does not
563      * have an active sub
564      * @param phoneId the phoneId to use
565      * @return the associated sub id
566      */
getSubIdForPhone(Context context, int phoneId)567     protected static int getSubIdForPhone(Context context, int phoneId) {
568         SubscriptionManager subMan =
569                 (SubscriptionManager) context.getSystemService(
570                         Context.TELEPHONY_SUBSCRIPTION_SERVICE);
571         int[] subIds = subMan.getSubscriptionIds(phoneId);
572         if (subIds != null) {
573             return subIds[0];
574         } else {
575             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
576         }
577     }
578 
579     /**
580      * Put the phone ID and sub ID into an intent as extras.
581      */
putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId)582     public static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) {
583         int subId = getSubIdForPhone(context, phoneId);
584         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
585             intent.putExtra("subscription", subId);
586             intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
587         }
588         intent.putExtra("phone", phoneId);
589         intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
590     }
591 
592     /**
593      * Broadcast the {@code message} to the applications.
594      * @param message a message need to broadcast
595      * @param messageUri message's uri
596      */
broadcastMessage(@onNull SmsCbMessage message, @Nullable Uri messageUri, int slotIndex)597     protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
598             int slotIndex) {
599         String msg;
600         Intent intent;
601         if (message.isEmergencyMessage()) {
602             msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
603             log(msg);
604             mLocalLog.log(msg);
605             intent = new Intent(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED);
606             //Emergency alerts need to be delivered with high priority
607             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
608 
609             intent.putExtra(EXTRA_MESSAGE, message);
610             putPhoneIdAndSubIdExtra(mContext, intent, slotIndex);
611 
612             if (IS_DEBUGGABLE) {
613                 // Send additional broadcast intent to the specified package. This is only for sl4a
614                 // automation tests.
615                 String[] testPkgs = mContext.getResources().getStringArray(
616                         R.array.test_cell_broadcast_receiver_packages);
617                 if (testPkgs != null) {
618                     Intent additionalIntent = new Intent(intent);
619                     for (String pkg : testPkgs) {
620                         additionalIntent.setPackage(pkg);
621                         mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
622                                 intent, null, (Bundle) null, null, getHandler(),
623                                 Activity.RESULT_OK, null, null);
624 
625                     }
626                 }
627             }
628 
629             List<String> pkgs = new ArrayList<>();
630             pkgs.add(getDefaultCBRPackageName(mContext, intent));
631             pkgs.addAll(Arrays.asList(mContext.getResources().getStringArray(
632                     R.array.additional_cell_broadcast_receiver_packages)));
633             if (pkgs != null) {
634                 mReceiverCount.addAndGet(pkgs.size());
635                 for (String pkg : pkgs) {
636                     // Explicitly send the intent to all the configured cell broadcast receivers.
637                     intent.setPackage(pkg);
638                     mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
639                             intent, null, (Bundle) null, mOrderedBroadcastReceiver, getHandler(),
640                             Activity.RESULT_OK, null, null);
641                 }
642             }
643         } else {
644             msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
645             log(msg);
646             mLocalLog.log(msg);
647             // Send implicit intent since there are various 3rd party carrier apps listen to
648             // this intent.
649 
650             mReceiverCount.incrementAndGet();
651             CellBroadcastIntents.sendSmsCbReceivedBroadcast(
652                     mContext, UserHandle.ALL, message, mOrderedBroadcastReceiver, getHandler(),
653                     Activity.RESULT_OK, slotIndex);
654         }
655 
656         if (messageUri != null) {
657             ContentValues cv = new ContentValues();
658             cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
659             mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
660                     CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
661         }
662     }
663 
664     /**
665      * Checks if the app's path starts with CB_APEX_PATH
666      */
isAppInCBApex(ApplicationInfo appInfo)667     private static boolean isAppInCBApex(ApplicationInfo appInfo) {
668         return appInfo.sourceDir.startsWith(CB_APEX_PATH);
669     }
670 
671     /**
672      * Find the name of the default CBR package. The criteria is that it belongs to CB apex and
673      * handles the given intent.
674      */
getDefaultCBRPackageName(Context context, Intent intent)675     static String getDefaultCBRPackageName(Context context, Intent intent) {
676         PackageManager packageManager = context.getPackageManager();
677         List<ResolveInfo> cbrPackages = packageManager.queryBroadcastReceivers(intent, 0);
678 
679         // remove apps that don't live in the CellBroadcast apex
680         cbrPackages.removeIf(info ->
681                 !isAppInCBApex(info.activityInfo.applicationInfo));
682 
683         if (cbrPackages.isEmpty()) {
684             Log.e(TAG, "getCBRPackageNames: no package found");
685             return null;
686         }
687 
688         if (cbrPackages.size() > 1) {
689             // multiple apps found, log an error but continue
690             Log.e(TAG, "Found > 1 APK in CB apex that can resolve " + intent.getAction() + ": "
691                     + cbrPackages.stream()
692                     .map(info -> info.activityInfo.applicationInfo.packageName)
693                     .collect(Collectors.joining(", ")));
694         }
695 
696         // Assume the first ResolveInfo is the one we're looking for
697         ResolveInfo info = cbrPackages.get(0);
698         return info.activityInfo.applicationInfo.packageName;
699     }
700 
701     /**
702      * Get the device resource based on SIM
703      *
704      * @param subId Subscription index
705      *
706      * @return The resource
707      */
getResources(int subId)708     public @NonNull Resources getResources(int subId) {
709         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
710                 || !SubscriptionManager.isValidSubscriptionId(subId)) {
711             return mContext.getResources();
712         }
713 
714         if (mResourcesCache.containsKey(subId)) {
715             return mResourcesCache.get(subId);
716         }
717 
718         Resources res = SubscriptionManager.getResourcesForSubId(mContext, subId);
719         mResourcesCache.put(subId, res);
720 
721         return res;
722     }
723 
724     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)725     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
726         pw.println("CellBroadcastHandler:");
727         mLocalLog.dump(fd, pw, args);
728         pw.flush();
729     }
730 
731     /** The callback interface of a location request. */
732     public interface LocationUpdateCallback {
733         /**
734          * Call when the location update is available.
735          * @param location a location in (latitude, longitude) format, or {@code null} if the
736          * location service is not available.
737          */
onLocationUpdate(@ullable LatLng location)738         void onLocationUpdate(@Nullable LatLng location);
739     }
740 
741     private static final class LocationRequester {
742         private static final String TAG = CellBroadcastHandler.class.getSimpleName();
743 
744         /**
745          * Fused location provider, which means GPS plus network based providers (cell, wifi, etc..)
746          */
747         //TODO: Should make LocationManager.FUSED_PROVIDER system API in S.
748         private static final String FUSED_PROVIDER = "fused";
749 
750         private final LocationManager mLocationManager;
751         private final List<LocationUpdateCallback> mCallbacks;
752         private final Context mContext;
753         private final Handler mLocationHandler;
754 
755         private boolean mLocationUpdateInProgress;
756         private final Runnable mTimeoutCallback;
757         private CancellationSignal mCancellationSignal;
758 
LocationRequester(Context context, LocationManager locationManager, Handler handler)759         LocationRequester(Context context, LocationManager locationManager, Handler handler) {
760             mLocationManager = locationManager;
761             mCallbacks = new ArrayList<>();
762             mContext = context;
763             mLocationHandler = handler;
764             mLocationUpdateInProgress = false;
765             mTimeoutCallback = this::onLocationTimeout;
766         }
767 
768         /**
769          * Request a single location update. If the location is not available, a callback with
770          * {@code null} location will be called immediately.
771          *
772          * @param callback a callback to the response when the location is available
773          * @param maximumWaitTimeS the maximum wait time of this request. If location is not
774          * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
775          * called.
776          */
requestLocationUpdate(@onNull LocationUpdateCallback callback, int maximumWaitTimeS)777         void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
778                 int maximumWaitTimeS) {
779             mLocationHandler.post(() -> requestLocationUpdateInternal(callback, maximumWaitTimeS));
780         }
781 
onLocationTimeout()782         private void onLocationTimeout() {
783             Log.e(TAG, "Location request timeout");
784             if (mCancellationSignal != null) {
785                 mCancellationSignal.cancel();
786             }
787             onLocationUpdate(null);
788         }
789 
onLocationUpdate(@ullable Location location)790         private void onLocationUpdate(@Nullable Location location) {
791             mLocationUpdateInProgress = false;
792             mLocationHandler.removeCallbacks(mTimeoutCallback);
793             LatLng latLng = null;
794             if (location != null) {
795                 Log.d(TAG, "Got location update");
796                 latLng = new LatLng(location.getLatitude(), location.getLongitude());
797             } else {
798                 Log.e(TAG, "Location is not available.");
799             }
800 
801             for (LocationUpdateCallback callback : mCallbacks) {
802                 callback.onLocationUpdate(latLng);
803             }
804             mCallbacks.clear();
805         }
806 
requestLocationUpdateInternal(@onNull LocationUpdateCallback callback, int maximumWaitTimeS)807         private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
808                 int maximumWaitTimeS) {
809             if (DBG) Log.d(TAG, "requestLocationUpdate");
810             if (!hasPermission(ACCESS_FINE_LOCATION) && !hasPermission(ACCESS_COARSE_LOCATION)) {
811                 if (DBG) {
812                     Log.e(TAG, "Can't request location update because of no location permission");
813                 }
814                 callback.onLocationUpdate(null);
815                 return;
816             }
817 
818             if (!mLocationUpdateInProgress) {
819                 LocationRequest request = LocationRequest.create()
820                         .setProvider(FUSED_PROVIDER)
821                         .setQuality(LocationRequest.ACCURACY_FINE)
822                         .setInterval(0)
823                         .setFastestInterval(0)
824                         .setSmallestDisplacement(0)
825                         .setNumUpdates(1)
826                         .setExpireIn(TimeUnit.SECONDS.toMillis(maximumWaitTimeS));
827                 if (DBG) {
828                     Log.d(TAG, "Location request=" + request);
829                 }
830                 try {
831                     mCancellationSignal = new CancellationSignal();
832                     mLocationManager.getCurrentLocation(request, mCancellationSignal,
833                             new HandlerExecutor(mLocationHandler), this::onLocationUpdate);
834                     // TODO: Remove the following workaround in S. We need to enforce the timeout
835                     // before location manager adds the support for timeout value which is less
836                     // than 30 seconds. After that we can rely on location manager's timeout
837                     // mechanism.
838                     mLocationHandler.postDelayed(mTimeoutCallback,
839                             TimeUnit.SECONDS.toMillis(maximumWaitTimeS));
840                 } catch (IllegalArgumentException e) {
841                     Log.e(TAG, "Cannot get current location. e=" + e);
842                     callback.onLocationUpdate(null);
843                     return;
844                 }
845                 mLocationUpdateInProgress = true;
846             }
847             mCallbacks.add(callback);
848         }
849 
hasPermission(String permission)850         private boolean hasPermission(String permission) {
851             // TODO: remove the check. This will always return true because cell broadcast service
852             // is running under the UID Process.NETWORK_STACK_UID, which is below 10000. It will be
853             // automatically granted with all runtime permissions.
854             return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
855                     == PackageManager.PERMISSION_GRANTED;
856         }
857     }
858 }
859