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