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