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 android.Manifest;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.Activity;
23 import android.app.AppOpsManager;
24 import android.content.BroadcastReceiver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.pm.PackageManager;
30 import android.content.res.Resources;
31 import android.database.Cursor;
32 import android.location.Location;
33 import android.location.LocationListener;
34 import android.location.LocationManager;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.os.Looper;
39 import android.os.Message;
40 import android.os.Process;
41 import android.os.SystemClock;
42 import android.os.SystemProperties;
43 import android.os.UserHandle;
44 import android.provider.Telephony;
45 import android.provider.Telephony.CellBroadcasts;
46 import android.telephony.CbGeoUtils.Geometry;
47 import android.telephony.CbGeoUtils.LatLng;
48 import android.telephony.CellBroadcastIntents;
49 import android.telephony.SmsCbMessage;
50 import android.telephony.SubscriptionManager;
51 import android.telephony.cdma.CdmaSmsCbProgramData;
52 import android.text.TextUtils;
53 import android.text.format.DateUtils;
54 import android.util.LocalLog;
55 import android.util.Log;
56 
57 import com.android.internal.annotations.VisibleForTesting;
58 
59 import java.io.FileDescriptor;
60 import java.io.PrintWriter;
61 import java.text.DateFormat;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.HashMap;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Objects;
68 import java.util.stream.Collectors;
69 import java.util.stream.Stream;
70 
71 /**
72  * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
73  * completes and our result receiver is called.
74  */
75 public class CellBroadcastHandler extends WakeLockStateMachine {
76     private static final String EXTRA_MESSAGE = "message";
77 
78     /**
79      * To disable cell broadcast duplicate detection for debugging purposes
80      * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
81      * --ez enable false</code>
82      *
83      * To enable cell broadcast duplicate detection for debugging purposes
84      * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
85      * --ez enable true</code>
86      */
87     private static final String ACTION_DUPLICATE_DETECTION =
88             "com.android.cellbroadcastservice.action.DUPLICATE_DETECTION";
89 
90     /**
91      * The extra for cell broadcast duplicate detection enable/disable
92      */
93     private static final String EXTRA_ENABLE = "enable";
94 
95     private final LocalLog mLocalLog = new LocalLog(100);
96 
97     private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
98 
99     /** Uses to request the location update. */
100     public final LocationRequester mLocationRequester;
101 
102     /** Timestamp of last airplane mode on */
103     protected long mLastAirplaneModeTime = 0;
104 
105     /** Resource cache */
106     private final Map<Integer, Resources> mResourcesCache = new HashMap<>();
107 
108     /** Whether performing duplicate detection or not. Note this is for debugging purposes only. */
109     private boolean mEnableDuplicateDetection = true;
110 
111     /**
112      * Service category equivalent map. The key is the GSM service category, the value is the CDMA
113      * service category.
114      */
115     private final Map<Integer, Integer> mServiceCategoryCrossRATMap;
116 
117     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
118         @Override
119         public void onReceive(Context context, Intent intent) {
120             switch (intent.getAction()) {
121                 case Intent.ACTION_AIRPLANE_MODE_CHANGED:
122                     boolean airplaneModeOn = intent.getBooleanExtra("state", false);
123                     if (airplaneModeOn) {
124                         mLastAirplaneModeTime = System.currentTimeMillis();
125                         log("Airplane mode on.");
126                     }
127                     break;
128                 case ACTION_DUPLICATE_DETECTION:
129                     mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE,
130                             true);
131                     log("Duplicate detection " + (mEnableDuplicateDetection
132                             ? "enabled" : "disabled"));
133                     break;
134                 default:
135                     log("Unhandled broadcast " + intent.getAction());
136             }
137         }
138     };
139 
CellBroadcastHandler(Context context)140     private CellBroadcastHandler(Context context) {
141         this("CellBroadcastHandler", context, Looper.myLooper());
142     }
143 
144     @VisibleForTesting
CellBroadcastHandler(String debugTag, Context context, Looper looper)145     public CellBroadcastHandler(String debugTag, Context context, Looper looper) {
146         super(debugTag, context, looper);
147         mLocationRequester = new LocationRequester(
148                 context,
149                 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
150                 getHandler().getLooper());
151 
152         // Adding GSM / CDMA service category mapping.
153         mServiceCategoryCrossRATMap = Stream.of(new Integer[][] {
154                 // Presidential alert
155                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
156                         CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
157                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE,
158                         CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
159 
160                 // Extreme alert
161                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
162                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
163                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE,
164                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
165                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
166                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
167                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE,
168                         CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
169 
170                 // Severe alert
171                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
172                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
173                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE,
174                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
175                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
176                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
177                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE,
178                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
179                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED,
180                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
181                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE,
182                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
183                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
184                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
185                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE,
186                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
187                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
188                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
189                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE,
190                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
191                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
192                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
193                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE,
194                         CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
195 
196                 // Amber alert
197                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
198                         CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
199                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE,
200                         CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
201 
202                 // Monthly test alert
203                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
204                         CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
205                 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE,
206                         CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
207         }).collect(Collectors.toMap(data -> data[0], data -> data[1]));
208 
209         IntentFilter intentFilter = new IntentFilter();
210         intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
211         if (IS_DEBUGGABLE) {
212             intentFilter.addAction(ACTION_DUPLICATE_DETECTION);
213         }
214 
215         mContext.registerReceiver(mReceiver, intentFilter);
216     }
217 
cleanup()218     public void cleanup() {
219         if (DBG) log("CellBroadcastHandler cleanup");
220         mContext.unregisterReceiver(mReceiver);
221     }
222 
223     /**
224      * Create a new CellBroadcastHandler.
225      * @param context the context to use for dispatching Intents
226      * @return the new handler
227      */
makeCellBroadcastHandler(Context context)228     public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
229         CellBroadcastHandler handler = new CellBroadcastHandler(context);
230         handler.start();
231         return handler;
232     }
233 
234     /**
235      * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
236      * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
237      *
238      * @param message the message to process
239      * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
240      */
241     @Override
handleSmsMessage(Message message)242     protected boolean handleSmsMessage(Message message) {
243         if (message.obj instanceof SmsCbMessage) {
244             if (!isDuplicate((SmsCbMessage) message.obj)) {
245                 handleBroadcastSms((SmsCbMessage) message.obj);
246                 return true;
247             }
248             return false;
249         } else {
250             loge("handleMessage got object of type: " + message.obj.getClass().getName());
251             return false;
252         }
253     }
254 
255     /**
256      * Dispatch a Cell Broadcast message to listeners.
257      * @param message the Cell Broadcast to broadcast
258      */
handleBroadcastSms(SmsCbMessage message)259     protected void handleBroadcastSms(SmsCbMessage message) {
260         int slotIndex = message.getSlotIndex();
261 
262         // TODO: Database inserting can be time consuming, therefore this should be changed to
263         // asynchronous.
264         ContentValues cv = message.getContentValues();
265         Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv);
266 
267         if (message.needGeoFencingCheck()) {
268             if (DBG) {
269                 log("Request location update for geo-fencing. serialNumber = "
270                         + message.getSerialNumber());
271             }
272 
273             requestLocationUpdate(location -> {
274                 if (location == null) {
275                     // Broadcast the message directly if the location is not available.
276                     broadcastMessage(message, uri, slotIndex);
277                 } else {
278                     performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
279                 }
280             }, message.getMaximumWaitingDuration());
281         } else {
282             if (DBG) {
283                 log("Broadcast the message directly because no geo-fencing required, "
284                         + "serialNumber = " + message.getSerialNumber()
285                         + " needGeoFencing = " + message.needGeoFencingCheck());
286             }
287             broadcastMessage(message, uri, slotIndex);
288         }
289     }
290 
291     /**
292      * Check if the message is a duplicate
293      *
294      * @param message Cell broadcast message
295      * @return {@code true} if this message is a duplicate
296      */
297     @VisibleForTesting
isDuplicate(SmsCbMessage message)298     public boolean isDuplicate(SmsCbMessage message) {
299         if (!mEnableDuplicateDetection) {
300             log("Duplicate detection was disabled for debugging purposes.");
301             return false;
302         }
303 
304         // Find the cell broadcast message identify by the message identifier and serial number
305         // and is not broadcasted.
306         String where = CellBroadcasts.RECEIVED_TIME + ">?";
307 
308         int slotIndex = message.getSlotIndex();
309         SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
310                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
311         int[] subIds = subMgr.getSubscriptionIds(slotIndex);
312         Resources res;
313         if (subIds != null) {
314             res = getResources(subIds[0]);
315         } else {
316             res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
317         }
318 
319         // Only consider cell broadcast messages received within certain period.
320         // By default it's 24 hours.
321         long expirationDuration = res.getInteger(R.integer.message_expiration_time);
322         long dupCheckTime = System.currentTimeMillis() - expirationDuration;
323 
324         // Some carriers require reset duplication detection after airplane mode or reboot.
325         if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) {
326             dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime);
327             dupCheckTime = Long.max(dupCheckTime,
328                     System.currentTimeMillis() - SystemClock.elapsedRealtime());
329         }
330 
331         List<SmsCbMessage> cbMessages = new ArrayList<>();
332 
333         try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI,
334                 CellBroadcastProvider.QUERY_COLUMNS,
335                 where,
336                 new String[] {Long.toString(dupCheckTime)},
337                 null)) {
338             if (cursor != null) {
339                 while (cursor.moveToNext()) {
340                     cbMessages.add(SmsCbMessage.createFromCursor(cursor));
341                 }
342             }
343         }
344 
345         boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body);
346         boolean compareCellLocation = res.getBoolean(R.bool.duplicate_compare_cell_location);
347 
348         log("Found " + cbMessages.size() + " messages since "
349                 + DateFormat.getDateTimeInstance().format(dupCheckTime));
350         for (SmsCbMessage messageToCheck : cbMessages) {
351             // If messages are from different slots, then we only compare the message body.
352             if (message.getSlotIndex() != messageToCheck.getSlotIndex()) {
353                 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
354                     log("Duplicate message detected from different slot. " + message);
355                     return true;
356                 }
357             } else {
358                 // Check serial number if message is from the same carrier.
359                 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
360                     // Not a dup. Check next one.
361                     continue;
362                 }
363 
364                 // ETWS primary / secondary should be treated differently.
365                 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
366                         && message.getEtwsWarningInfo().isPrimary()
367                         != messageToCheck.getEtwsWarningInfo().isPrimary()) {
368                     // Not a dup. Check next one.
369                     continue;
370                 }
371 
372                 // Check if the message category is different. Some carriers send cell broadcast
373                 // messages on different techs (i.e. GSM / CDMA), so we need to compare service
374                 // category cross techs.
375                 if (message.getServiceCategory() != messageToCheck.getServiceCategory()
376                         && !Objects.equals(mServiceCategoryCrossRATMap.get(
377                                 message.getServiceCategory()), messageToCheck.getServiceCategory())
378                         && !Objects.equals(mServiceCategoryCrossRATMap.get(
379                                 messageToCheck.getServiceCategory()),
380                         message.getServiceCategory())) {
381                     // Not a dup. Check next one.
382                     continue;
383                 }
384 
385                 // For some carriers, comparing cell location is required.
386                 if (compareCellLocation && (!message.getLocation().equals(
387                         messageToCheck.getLocation()))) {
388                     // Not a dup. Check next one.
389                     continue;
390                 }
391 
392                 // Compare message body if needed.
393                 if (!compareMessageBody || TextUtils.equals(
394                         message.getMessageBody(), messageToCheck.getMessageBody())) {
395                     log("Duplicate message detected. " + message);
396                     return true;
397                 }
398             }
399         }
400 
401         log("Not a duplicate message. " + message);
402         return false;
403     }
404 
405     /**
406      * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
407      * {@code location} is inside the {@code broadcastArea}.
408      * @param message the message need to geo-fencing check
409      * @param uri the message's uri
410      * @param broadcastArea the broadcast area of the message
411      * @param location current location
412      */
performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea, LatLng location, int slotIndex)413     protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
414             LatLng location, int slotIndex) {
415 
416         if (DBG) {
417             logd("Perform geo-fencing check for message identifier = "
418                     + message.getServiceCategory()
419                     + " serialNumber = " + message.getSerialNumber());
420         }
421 
422         if (uri != null) {
423             ContentValues cv = new ContentValues();
424             cv.put(CellBroadcasts.LOCATION_CHECK_TIME, System.currentTimeMillis());
425             mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
426                     CellBroadcasts._ID + "=?", new String[] {uri.getLastPathSegment()});
427         }
428 
429         for (Geometry geo : broadcastArea) {
430             if (geo.contains(location)) {
431                 broadcastMessage(message, uri, slotIndex);
432                 return;
433             }
434         }
435 
436         if (DBG) {
437             logd("Device location is outside the broadcast area "
438                     + CbGeoUtils.encodeGeometriesToString(broadcastArea));
439         }
440 
441         sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
442     }
443 
444     /**
445      * Request a single location update.
446      * @param callback a callback will be called when the location is available.
447      * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
448      * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
449      */
requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec)450     protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
451         mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
452     }
453 
454     /**
455      * Get the subscription ID for a phone ID, or INVALID_SUBSCRIPTION_ID if the phone does not
456      * have an active sub
457      * @param phoneId the phoneId to use
458      * @return the associated sub id
459      */
getSubIdForPhone(Context context, int phoneId)460     protected static int getSubIdForPhone(Context context, int phoneId) {
461         SubscriptionManager subMan =
462                 (SubscriptionManager) context.getSystemService(
463                         Context.TELEPHONY_SUBSCRIPTION_SERVICE);
464         int[] subIds = subMan.getSubscriptionIds(phoneId);
465         if (subIds != null) {
466             return subIds[0];
467         } else {
468             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
469         }
470     }
471 
472     /**
473      * Put the phone ID and sub ID into an intent as extras.
474      */
putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId)475     public static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) {
476         int subId = getSubIdForPhone(context, phoneId);
477         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
478             intent.putExtra("subscription", subId);
479             intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
480         }
481         intent.putExtra("phone", phoneId);
482         intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
483     }
484 
485     /**
486      * Broadcast the {@code message} to the applications.
487      * @param message a message need to broadcast
488      * @param messageUri message's uri
489      */
broadcastMessage(@onNull SmsCbMessage message, @Nullable Uri messageUri, int slotIndex)490     protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
491             int slotIndex) {
492         String receiverPermission;
493         String appOp;
494         String msg;
495         Intent intent;
496         if (message.isEmergencyMessage()) {
497             msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
498             log(msg);
499             mLocalLog.log(msg);
500             intent = new Intent(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED);
501             //Emergency alerts need to be delivered with high priority
502             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
503             receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
504             appOp = AppOpsManager.OPSTR_RECEIVE_EMERGENCY_BROADCAST;
505 
506             intent.putExtra(EXTRA_MESSAGE, message);
507             putPhoneIdAndSubIdExtra(mContext, intent, slotIndex);
508 
509             if (IS_DEBUGGABLE) {
510                 // Send additional broadcast intent to the specified package. This is only for sl4a
511                 // automation tests.
512                 String[] testPkgs = mContext.getResources().getStringArray(
513                         R.array.config_testCellBroadcastReceiverPkgs);
514                 if (testPkgs != null) {
515                     Intent additionalIntent = new Intent(intent);
516                     for (String pkg : testPkgs) {
517                         additionalIntent.setPackage(pkg);
518                         mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
519                                 additionalIntent, receiverPermission, appOp, null,
520                                 getHandler(), Activity.RESULT_OK, null, null);
521                     }
522                 }
523             }
524 
525             String[] pkgs = mContext.getResources().getStringArray(
526                     R.array.config_defaultCellBroadcastReceiverPkgs);
527             if (pkgs != null) {
528                 mReceiverCount.addAndGet(pkgs.length);
529                 for (String pkg : pkgs) {
530                     // Explicitly send the intent to all the configured cell broadcast receivers.
531                     intent.setPackage(pkg);
532                     mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
533                             intent, receiverPermission, appOp, mOrderedBroadcastReceiver,
534                             getHandler(), Activity.RESULT_OK, null, null);
535                 }
536             }
537         } else {
538             msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
539             log(msg);
540             mLocalLog.log(msg);
541             // Send implicit intent since there are various 3rd party carrier apps listen to
542             // this intent.
543 
544             mReceiverCount.incrementAndGet();
545             CellBroadcastIntents.sendSmsCbReceivedBroadcast(
546                     mContext, UserHandle.ALL, message, mOrderedBroadcastReceiver, getHandler(),
547                     Activity.RESULT_OK, slotIndex);
548         }
549 
550         if (messageUri != null) {
551             ContentValues cv = new ContentValues();
552             cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
553             mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
554                     CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
555         }
556     }
557 
558     /**
559      * Get the device resource based on SIM
560      *
561      * @param subId Subscription index
562      *
563      * @return The resource
564      */
getResources(int subId)565     public @NonNull Resources getResources(int subId) {
566         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
567                 || !SubscriptionManager.isValidSubscriptionId(subId)) {
568             return mContext.getResources();
569         }
570 
571         if (mResourcesCache.containsKey(subId)) {
572             return mResourcesCache.get(subId);
573         }
574 
575         Resources res = SubscriptionManager.getResourcesForSubId(mContext, subId);
576         mResourcesCache.put(subId, res);
577 
578         return res;
579     }
580 
581     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)582     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
583         pw.println("CellBroadcastHandler:");
584         mLocalLog.dump(fd, pw, args);
585         pw.flush();
586     }
587 
588     /** The callback interface of a location request. */
589     public interface LocationUpdateCallback {
590         /**
591          * Call when the location update is available.
592          * @param location a location in (latitude, longitude) format, or {@code null} if the
593          * location service is not available.
594          */
onLocationUpdate(@ullable LatLng location)595         void onLocationUpdate(@Nullable LatLng location);
596     }
597 
598     private static final class LocationRequester {
599         private static final String TAG = LocationRequester.class.getSimpleName();
600 
601         /**
602          * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
603          * Most of the location request should be responded within 20 seconds.
604          */
605         private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 20;
606 
607         /**
608          * Trigger this event when the {@link LocationManager} is not responded within the given
609          * time.
610          */
611         private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
612 
613         /** Request a single location update. */
614         private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
615 
616         /**
617          * Request location update from network or gps location provider. Network provider will be
618          * used if available, otherwise use the gps provider.
619          */
620         private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
621                 LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
622 
623         private final LocationManager mLocationManager;
624         private final Looper mLooper;
625         private final List<LocationUpdateCallback> mCallbacks;
626         private final Context mContext;
627         private Handler mLocationHandler;
628 
LocationRequester(Context context, LocationManager locationManager, Looper looper)629         LocationRequester(Context context, LocationManager locationManager, Looper looper) {
630             mLocationManager = locationManager;
631             mLooper = looper;
632             mCallbacks = new ArrayList<>();
633             mContext = context;
634             mLocationHandler = new LocationHandler(looper);
635         }
636 
637         /**
638          * Request a single location update. If the location is not available, a callback with
639          * {@code null} location will be called immediately.
640          *
641          * @param callback a callback to the response when the location is available
642          * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
643          * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
644          * called.
645          */
requestLocationUpdate(@onNull LocationUpdateCallback callback, int maximumWaitTimeSec)646         void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
647                 int maximumWaitTimeSec) {
648             mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
649                     0 /* arg2 */, callback).sendToTarget();
650         }
651 
onLocationUpdate(@ullable LatLng location)652         private void onLocationUpdate(@Nullable LatLng location) {
653             for (LocationUpdateCallback callback : mCallbacks) {
654                 callback.onLocationUpdate(location);
655             }
656             mCallbacks.clear();
657         }
658 
requestLocationUpdateInternal(@onNull LocationUpdateCallback callback, int maximumWaitTimeSec)659         private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
660                 int maximumWaitTimeSec) {
661             if (DBG) Log.d(TAG, "requestLocationUpdate");
662             if (!isLocationServiceAvailable()) {
663                 if (DBG) {
664                     Log.d(TAG, "Can't request location update because of no location permission");
665                 }
666                 callback.onLocationUpdate(null);
667                 return;
668             }
669 
670             if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
671                 if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
672                     maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
673                 }
674                 mLocationHandler.sendMessageDelayed(
675                         mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
676                         maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
677             }
678 
679             mCallbacks.add(callback);
680 
681             for (String provider : LOCATION_PROVIDERS) {
682                 if (mLocationManager.isProviderEnabled(provider)) {
683                     mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper);
684                     break;
685                 }
686             }
687         }
688 
isLocationServiceAvailable()689         private boolean isLocationServiceAvailable() {
690             if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
691                     && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
692             for (String provider : LOCATION_PROVIDERS) {
693                 if (mLocationManager.isProviderEnabled(provider)) return true;
694             }
695             return false;
696         }
697 
hasPermission(String permission)698         private boolean hasPermission(String permission) {
699             return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
700                     == PackageManager.PERMISSION_GRANTED;
701         }
702 
703         private final LocationListener mLocationListener = new LocationListener() {
704             @Override
705             public void onLocationChanged(Location location) {
706                 mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
707                 onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude()));
708             }
709 
710             @Override
711             public void onStatusChanged(String provider, int status, Bundle extras) {}
712 
713             @Override
714             public void onProviderEnabled(String provider) {}
715 
716             @Override
717             public void onProviderDisabled(String provider) {}
718         };
719 
720         private final class LocationHandler extends Handler {
LocationHandler(Looper looper)721             LocationHandler(Looper looper) {
722                 super(looper);
723             }
724 
725             @Override
handleMessage(Message msg)726             public void handleMessage(Message msg) {
727                 switch (msg.what) {
728                     case EVENT_LOCATION_REQUEST_TIMEOUT:
729                         if (DBG) Log.d(TAG, "location request timeout");
730                         onLocationUpdate(null);
731                         break;
732                     case EVENT_REQUEST_LOCATION_UPDATE:
733                         requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
734                         break;
735                     default:
736                         Log.e(TAG, "Unsupported message type " + msg.what);
737                 }
738             }
739         }
740     }
741 }
742