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.CbSendMessageCalculator.SEND_MESSAGE_ACTION_AMBIGUOUS; 23 import static com.android.cellbroadcastservice.CbSendMessageCalculator.SEND_MESSAGE_ACTION_DONT_SEND; 24 import static com.android.cellbroadcastservice.CbSendMessageCalculator.SEND_MESSAGE_ACTION_NO_COORDINATES; 25 import static com.android.cellbroadcastservice.CbSendMessageCalculator.SEND_MESSAGE_ACTION_SEND; 26 import static com.android.cellbroadcastservice.CbSendMessageCalculator.SEND_MESSAGE_ACTION_SENT; 27 import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERRSRC_CBS; 28 import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERRTYPE_FOUND_MULTIPLECBRPKGS; 29 import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERRTYPE_NOTFOUND_DEFAULTCBRPKGS; 30 import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERR_UNEXPECTED_CDMA_MSG_FROM_FWK; 31 import static com.android.cellbroadcastservice.CellBroadcastMetrics.FILTER_DUPLICATE; 32 import static com.android.cellbroadcastservice.CellBroadcastMetrics.FILTER_GEOFENCED; 33 34 import android.annotation.NonNull; 35 import android.annotation.Nullable; 36 import android.annotation.SuppressLint; 37 import android.app.Activity; 38 import android.content.BroadcastReceiver; 39 import android.content.ContentValues; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.content.IntentFilter; 43 import android.content.pm.ApplicationInfo; 44 import android.content.pm.PackageManager; 45 import android.content.pm.ResolveInfo; 46 import android.content.res.Resources; 47 import android.database.Cursor; 48 import android.location.Location; 49 import android.location.LocationListener; 50 import android.location.LocationManager; 51 import android.location.LocationRequest; 52 import android.net.Uri; 53 import android.os.Bundle; 54 import android.os.Handler; 55 import android.os.Looper; 56 import android.os.Message; 57 import android.os.Process; 58 import android.os.SystemClock; 59 import android.os.SystemProperties; 60 import android.os.UserHandle; 61 import android.provider.Telephony; 62 import android.provider.Telephony.CellBroadcasts; 63 import android.telephony.CbGeoUtils.LatLng; 64 import android.telephony.CellBroadcastIntents; 65 import android.telephony.SmsCbMessage; 66 import android.telephony.SubscriptionManager; 67 import android.telephony.cdma.CdmaSmsCbProgramData; 68 import android.text.TextUtils; 69 import android.util.LocalLog; 70 import android.util.Log; 71 72 import com.android.internal.annotations.VisibleForTesting; 73 import com.android.modules.utils.HandlerExecutor; 74 import com.android.modules.utils.build.SdkLevel; 75 76 import java.io.File; 77 import java.io.FileDescriptor; 78 import java.io.PrintWriter; 79 import java.text.DateFormat; 80 import java.util.ArrayList; 81 import java.util.Arrays; 82 import java.util.HashMap; 83 import java.util.List; 84 import java.util.Map; 85 import java.util.Objects; 86 import java.util.concurrent.TimeUnit; 87 import java.util.stream.Collectors; 88 import java.util.stream.Stream; 89 90 /** 91 * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast 92 * completes and our result receiver is called. 93 */ 94 public class CellBroadcastHandler extends WakeLockStateMachine { 95 private static final String TAG = "CellBroadcastHandler"; 96 97 /** 98 * CellBroadcast apex name 99 */ 100 private static final String CB_APEX_NAME = "com.android.cellbroadcast"; 101 102 /** 103 * CellBroadcast app platform name 104 */ 105 private static final String CB_APP_PLATFORM_NAME = "CellBroadcastAppPlatform"; 106 107 /** 108 * Path where CB apex is mounted (/apex/com.android.cellbroadcast) 109 */ 110 private static final String CB_APEX_PATH = new File("/apex", CB_APEX_NAME).getAbsolutePath(); 111 112 private static final String EXTRA_MESSAGE = "message"; 113 114 /** 115 * To disable cell broadcast duplicate detection for debugging purposes 116 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION 117 * --ez enable false</code> 118 * 119 * To enable cell broadcast duplicate detection for debugging purposes 120 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION 121 * --ez enable true</code> 122 */ 123 private static final String ACTION_DUPLICATE_DETECTION = 124 "com.android.cellbroadcastservice.action.DUPLICATE_DETECTION"; 125 126 /** 127 * The extra for cell broadcast duplicate detection enable/disable 128 */ 129 private static final String EXTRA_ENABLE = "enable"; 130 131 private final LocalLog mLocalLog = new LocalLog(100); 132 133 private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1; 134 135 /** 136 * Used to register receivers as exported to other apps on the device. The Context flag was 137 * introduced in T, and all previous releases use a default value of 0 to export the receiver. 138 */ 139 protected static final int RECEIVER_EXPORTED = 140 SdkLevel.isAtLeastT() ? Context.RECEIVER_EXPORTED : 0; 141 142 /** Uses to request the location update. */ 143 private final LocationRequester mLocationRequester; 144 145 /** Used to inject new calculators during unit testing */ 146 @NonNull 147 protected final CbSendMessageCalculatorFactory mCbSendMessageCalculatorFactory; 148 149 /** Timestamp of last airplane mode on */ 150 protected long mLastAirplaneModeTime = 0; 151 152 /** Resource cache used for test purpose, to be removed by b/223644462 */ 153 protected final Map<Integer, Resources> mResourcesCache = new HashMap<>(); 154 155 /** Whether performing duplicate detection or not. Note this is for debugging purposes only. */ 156 private boolean mEnableDuplicateDetection = true; 157 158 /** 159 * Service category equivalent map. The key is the GSM service category, the value is the CDMA 160 * service category. 161 */ 162 private final Map<Integer, Integer> mServiceCategoryCrossRATMap; 163 164 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 165 @Override 166 public void onReceive(Context context, Intent intent) { 167 switch (intent.getAction()) { 168 case Intent.ACTION_AIRPLANE_MODE_CHANGED: 169 boolean airplaneModeOn = intent.getBooleanExtra("state", false); 170 if (airplaneModeOn) { 171 mLastAirplaneModeTime = System.currentTimeMillis(); 172 log("Airplane mode on."); 173 } 174 break; 175 case ACTION_DUPLICATE_DETECTION: 176 mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE, 177 true); 178 log("Duplicate detection " + (mEnableDuplicateDetection 179 ? "enabled" : "disabled")); 180 break; 181 default: 182 log("Unhandled broadcast " + intent.getAction()); 183 } 184 } 185 }; 186 CellBroadcastHandler(Context context)187 private CellBroadcastHandler(Context context) { 188 this(CellBroadcastHandler.class.getSimpleName(), context, Looper.myLooper(), 189 new CbSendMessageCalculatorFactory(), null); 190 } 191 192 /** 193 * Allows tests to inject new calculators 194 */ 195 @VisibleForTesting 196 public static class CbSendMessageCalculatorFactory { CbSendMessageCalculatorFactory()197 public CbSendMessageCalculatorFactory() { 198 } 199 200 /** 201 * Creates new calculator 202 * 203 * @param context context 204 * @param fences the geo fences to use in the calculator 205 * @return a new instance of the calculator 206 */ createNew(@onNull final Context context, @NonNull final List<android.telephony.CbGeoUtils.Geometry> fences)207 public CbSendMessageCalculator createNew(@NonNull final Context context, 208 @NonNull final List<android.telephony.CbGeoUtils.Geometry> fences) { 209 return new CbSendMessageCalculator(context, fences); 210 } 211 } 212 213 @VisibleForTesting CellBroadcastHandler(String debugTag, Context context, Looper looper, @NonNull final CbSendMessageCalculatorFactory cbSendMessageCalculatorFactory, @Nullable HandlerHelper handlerHelper)214 public CellBroadcastHandler(String debugTag, Context context, Looper looper, 215 @NonNull final CbSendMessageCalculatorFactory cbSendMessageCalculatorFactory, 216 @Nullable HandlerHelper handlerHelper) { 217 super(debugTag, context, looper); 218 219 if (handlerHelper == null) { 220 // Would have preferred to not have handlerHelper has nullable and pass this through the 221 // default ctor. Had trouble doing this because getHander() can't be called until 222 // the type is fully constructed. 223 handlerHelper = new HandlerHelper(getHandler()); 224 } 225 mCbSendMessageCalculatorFactory = cbSendMessageCalculatorFactory; 226 mLocationRequester = new LocationRequester( 227 context, 228 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE), 229 handlerHelper, getName()); 230 231 // Adding GSM / CDMA service category mapping. 232 mServiceCategoryCrossRATMap = Stream.of(new Integer[][] { 233 // Presidential alert 234 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL, 235 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT}, 236 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE, 237 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT}, 238 239 // Extreme alert 240 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED, 241 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT}, 242 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE, 243 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT}, 244 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY, 245 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT}, 246 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE, 247 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT}, 248 249 // Severe alert 250 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED, 251 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 252 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE, 253 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 254 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY, 255 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 256 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE, 257 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 258 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED, 259 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 260 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE, 261 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 262 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY, 263 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 264 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE, 265 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 266 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED, 267 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 268 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE, 269 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 270 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY, 271 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 272 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE, 273 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT}, 274 275 // Amber alert 276 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY, 277 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY}, 278 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE, 279 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY}, 280 281 // Monthly test alert 282 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST, 283 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE}, 284 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE, 285 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE}, 286 }).collect(Collectors.toMap(data -> data[0], data -> data[1])); 287 288 IntentFilter intentFilter = new IntentFilter(); 289 intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); 290 if (IS_DEBUGGABLE) { 291 intentFilter.addAction(ACTION_DUPLICATE_DETECTION); 292 } 293 294 mContext.registerReceiver(mReceiver, intentFilter, RECEIVER_EXPORTED); 295 } 296 cleanup()297 public void cleanup() { 298 if (DBG) log("CellBroadcastHandler cleanup"); 299 mContext.unregisterReceiver(mReceiver); 300 } 301 302 /** 303 * Create a new CellBroadcastHandler. 304 * @param context the context to use for dispatching Intents 305 * @return the new handler 306 */ makeCellBroadcastHandler(Context context)307 public static CellBroadcastHandler makeCellBroadcastHandler(Context context) { 308 CellBroadcastHandler handler = new CellBroadcastHandler(context); 309 handler.start(); 310 return handler; 311 } 312 313 /** 314 * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}. 315 * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass. 316 * 317 * @param message the message to process 318 * @return true if need to wait for geo-fencing or an ordered broadcast was sent. 319 */ 320 @Override handleSmsMessage(Message message)321 protected boolean handleSmsMessage(Message message) { 322 if (message.obj instanceof SmsCbMessage) { 323 if (!isDuplicate((SmsCbMessage) message.obj)) { 324 handleBroadcastSms((SmsCbMessage) message.obj); 325 return true; 326 } else { 327 CellBroadcastServiceMetrics.getInstance() 328 .logMessageFiltered(FILTER_DUPLICATE, (SmsCbMessage) message.obj); 329 330 } 331 return false; 332 } else { 333 final String errorMessage = 334 "handleSmsMessage got object of type: " + message.obj.getClass().getName(); 335 loge(errorMessage); 336 CellBroadcastServiceMetrics.getInstance().logMessageError( 337 ERR_UNEXPECTED_CDMA_MSG_FROM_FWK, errorMessage); 338 return false; 339 } 340 } 341 342 /** 343 * Get the maximum time for waiting location. 344 * 345 * @param message Cell broadcast message 346 * @return The maximum waiting time in second 347 */ getMaxLocationWaitingTime(SmsCbMessage message)348 protected int getMaxLocationWaitingTime(SmsCbMessage message) { 349 int maximumTime = message.getMaximumWaitingDuration(); 350 if (maximumTime == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) { 351 Resources res = getResources(message.getSubscriptionId()); 352 maximumTime = res.getInteger(R.integer.max_location_waiting_time); 353 } 354 return maximumTime; 355 } 356 357 /** 358 * Dispatch a Cell Broadcast message to listeners. 359 * @param message the Cell Broadcast to broadcast 360 */ handleBroadcastSms(SmsCbMessage message)361 protected void handleBroadcastSms(SmsCbMessage message) { 362 int slotIndex = message.getSlotIndex(); 363 364 // TODO: Database inserting can be time consuming, therefore this should be changed to 365 // asynchronous. 366 ContentValues cv = message.getContentValues(); 367 Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv); 368 369 if (message.needGeoFencingCheck()) { 370 int maximumWaitingTime = getMaxLocationWaitingTime(message); 371 if (DBG) { 372 log("Requesting location for geo-fencing. serialNumber = " 373 + message.getSerialNumber() + ", maximumWaitingTime = " 374 + maximumWaitingTime); 375 } 376 377 CbSendMessageCalculator calculator = 378 mCbSendMessageCalculatorFactory.createNew(mContext, message.getGeometries()); 379 requestLocationUpdate(new LocationUpdateCallback() { 380 @Override 381 public void onLocationUpdate(@NonNull LatLng location, 382 float accuracy) { 383 if (VDBG) { 384 logd("onLocationUpdate: location=" + location 385 + ", acc=" + accuracy + ". " + getMessageString(message)); 386 } 387 performGeoFencing(message, uri, calculator, location, slotIndex, 388 accuracy); 389 } 390 391 @Override 392 public boolean areAllMessagesHandled() { 393 return !isMessageInAmbiguousState(calculator); 394 } 395 396 @Override 397 public void onLocationUnavailable() { 398 CellBroadcastHandler.this.onLocationUnavailable( 399 calculator, message, uri, slotIndex); 400 } 401 }, maximumWaitingTime); 402 } else { 403 if (DBG) { 404 log("Broadcast the message directly because no geo-fencing required, " 405 + " needGeoFencing = " + message.needGeoFencingCheck() + ". " 406 + getMessageString(message)); 407 } 408 broadcastMessage(message, uri, slotIndex); 409 } 410 } 411 412 /** 413 * Returns true if the message calculator is in a non-ambiguous state. 414 * 415 * </b>Note:</b> NO_COORDINATES is considered ambiguous because we received no information 416 * in this case. 417 * @param calculator the message calculator 418 * @return whether or not the message is handled 419 */ isMessageInAmbiguousState(CbSendMessageCalculator calculator)420 protected boolean isMessageInAmbiguousState(CbSendMessageCalculator calculator) { 421 return calculator.getAction() == SEND_MESSAGE_ACTION_AMBIGUOUS 422 || calculator.getAction() == SEND_MESSAGE_ACTION_NO_COORDINATES; 423 } 424 425 /** 426 * When location requester cannot send anymore updates, we look at the calculated action and 427 * determine whether or not we should send it. 428 * 429 * see: {@code CellBroadcastHandler.LocationUpdateCallback#onLocationUnavailable} for more info. 430 * 431 * @param calculator the send message calculator 432 * @param message the cell broadcast message received 433 * @param uri the message's uri 434 * @param slotIndex the slot 435 */ onLocationUnavailable(CbSendMessageCalculator calculator, SmsCbMessage message, Uri uri, int slotIndex)436 protected void onLocationUnavailable(CbSendMessageCalculator calculator, SmsCbMessage message, 437 Uri uri, int slotIndex) { 438 @CbSendMessageCalculator.SendMessageAction int action = calculator.getAction(); 439 if (DBG) { 440 logd("onLocationUnavailable: action=" 441 + CbSendMessageCalculator.getActionString(action) + ". " 442 + getMessageString(message)); 443 } 444 445 if (isMessageInAmbiguousState(calculator)) { 446 /* Case 1. If we reached the end of the location time out and we are still in an 447 ambiguous state or no coordinates state, we send the message. 448 Case 2. If we don't have permissions, then no location was received and the 449 calculator's action is NO_COORDINATES, which means we also send. */ 450 broadcastGeofenceMessage(message, uri, slotIndex, calculator); 451 } else if (action == SEND_MESSAGE_ACTION_DONT_SEND) { 452 geofenceMessageNotRequired(message); 453 } 454 } 455 456 /** 457 * Check the location based on geographical scope defined in 3GPP TS 23.041 section 9.4.1.2.1. 458 * 459 * The Geographical Scope (GS) indicates the geographical area over which the Message Code 460 * is unique, and the display mode. The CBS message is not necessarily broadcast by all cells 461 * within the geographical area. When two CBS messages are received with identical Serial 462 * Numbers/Message Identifiers in two different cells, the Geographical Scope may be used to 463 * determine if the CBS messages are indeed identical. 464 * 465 * @param message The current message 466 * @param messageToCheck The older message in the database to be checked 467 * @return {@code true} if within the same area, otherwise {@code false}, which should be 468 * be considered as a new message. 469 */ isSameLocation(SmsCbMessage message, SmsCbMessage messageToCheck)470 private boolean isSameLocation(SmsCbMessage message, 471 SmsCbMessage messageToCheck) { 472 if (message.getGeographicalScope() != messageToCheck.getGeographicalScope()) { 473 return false; 474 } 475 476 // only cell wide (which means that if a message is displayed it is desirable that the 477 // message is removed from the screen when the UE selects the next cell and if any CBS 478 // message is received in the next cell it is to be regarded as "new"). 479 if (message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE 480 || message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE) { 481 return message.getLocation().isInLocationArea(messageToCheck.getLocation()); 482 } 483 484 // Service Area wide (which means that a CBS message with the same Message Code and Update 485 // Number may or may not be "new" in the next cell according to whether the next cell is 486 // in the same Service Area as the current cell) 487 if (message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE) { 488 if (!message.getLocation().getPlmn().equals(messageToCheck.getLocation().getPlmn())) { 489 return false; 490 } 491 492 return message.getLocation().getLac() != -1 493 && message.getLocation().getLac() == messageToCheck.getLocation().getLac(); 494 } 495 496 // PLMN wide (which means that the Message Code and/or Update Number must change in the 497 // next cell, of the PLMN, for the CBS message to be "new". The CBS message is only relevant 498 // to the PLMN in which it is broadcast, so any change of PLMN (including a change to 499 // another PLMN which is an ePLMN) means the CBS message is "new") 500 if (message.getGeographicalScope() == SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE) { 501 return TextUtils.equals(message.getLocation().getPlmn(), 502 messageToCheck.getLocation().getPlmn()); 503 } 504 505 return false; 506 } 507 508 /** 509 * Check if the message is a duplicate 510 * 511 * @param message Cell broadcast message 512 * @return {@code true} if this message is a duplicate 513 */ 514 @VisibleForTesting isDuplicate(SmsCbMessage message)515 public boolean isDuplicate(SmsCbMessage message) { 516 if (!mEnableDuplicateDetection) { 517 log("Duplicate detection was disabled for debugging purposes."); 518 return false; 519 } 520 521 // Find the cell broadcast message identify by the message identifier and serial number 522 // and is not broadcasted. 523 String where = CellBroadcasts.RECEIVED_TIME + ">?"; 524 525 Resources res = getResources(message.getSubscriptionId()); 526 527 // Only consider cell broadcast messages received within certain period. 528 // By default it's 24 hours. 529 long expirationDuration = res.getInteger(R.integer.message_expiration_time); 530 long dupCheckTime = System.currentTimeMillis() - expirationDuration; 531 532 // Some carriers require reset duplication detection after airplane mode or reboot. 533 if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) { 534 dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime); 535 dupCheckTime = Long.max(dupCheckTime, 536 System.currentTimeMillis() - SystemClock.elapsedRealtime()); 537 } 538 539 List<SmsCbMessage> cbMessages = new ArrayList<>(); 540 541 try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI, 542 CellBroadcastProvider.QUERY_COLUMNS, 543 where, 544 new String[] {Long.toString(dupCheckTime)}, 545 null)) { 546 if (cursor != null) { 547 while (cursor.moveToNext()) { 548 cbMessages.add(SmsCbMessage.createFromCursor(cursor)); 549 } 550 } 551 } 552 553 boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body); 554 boolean compareServiceCategory = res.getBoolean(R.bool.duplicate_compare_service_category); 555 boolean crossSimDuplicateDetection = res.getBoolean(R.bool.cross_sim_duplicate_detection); 556 557 log("Found " + cbMessages.size() + " messages since " 558 + DateFormat.getDateTimeInstance().format(dupCheckTime)); 559 log("compareMessageBody=" + compareMessageBody + ", compareServiceCategory=" 560 + compareServiceCategory + ", crossSimDuplicateDetection=" 561 + crossSimDuplicateDetection); 562 for (SmsCbMessage messageToCheck : cbMessages) { 563 // If messages are from different slots, then we only compare the message body. 564 if (VDBG) log("Checking the message " + messageToCheck); 565 if (crossSimDuplicateDetection 566 && message.getSubscriptionId() != messageToCheck.getSubscriptionId()) { 567 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) { 568 log("Duplicate message detected from different slot. " + message); 569 return true; 570 } 571 if (VDBG) log("Not from the same slot."); 572 } else { 573 // Check serial number if message is from the same carrier. 574 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) { 575 if (VDBG) log("Serial number does not match."); 576 // Not a dup. Check next one. 577 continue; 578 } 579 580 // ETWS primary / secondary should be treated differently. 581 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage() 582 && message.getEtwsWarningInfo().isPrimary() 583 != messageToCheck.getEtwsWarningInfo().isPrimary()) { 584 if (VDBG) log("ETWS primary/secondary does not match."); 585 // Not a dup. Check next one. 586 continue; 587 } 588 589 // Check if the message category is different. 590 if (compareServiceCategory 591 && message.getServiceCategory() != messageToCheck.getServiceCategory() 592 && !Objects.equals(mServiceCategoryCrossRATMap.get( 593 message.getServiceCategory()), messageToCheck.getServiceCategory()) 594 && !Objects.equals(mServiceCategoryCrossRATMap.get( 595 messageToCheck.getServiceCategory()), 596 message.getServiceCategory())) { 597 if (VDBG) log("Category does not match."); 598 // Not a dup. Check next one. 599 continue; 600 } 601 602 // Check if the message location is different. Note this is only applicable to 603 // 3GPP format cell broadcast messages. 604 if (message.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP 605 && messageToCheck.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP 606 && !isSameLocation(message, messageToCheck)) { 607 if (VDBG) log("Location does not match."); 608 // Not a dup. Check next one. 609 continue; 610 } 611 612 // Compare message body if needed. 613 if (!compareMessageBody || TextUtils.equals( 614 message.getMessageBody(), messageToCheck.getMessageBody())) { 615 log("Duplicate message detected. " + message); 616 return true; 617 } else { 618 if (VDBG) log("Body does not match."); 619 } 620 } 621 } 622 623 log("Not a duplicate message. " + message); 624 return false; 625 } 626 627 /** 628 * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the 629 * {@code location} is inside the {@code broadcastArea}. 630 * @param message the message need to geo-fencing check 631 * @param uri the message's uri 632 * @param calculator the message calculator 633 * @param location current location 634 * @param slotIndex the index of the slot 635 * @param accuracy the accuracy of the coordinate given in meters 636 */ performGeoFencing(SmsCbMessage message, Uri uri, CbSendMessageCalculator calculator, LatLng location, int slotIndex, float accuracy)637 protected void performGeoFencing(SmsCbMessage message, Uri uri, 638 CbSendMessageCalculator calculator, LatLng location, int slotIndex, float accuracy) { 639 640 logd(calculator.toString() + ", current action=" 641 + CbSendMessageCalculator.getActionString(calculator.getAction())); 642 643 if (calculator.getAction() == SEND_MESSAGE_ACTION_SENT) { 644 if (VDBG) { 645 logd("performGeoFencing:" + getMessageString(message)); 646 } 647 return; 648 } 649 650 if (uri != null) { 651 ContentValues cv = new ContentValues(); 652 cv.put(CellBroadcasts.LOCATION_CHECK_TIME, System.currentTimeMillis()); 653 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv, 654 CellBroadcasts._ID + "=?", new String[] {uri.getLastPathSegment()}); 655 } 656 657 658 calculator.addCoordinate(location, accuracy); 659 660 if (VDBG) { 661 logd("Device location new action = " 662 + CbSendMessageCalculator.getActionString(calculator.getAction()) 663 + ", threshold = " + calculator.getThreshold() 664 + ", geos=" + CbGeoUtils.encodeGeometriesToString(calculator.getFences()) 665 + ". " + getMessageString(message)); 666 } 667 668 if (calculator.getAction() == SEND_MESSAGE_ACTION_SEND) { 669 broadcastGeofenceMessage(message, uri, slotIndex, calculator); 670 return; 671 } 672 } 673 geofenceMessageNotRequired(SmsCbMessage msg)674 protected void geofenceMessageNotRequired(SmsCbMessage msg) { 675 if (msg.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP) { 676 CellBroadcastServiceMetrics.getInstance().logMessageFiltered(FILTER_GEOFENCED, msg); 677 } else if (msg.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP2) { 678 CellBroadcastServiceMetrics.getInstance().logMessageFiltered(FILTER_GEOFENCED, msg); 679 } 680 sendMessage(EVENT_BROADCAST_NOT_REQUIRED); 681 } 682 683 /** 684 * send message that broadcast is not required due to geo-fencing check 685 */ 686 @VisibleForTesting sendMessageBroadcastNotRequired()687 public void sendMessageBroadcastNotRequired() { 688 sendMessage(EVENT_BROADCAST_NOT_REQUIRED); 689 } 690 691 /** 692 * Requests a stream of updates for {@code maximumWaitTimeSec} seconds. 693 * @param callback the callback used to communicate back to the caller 694 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated 695 * within the maximum wait time, {@code callback#onLocationUnavailable()} will be called. 696 */ requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec)697 protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) { 698 mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec); 699 } 700 701 /** 702 * Get the subscription id from the phone id. 703 * 704 * @param phoneId the phone id (i.e. logical SIM slot index) 705 * 706 * @return The associated subscription id. {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} 707 * if the phone does not have an active sub or when {@code phoneId} is not valid. 708 */ getSubIdForPhone(Context context, int phoneId)709 public static int getSubIdForPhone(Context context, int phoneId) { 710 if (SdkLevel.isAtLeastU()) { 711 return SubscriptionManager.getSubscriptionId(phoneId); 712 } else { 713 SubscriptionManager subMan = context.getSystemService(SubscriptionManager.class); 714 int[] subIds = subMan.getSubscriptionIds(phoneId); 715 if (subIds != null) { 716 return subIds[0]; 717 } else { 718 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 719 } 720 } 721 } 722 723 /** 724 * Put the phone ID and sub ID into an intent as extras. 725 */ putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId)726 public static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) { 727 int subId = getSubIdForPhone(context, phoneId); 728 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 729 intent.putExtra("subscription", subId); 730 intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); 731 } 732 intent.putExtra("phone", phoneId); 733 intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId); 734 } 735 736 /** 737 * Call when dealing with messages that are checked against a geofence. 738 * 739 * @param message the message being broadcast 740 * @param messageUri the message uri 741 * @param slotIndex the slot index 742 * @param calculator the messages send message calculator 743 */ broadcastGeofenceMessage(@onNull SmsCbMessage message, @Nullable Uri messageUri, int slotIndex, CbSendMessageCalculator calculator)744 protected void broadcastGeofenceMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri, 745 int slotIndex, CbSendMessageCalculator calculator) { 746 // Check that the message wasn't already SENT 747 if (calculator.getAction() == CbSendMessageCalculator.SEND_MESSAGE_ACTION_SENT) { 748 return; 749 } 750 751 if (VDBG) { 752 logd("broadcastGeofenceMessage: mark as sent. " + getMessageString(message)); 753 } 754 // Mark the message as SENT 755 calculator.markAsSent(); 756 757 // Broadcast the message 758 broadcastMessage(message, messageUri, slotIndex); 759 } 760 761 /** 762 * Broadcast the {@code message} to the applications. 763 * @param message a message need to broadcast 764 * @param messageUri message's uri 765 */ 766 // TODO(b/193460475): Remove when tooling supports SystemApi to public API. 767 @SuppressLint("NewApi") broadcastMessage(@onNull SmsCbMessage message, @Nullable Uri messageUri, int slotIndex)768 protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri, 769 int slotIndex) { 770 String msg; 771 Intent intent; 772 if (VDBG) { 773 logd("broadcastMessage: " + getMessageString(message)); 774 } 775 776 if (message.isEmergencyMessage()) { 777 msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message; 778 log(msg); 779 mLocalLog.log(msg); 780 intent = new Intent(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED); 781 //Emergency alerts need to be delivered with high priority 782 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 783 784 intent.putExtra(EXTRA_MESSAGE, message); 785 putPhoneIdAndSubIdExtra(mContext, intent, slotIndex); 786 787 if (IS_DEBUGGABLE) { 788 // Send additional broadcast intent to the specified package. This is only for sl4a 789 // automation tests. 790 String[] testPkgs = mContext.getResources().getStringArray( 791 R.array.test_cell_broadcast_receiver_packages); 792 if (testPkgs != null) { 793 Intent additionalIntent = new Intent(intent); 794 for (String pkg : testPkgs) { 795 additionalIntent.setPackage(pkg); 796 mLocalLog.log("intent=" + intent + " package=" + pkg); 797 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast( 798 additionalIntent, null, (Bundle) null, null, getHandler(), 799 Activity.RESULT_OK, null, null); 800 801 } 802 } 803 } 804 805 List<String> pkgs = new ArrayList<>(); 806 pkgs.add(getDefaultCBRPackageName(mContext, intent)); 807 pkgs.addAll(Arrays.asList(mContext.getResources().getStringArray( 808 R.array.additional_cell_broadcast_receiver_packages))); 809 if (pkgs != null) { 810 mReceiverCount.addAndGet(pkgs.size()); 811 CellBroadcastServiceMetrics.getInstance().getFeatureMetrics(mContext) 812 .onChangedAdditionalCbrPackage(pkgs.size() > 1 ? true : false); 813 for (String pkg : pkgs) { 814 // Explicitly send the intent to all the configured cell broadcast receivers. 815 intent.setPackage(pkg); 816 mLocalLog.log("intent=" + intent + " package=" + pkg); 817 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast( 818 intent, null, (Bundle) null, mOrderedBroadcastReceiver, getHandler(), 819 Activity.RESULT_OK, null, null); 820 } 821 } 822 } else { 823 msg = "Dispatching SMS CB, SmsCbMessage is: " + message; 824 log(msg); 825 mLocalLog.log(msg); 826 // Send implicit intent since there are various 3rd party carrier apps listen to 827 // this intent. 828 829 mReceiverCount.incrementAndGet(); 830 CellBroadcastIntents.sendSmsCbReceivedBroadcast( 831 mContext, UserHandle.ALL, message, mOrderedBroadcastReceiver, getHandler(), 832 Activity.RESULT_OK, slotIndex); 833 } 834 835 if (messageUri != null) { 836 ContentValues cv = new ContentValues(); 837 cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1); 838 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv, 839 CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()}); 840 } 841 842 CellBroadcastServiceMetrics.getInstance().logFeatureChangedAsNeeded(mContext); 843 } 844 845 /** 846 * Checks if the app's path starts with CB_APEX_PATH 847 */ isAppInCBApexOrAlternativeApp(ApplicationInfo appInfo)848 private static boolean isAppInCBApexOrAlternativeApp(ApplicationInfo appInfo) { 849 return appInfo.sourceDir.startsWith(CB_APEX_PATH) || 850 appInfo.sourceDir.contains(CB_APP_PLATFORM_NAME); 851 } 852 853 /** 854 * Find the name of the default CBR package. The criteria is that it belongs to CB apex and 855 * handles the given intent. 856 */ getDefaultCBRPackageName(Context context, Intent intent)857 static String getDefaultCBRPackageName(Context context, Intent intent) { 858 PackageManager packageManager = context.getPackageManager(); 859 List<ResolveInfo> cbrPackages = packageManager.queryBroadcastReceivers(intent, 0); 860 861 // remove apps that don't live in the CellBroadcast apex 862 cbrPackages.removeIf(info -> 863 !isAppInCBApexOrAlternativeApp(info.activityInfo.applicationInfo)); 864 865 if (cbrPackages.isEmpty()) { 866 CellBroadcastServiceMetrics.getInstance().logModuleError( 867 ERRSRC_CBS, ERRTYPE_NOTFOUND_DEFAULTCBRPKGS); 868 Log.e(TAG, "getCBRPackageNames: no package found"); 869 return null; 870 } 871 872 if (cbrPackages.size() > 1) { 873 // multiple apps found, log an error but continue 874 CellBroadcastServiceMetrics.getInstance().logModuleError( 875 ERRSRC_CBS, ERRTYPE_FOUND_MULTIPLECBRPKGS); 876 Log.e(TAG, "Found > 1 APK in CB apex that can resolve " + intent.getAction() + ": " 877 + cbrPackages.stream() 878 .map(info -> info.activityInfo.applicationInfo.packageName) 879 .collect(Collectors.joining(", "))); 880 } 881 882 // Assume the first ResolveInfo is the one we're looking for 883 ResolveInfo info = cbrPackages.get(0); 884 return info.activityInfo.applicationInfo.packageName; 885 } 886 887 /** 888 * Get the device resource based on SIM 889 * 890 * @param subId Subscription index 891 * 892 * @return The resource 893 */ getResources(int subId)894 public @NonNull Resources getResources(int subId) { 895 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID 896 || !SubscriptionManager.isValidSubscriptionId(subId)) { 897 return mContext.getResources(); 898 } 899 900 if (mResourcesCache.containsKey(subId)) { 901 return mResourcesCache.get(subId); 902 } 903 904 return SubscriptionManager.getResourcesForSubId(mContext, subId); 905 } 906 907 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)908 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 909 pw.println("CellBroadcastHandler:"); 910 mLocalLog.dump(fd, pw, args); 911 pw.flush(); 912 try { 913 super.dump(fd, pw, args); 914 } catch (NullPointerException e) { 915 // StateMachine.dump() throws a NPE if there is no current state in the stack. Since 916 // StateMachine is defined in the framework and CBS is updated through mailine, we 917 // catch the NPE here as well as fixing the exception in the framework. 918 pw.println("StateMachine: no state info"); 919 } 920 } 921 922 /** The callback interface of a location request. */ 923 public interface LocationUpdateCallback { 924 /** 925 * Called when the location update is available. 926 * @param location a location in (latitude, longitude) format. 927 * @param accuracy the accuracy of the location given from location manager. Given in 928 * meters. 929 */ onLocationUpdate(@onNull LatLng location, float accuracy)930 void onLocationUpdate(@NonNull LatLng location, float accuracy); 931 932 /** 933 * This is called in the following scenarios: 934 * 1. The max time limit of the LocationRequester was reached, and consequently, 935 * no more location updates will be sent. 936 * 2. The service does not have permission to request a location update. 937 * 3. The LocationRequester was explicitly cancelled. 938 */ onLocationUnavailable()939 void onLocationUnavailable(); 940 941 /** 942 * Returns true if all messages are handled. 943 */ areAllMessagesHandled()944 boolean areAllMessagesHandled(); 945 } 946 947 private static final class LocationRequester { 948 /** 949 * Fused location provider, which means GPS plus network based providers (cell, wifi, etc..) 950 */ 951 //TODO: Should make LocationManager.FUSED_PROVIDER system API in S. 952 private static final String FUSED_PROVIDER = "fused"; 953 954 /** 955 * The interval in which location requests will be sent. 956 * see more: <code>LocationRequest#setInterval</code> 957 */ 958 private static final long LOCATION_REQUEST_INTERVAL_MILLIS = 1000; 959 960 private final LocationManager mLocationManager; 961 private final List<LocationUpdateCallback> mCallbacks; 962 private final HandlerHelper mHandlerHelper; 963 private final Context mContext; 964 private final LocationListener mLocationListener; 965 966 private boolean mLocationUpdateInProgress; 967 private final Runnable mLocationUnavailable; 968 private final String mDebugTag; 969 LocationRequester(Context context, LocationManager locationManager, HandlerHelper handlerHelper, String debugTag)970 LocationRequester(Context context, LocationManager locationManager, 971 HandlerHelper handlerHelper, String debugTag) { 972 mLocationManager = locationManager; 973 mCallbacks = new ArrayList<>(); 974 mContext = context; 975 mHandlerHelper = handlerHelper; 976 mLocationUpdateInProgress = false; 977 mLocationUnavailable = this::onLocationUnavailable; 978 979 // Location request did not cancel itself when using this::onLocationListener 980 mLocationListener = this::onLocationUpdate; 981 mDebugTag = debugTag; 982 } 983 984 /** 985 * Requests a stream of updates for {@code maximumWaitTimeSec} seconds. 986 * @param callback the callback used to communicate back to the caller 987 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not 988 * updated within the maximum wait time, 989 * {@code callback#onLocationUnavailable()} will be called. 990 */ requestLocationUpdate(@onNull LocationUpdateCallback callback, int maximumWaitTimeSec)991 void requestLocationUpdate(@NonNull LocationUpdateCallback callback, 992 int maximumWaitTimeSec) { 993 mHandlerHelper.post(() -> requestLocationUpdateInternal(callback, maximumWaitTimeSec)); 994 } 995 onLocationUpdate(@onNull Location location)996 private void onLocationUpdate(@NonNull Location location) { 997 if (location == null) { 998 /* onLocationUpdate should neverreceive a null location, but, covering all of our 999 bases here. */ 1000 Log.wtf(mDebugTag, "Location is never supposed to be null"); 1001 return; 1002 } 1003 1004 LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); 1005 float accuracy = location.getAccuracy(); 1006 if (DBG) { 1007 Log.d(mDebugTag, "onLocationUpdate: received location update"); 1008 } 1009 1010 boolean canCancel = true; 1011 for (LocationUpdateCallback callback : mCallbacks) { 1012 callback.onLocationUpdate(latLng, accuracy); 1013 canCancel = canCancel && callback.areAllMessagesHandled(); 1014 } 1015 if (canCancel) { 1016 Log.d(mDebugTag, "call cancel because all messages are handled."); 1017 cancel(); 1018 } 1019 } 1020 onLocationUnavailable()1021 private void onLocationUnavailable() { 1022 Log.d(mDebugTag, "onLocationUnavailable: called"); 1023 locationRequesterCycleComplete(); 1024 } 1025 1026 /* This should only be called if all of the messages are handled. */ cancel()1027 public void cancel() { 1028 if (mLocationUpdateInProgress) { 1029 Log.d(mDebugTag, "cancel: location update in progress"); 1030 mHandlerHelper.removeCallbacks(mLocationUnavailable); 1031 locationRequesterCycleComplete(); 1032 } else { 1033 Log.d(mDebugTag, "cancel: location update NOT in progress"); 1034 } 1035 } 1036 locationRequesterCycleComplete()1037 private void locationRequesterCycleComplete() { 1038 try { 1039 for (LocationUpdateCallback callback : mCallbacks) { 1040 callback.onLocationUnavailable(); 1041 } 1042 } finally { 1043 mLocationManager.removeUpdates(mLocationListener); 1044 // Reset the state of location requester for the next request 1045 mCallbacks.clear(); 1046 mLocationUpdateInProgress = false; 1047 } 1048 } 1049 requestLocationUpdateInternal(@onNull LocationUpdateCallback callback, int maximumWaitTimeS)1050 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback, 1051 int maximumWaitTimeS) { 1052 if (DBG) Log.d(mDebugTag, "requestLocationUpdate"); 1053 if (!hasPermission(ACCESS_FINE_LOCATION) && !hasPermission(ACCESS_COARSE_LOCATION)) { 1054 if (DBG) { 1055 Log.e(mDebugTag, 1056 "Can't request location update because of no location permission"); 1057 } 1058 callback.onLocationUnavailable(); 1059 return; 1060 } 1061 1062 if (!mLocationUpdateInProgress) { 1063 try { 1064 // If the user does not turn on location, immediately report location 1065 // unavailable. 1066 if (!mLocationManager.isLocationEnabled()) { 1067 Log.d(mDebugTag, "Location is turned off."); 1068 callback.onLocationUnavailable(); 1069 return; 1070 } 1071 1072 /* We will continue to send updates until the location timeout is reached. The 1073 location timeout case is handled through onLocationUnavailable. */ 1074 LocationRequest request = LocationRequest.create() 1075 .setProvider(FUSED_PROVIDER) 1076 .setQuality(LocationRequest.ACCURACY_FINE) 1077 .setInterval(LOCATION_REQUEST_INTERVAL_MILLIS); 1078 if (DBG) { 1079 Log.d(mDebugTag, "Location request=" + request); 1080 } 1081 mLocationManager.requestLocationUpdates(request, 1082 new HandlerExecutor(mHandlerHelper.getHandler()), 1083 mLocationListener); 1084 1085 // TODO: Remove the following workaround in S. We need to enforce the timeout 1086 // before location manager adds the support for timeout value which is less 1087 // than 30 seconds. After that we can rely on location manager's timeout 1088 // mechanism. 1089 mHandlerHelper.postDelayed(mLocationUnavailable, 1090 TimeUnit.SECONDS.toMillis(maximumWaitTimeS)); 1091 } catch (IllegalArgumentException e) { 1092 Log.e(mDebugTag, "Cannot get current location. e=" + e); 1093 callback.onLocationUnavailable(); 1094 return; 1095 } 1096 mLocationUpdateInProgress = true; 1097 } 1098 mCallbacks.add(callback); 1099 } 1100 hasPermission(String permission)1101 private boolean hasPermission(String permission) { 1102 // TODO: remove the check. This will always return true because cell broadcast service 1103 // is running under the UID Process.NETWORK_STACK_UID, which is below 10000. It will be 1104 // automatically granted with all runtime permissions. 1105 return mContext.checkPermission(permission, Process.myPid(), Process.myUid()) 1106 == PackageManager.PERMISSION_GRANTED; 1107 } 1108 } 1109 1110 /** 1111 * Provides message identifiers that are helpful when logging messages. 1112 * 1113 * @param message the message to log 1114 * @return a helpful message 1115 */ getMessageString(SmsCbMessage message)1116 protected static String getMessageString(SmsCbMessage message) { 1117 return "msg=(" 1118 + message.getServiceCategory() + "," 1119 + message.getSerialNumber() + ")"; 1120 } 1121 1122 1123 /** 1124 * Wraps the {@code Handler} in order to mock the methods. 1125 */ 1126 @VisibleForTesting 1127 public static class HandlerHelper { 1128 1129 private final Handler mHandler; 1130 HandlerHelper(@onNull final Handler handler)1131 public HandlerHelper(@NonNull final Handler handler) { 1132 mHandler = handler; 1133 } 1134 1135 /** 1136 * Posts {@code r} to {@code handler} with a delay of {@code delayMillis} 1137 * 1138 * @param r the runnable callback 1139 * @param delayMillis the number of milliseconds to delay 1140 */ postDelayed(Runnable r, long delayMillis)1141 public void postDelayed(Runnable r, long delayMillis) { 1142 mHandler.postDelayed(r, delayMillis); 1143 } 1144 1145 /** 1146 * Posts {@code r} to the underlying handler 1147 * 1148 * @param r the runnable callback 1149 */ post(Runnable r)1150 public void post(Runnable r) { 1151 mHandler.post(r); 1152 } 1153 1154 /** 1155 * Gets the underlying handler 1156 * @return the handler 1157 */ getHandler()1158 public Handler getHandler() { 1159 return mHandler; 1160 } 1161 1162 /** 1163 * Remove any pending posts of Runnable r that are in the message queue. 1164 */ removeCallbacks(Runnable r)1165 public void removeCallbacks(Runnable r) { 1166 mHandler.removeCallbacks(r); 1167 } 1168 } 1169 } 1170