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.isEmpty(message.getLocation().getPlmn()) 502 && message.getLocation().getPlmn().equals( 503 messageToCheck.getLocation().getPlmn()); 504 } 505 506 return false; 507 } 508 509 /** 510 * Check if the message is a duplicate 511 * 512 * @param message Cell broadcast message 513 * @return {@code true} if this message is a duplicate 514 */ 515 @VisibleForTesting isDuplicate(SmsCbMessage message)516 public boolean isDuplicate(SmsCbMessage message) { 517 if (!mEnableDuplicateDetection) { 518 log("Duplicate detection was disabled for debugging purposes."); 519 return false; 520 } 521 522 // Find the cell broadcast message identify by the message identifier and serial number 523 // and is not broadcasted. 524 String where = CellBroadcasts.RECEIVED_TIME + ">?"; 525 526 Resources res = getResources(message.getSubscriptionId()); 527 528 // Only consider cell broadcast messages received within certain period. 529 // By default it's 24 hours. 530 long expirationDuration = res.getInteger(R.integer.message_expiration_time); 531 long dupCheckTime = System.currentTimeMillis() - expirationDuration; 532 533 // Some carriers require reset duplication detection after airplane mode or reboot. 534 if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) { 535 dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime); 536 dupCheckTime = Long.max(dupCheckTime, 537 System.currentTimeMillis() - SystemClock.elapsedRealtime()); 538 } 539 540 List<SmsCbMessage> cbMessages = new ArrayList<>(); 541 542 try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI, 543 CellBroadcastProvider.QUERY_COLUMNS, 544 where, 545 new String[] {Long.toString(dupCheckTime)}, 546 null)) { 547 if (cursor != null) { 548 while (cursor.moveToNext()) { 549 cbMessages.add(SmsCbMessage.createFromCursor(cursor)); 550 } 551 } 552 } 553 554 boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body); 555 boolean compareServiceCategory = res.getBoolean(R.bool.duplicate_compare_service_category); 556 boolean crossSimDuplicateDetection = res.getBoolean(R.bool.cross_sim_duplicate_detection); 557 558 log("Found " + cbMessages.size() + " messages since " 559 + DateFormat.getDateTimeInstance().format(dupCheckTime)); 560 log("compareMessageBody=" + compareMessageBody + ", compareServiceCategory=" 561 + compareServiceCategory + ", crossSimDuplicateDetection=" 562 + crossSimDuplicateDetection); 563 for (SmsCbMessage messageToCheck : cbMessages) { 564 // If messages are from different slots, then we only compare the message body. 565 if (VDBG) log("Checking the message " + messageToCheck); 566 if (crossSimDuplicateDetection 567 && message.getSlotIndex() != messageToCheck.getSlotIndex()) { 568 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) { 569 log("Duplicate message detected from different slot. " + message); 570 return true; 571 } 572 if (VDBG) log("Not from the same slot."); 573 } else { 574 // Check serial number if message is from the same carrier. 575 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) { 576 if (VDBG) log("Serial number does not match."); 577 // Not a dup. Check next one. 578 continue; 579 } 580 581 // ETWS primary / secondary should be treated differently. 582 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage() 583 && message.getEtwsWarningInfo().isPrimary() 584 != messageToCheck.getEtwsWarningInfo().isPrimary()) { 585 if (VDBG) log("ETWS primary/secondary does not match."); 586 // Not a dup. Check next one. 587 continue; 588 } 589 590 // Check if the message category is different. 591 if (compareServiceCategory 592 && message.getServiceCategory() != messageToCheck.getServiceCategory() 593 && !Objects.equals(mServiceCategoryCrossRATMap.get( 594 message.getServiceCategory()), messageToCheck.getServiceCategory()) 595 && !Objects.equals(mServiceCategoryCrossRATMap.get( 596 messageToCheck.getServiceCategory()), 597 message.getServiceCategory())) { 598 if (VDBG) log("Category does not match."); 599 // Not a dup. Check next one. 600 continue; 601 } 602 603 // Check if the message location is different. Note this is only applicable to 604 // 3GPP format cell broadcast messages. 605 if (message.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP 606 && messageToCheck.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP 607 && !isSameLocation(message, messageToCheck)) { 608 if (VDBG) log("Location does not match."); 609 // Not a dup. Check next one. 610 continue; 611 } 612 613 // Compare message body if needed. 614 if (!compareMessageBody || TextUtils.equals( 615 message.getMessageBody(), messageToCheck.getMessageBody())) { 616 log("Duplicate message detected. " + message); 617 return true; 618 } else { 619 if (VDBG) log("Body does not match."); 620 } 621 } 622 } 623 624 log("Not a duplicate message. " + message); 625 return false; 626 } 627 628 /** 629 * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the 630 * {@code location} is inside the {@code broadcastArea}. 631 * @param message the message need to geo-fencing check 632 * @param uri the message's uri 633 * @param calculator the message calculator 634 * @param location current location 635 * @param slotIndex the index of the slot 636 * @param accuracy the accuracy of the coordinate given in meters 637 */ performGeoFencing(SmsCbMessage message, Uri uri, CbSendMessageCalculator calculator, LatLng location, int slotIndex, float accuracy)638 protected void performGeoFencing(SmsCbMessage message, Uri uri, 639 CbSendMessageCalculator calculator, LatLng location, int slotIndex, float accuracy) { 640 641 logd(calculator.toString() + ", current action=" 642 + CbSendMessageCalculator.getActionString(calculator.getAction())); 643 644 if (calculator.getAction() == SEND_MESSAGE_ACTION_SENT) { 645 if (VDBG) { 646 logd("performGeoFencing:" + getMessageString(message)); 647 } 648 return; 649 } 650 651 if (uri != null) { 652 ContentValues cv = new ContentValues(); 653 cv.put(CellBroadcasts.LOCATION_CHECK_TIME, System.currentTimeMillis()); 654 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv, 655 CellBroadcasts._ID + "=?", new String[] {uri.getLastPathSegment()}); 656 } 657 658 659 calculator.addCoordinate(location, accuracy); 660 661 if (VDBG) { 662 logd("Device location new action = " 663 + CbSendMessageCalculator.getActionString(calculator.getAction()) 664 + ", threshold = " + calculator.getThreshold() 665 + ", geos=" + CbGeoUtils.encodeGeometriesToString(calculator.getFences()) 666 + ". " + getMessageString(message)); 667 } 668 669 if (calculator.getAction() == SEND_MESSAGE_ACTION_SEND) { 670 broadcastGeofenceMessage(message, uri, slotIndex, calculator); 671 return; 672 } 673 } 674 geofenceMessageNotRequired(SmsCbMessage msg)675 protected void geofenceMessageNotRequired(SmsCbMessage msg) { 676 if (msg.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP) { 677 CellBroadcastServiceMetrics.getInstance().logMessageFiltered(FILTER_GEOFENCED, msg); 678 } else if (msg.getMessageFormat() == SmsCbMessage.MESSAGE_FORMAT_3GPP2) { 679 CellBroadcastServiceMetrics.getInstance().logMessageFiltered(FILTER_GEOFENCED, msg); 680 } 681 sendMessage(EVENT_BROADCAST_NOT_REQUIRED); 682 } 683 684 /** 685 * send message that broadcast is not required due to geo-fencing check 686 */ 687 @VisibleForTesting sendMessageBroadcastNotRequired()688 public void sendMessageBroadcastNotRequired() { 689 sendMessage(EVENT_BROADCAST_NOT_REQUIRED); 690 } 691 692 /** 693 * Requests a stream of updates for {@code maximumWaitTimeSec} seconds. 694 * @param callback the callback used to communicate back to the caller 695 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated 696 * within the maximum wait time, {@code callback#onLocationUnavailable()} will be called. 697 */ requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec)698 protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) { 699 mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec); 700 } 701 702 /** 703 * Get the subscription id from the phone id. 704 * 705 * @param phoneId the phone id (i.e. logical SIM slot index) 706 * 707 * @return The associated subscription id. {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} 708 * if the phone does not have an active sub or when {@code phoneId} is not valid. 709 */ getSubIdForPhone(Context context, int phoneId)710 public static int getSubIdForPhone(Context context, int phoneId) { 711 if (SdkLevel.isAtLeastU()) { 712 return SubscriptionManager.getSubscriptionId(phoneId); 713 } else { 714 SubscriptionManager subMan = context.getSystemService(SubscriptionManager.class); 715 int[] subIds = subMan.getSubscriptionIds(phoneId); 716 if (subIds != null) { 717 return subIds[0]; 718 } else { 719 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 720 } 721 } 722 } 723 724 /** 725 * Put the phone ID and sub ID into an intent as extras. 726 */ putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId)727 public static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) { 728 int subId = getSubIdForPhone(context, phoneId); 729 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 730 intent.putExtra("subscription", subId); 731 intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); 732 } 733 intent.putExtra("phone", phoneId); 734 intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId); 735 } 736 737 /** 738 * Call when dealing with messages that are checked against a geofence. 739 * 740 * @param message the message being broadcast 741 * @param messageUri the message uri 742 * @param slotIndex the slot index 743 * @param calculator the messages send message calculator 744 */ broadcastGeofenceMessage(@onNull SmsCbMessage message, @Nullable Uri messageUri, int slotIndex, CbSendMessageCalculator calculator)745 protected void broadcastGeofenceMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri, 746 int slotIndex, CbSendMessageCalculator calculator) { 747 // Check that the message wasn't already SENT 748 if (calculator.getAction() == CbSendMessageCalculator.SEND_MESSAGE_ACTION_SENT) { 749 return; 750 } 751 752 if (VDBG) { 753 logd("broadcastGeofenceMessage: mark as sent. " + getMessageString(message)); 754 } 755 // Mark the message as SENT 756 calculator.markAsSent(); 757 758 // Broadcast the message 759 broadcastMessage(message, messageUri, slotIndex); 760 } 761 762 /** 763 * Broadcast the {@code message} to the applications. 764 * @param message a message need to broadcast 765 * @param messageUri message's uri 766 */ 767 // TODO(b/193460475): Remove when tooling supports SystemApi to public API. 768 @SuppressLint("NewApi") broadcastMessage(@onNull SmsCbMessage message, @Nullable Uri messageUri, int slotIndex)769 protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri, 770 int slotIndex) { 771 String msg; 772 Intent intent; 773 if (VDBG) { 774 logd("broadcastMessage: " + getMessageString(message)); 775 } 776 777 if (message.isEmergencyMessage()) { 778 msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message; 779 log(msg); 780 mLocalLog.log(msg); 781 intent = new Intent(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED); 782 //Emergency alerts need to be delivered with high priority 783 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 784 785 intent.putExtra(EXTRA_MESSAGE, message); 786 putPhoneIdAndSubIdExtra(mContext, intent, slotIndex); 787 788 if (IS_DEBUGGABLE) { 789 // Send additional broadcast intent to the specified package. This is only for sl4a 790 // automation tests. 791 String[] testPkgs = mContext.getResources().getStringArray( 792 R.array.test_cell_broadcast_receiver_packages); 793 if (testPkgs != null) { 794 Intent additionalIntent = new Intent(intent); 795 for (String pkg : testPkgs) { 796 additionalIntent.setPackage(pkg); 797 mLocalLog.log("intent=" + intent + " package=" + pkg); 798 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast( 799 intent, null, (Bundle) null, null, getHandler(), 800 Activity.RESULT_OK, null, null); 801 802 } 803 } 804 } 805 806 List<String> pkgs = new ArrayList<>(); 807 pkgs.add(getDefaultCBRPackageName(mContext, intent)); 808 pkgs.addAll(Arrays.asList(mContext.getResources().getStringArray( 809 R.array.additional_cell_broadcast_receiver_packages))); 810 if (pkgs != null) { 811 mReceiverCount.addAndGet(pkgs.size()); 812 CellBroadcastServiceMetrics.getInstance().getFeatureMetrics(mContext) 813 .onChangedAdditionalCbrPackage(pkgs.size() > 1 ? true : false); 814 for (String pkg : pkgs) { 815 // Explicitly send the intent to all the configured cell broadcast receivers. 816 intent.setPackage(pkg); 817 mLocalLog.log("intent=" + intent + " package=" + pkg); 818 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast( 819 intent, null, (Bundle) null, mOrderedBroadcastReceiver, getHandler(), 820 Activity.RESULT_OK, null, null); 821 } 822 } 823 } else { 824 msg = "Dispatching SMS CB, SmsCbMessage is: " + message; 825 log(msg); 826 mLocalLog.log(msg); 827 // Send implicit intent since there are various 3rd party carrier apps listen to 828 // this intent. 829 830 mReceiverCount.incrementAndGet(); 831 CellBroadcastIntents.sendSmsCbReceivedBroadcast( 832 mContext, UserHandle.ALL, message, mOrderedBroadcastReceiver, getHandler(), 833 Activity.RESULT_OK, slotIndex); 834 } 835 836 if (messageUri != null) { 837 ContentValues cv = new ContentValues(); 838 cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1); 839 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv, 840 CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()}); 841 } 842 843 CellBroadcastServiceMetrics.getInstance().logFeatureChangedAsNeeded(mContext); 844 } 845 846 /** 847 * Checks if the app's path starts with CB_APEX_PATH 848 */ isAppInCBApexOrAlternativeApp(ApplicationInfo appInfo)849 private static boolean isAppInCBApexOrAlternativeApp(ApplicationInfo appInfo) { 850 return appInfo.sourceDir.startsWith(CB_APEX_PATH) || 851 appInfo.sourceDir.contains(CB_APP_PLATFORM_NAME); 852 } 853 854 /** 855 * Find the name of the default CBR package. The criteria is that it belongs to CB apex and 856 * handles the given intent. 857 */ getDefaultCBRPackageName(Context context, Intent intent)858 static String getDefaultCBRPackageName(Context context, Intent intent) { 859 PackageManager packageManager = context.getPackageManager(); 860 List<ResolveInfo> cbrPackages = packageManager.queryBroadcastReceivers(intent, 0); 861 862 // remove apps that don't live in the CellBroadcast apex 863 cbrPackages.removeIf(info -> 864 !isAppInCBApexOrAlternativeApp(info.activityInfo.applicationInfo)); 865 866 if (cbrPackages.isEmpty()) { 867 CellBroadcastServiceMetrics.getInstance().logModuleError( 868 ERRSRC_CBS, ERRTYPE_NOTFOUND_DEFAULTCBRPKGS); 869 Log.e(TAG, "getCBRPackageNames: no package found"); 870 return null; 871 } 872 873 if (cbrPackages.size() > 1) { 874 // multiple apps found, log an error but continue 875 CellBroadcastServiceMetrics.getInstance().logModuleError( 876 ERRSRC_CBS, ERRTYPE_FOUND_MULTIPLECBRPKGS); 877 Log.e(TAG, "Found > 1 APK in CB apex that can resolve " + intent.getAction() + ": " 878 + cbrPackages.stream() 879 .map(info -> info.activityInfo.applicationInfo.packageName) 880 .collect(Collectors.joining(", "))); 881 } 882 883 // Assume the first ResolveInfo is the one we're looking for 884 ResolveInfo info = cbrPackages.get(0); 885 return info.activityInfo.applicationInfo.packageName; 886 } 887 888 /** 889 * Get the device resource based on SIM 890 * 891 * @param subId Subscription index 892 * 893 * @return The resource 894 */ getResources(int subId)895 public @NonNull Resources getResources(int subId) { 896 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID 897 || !SubscriptionManager.isValidSubscriptionId(subId)) { 898 return mContext.getResources(); 899 } 900 901 if (mResourcesCache.containsKey(subId)) { 902 return mResourcesCache.get(subId); 903 } 904 905 return SubscriptionManager.getResourcesForSubId(mContext, subId); 906 } 907 908 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)909 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 910 pw.println("CellBroadcastHandler:"); 911 mLocalLog.dump(fd, pw, args); 912 pw.flush(); 913 try { 914 super.dump(fd, pw, args); 915 } catch (NullPointerException e) { 916 // StateMachine.dump() throws a NPE if there is no current state in the stack. Since 917 // StateMachine is defined in the framework and CBS is updated through mailine, we 918 // catch the NPE here as well as fixing the exception in the framework. 919 pw.println("StateMachine: no state info"); 920 } 921 } 922 923 /** The callback interface of a location request. */ 924 public interface LocationUpdateCallback { 925 /** 926 * Called when the location update is available. 927 * @param location a location in (latitude, longitude) format. 928 * @param accuracy the accuracy of the location given from location manager. Given in 929 * meters. 930 */ onLocationUpdate(@onNull LatLng location, float accuracy)931 void onLocationUpdate(@NonNull LatLng location, float accuracy); 932 933 /** 934 * This is called in the following scenarios: 935 * 1. The max time limit of the LocationRequester was reached, and consequently, 936 * no more location updates will be sent. 937 * 2. The service does not have permission to request a location update. 938 * 3. The LocationRequester was explicitly cancelled. 939 */ onLocationUnavailable()940 void onLocationUnavailable(); 941 942 /** 943 * Returns true if all messages are handled. 944 */ areAllMessagesHandled()945 boolean areAllMessagesHandled(); 946 } 947 948 private static final class LocationRequester { 949 /** 950 * Fused location provider, which means GPS plus network based providers (cell, wifi, etc..) 951 */ 952 //TODO: Should make LocationManager.FUSED_PROVIDER system API in S. 953 private static final String FUSED_PROVIDER = "fused"; 954 955 /** 956 * The interval in which location requests will be sent. 957 * see more: <code>LocationRequest#setInterval</code> 958 */ 959 private static final long LOCATION_REQUEST_INTERVAL_MILLIS = 1000; 960 961 private final LocationManager mLocationManager; 962 private final List<LocationUpdateCallback> mCallbacks; 963 private final HandlerHelper mHandlerHelper; 964 private final Context mContext; 965 private final LocationListener mLocationListener; 966 967 private boolean mLocationUpdateInProgress; 968 private final Runnable mLocationUnavailable; 969 private final String mDebugTag; 970 LocationRequester(Context context, LocationManager locationManager, HandlerHelper handlerHelper, String debugTag)971 LocationRequester(Context context, LocationManager locationManager, 972 HandlerHelper handlerHelper, String debugTag) { 973 mLocationManager = locationManager; 974 mCallbacks = new ArrayList<>(); 975 mContext = context; 976 mHandlerHelper = handlerHelper; 977 mLocationUpdateInProgress = false; 978 mLocationUnavailable = this::onLocationUnavailable; 979 980 // Location request did not cancel itself when using this::onLocationListener 981 mLocationListener = this::onLocationUpdate; 982 mDebugTag = debugTag; 983 } 984 985 /** 986 * Requests a stream of updates for {@code maximumWaitTimeSec} seconds. 987 * @param callback the callback used to communicate back to the caller 988 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not 989 * updated within the maximum wait time, 990 * {@code callback#onLocationUnavailable()} will be called. 991 */ requestLocationUpdate(@onNull LocationUpdateCallback callback, int maximumWaitTimeSec)992 void requestLocationUpdate(@NonNull LocationUpdateCallback callback, 993 int maximumWaitTimeSec) { 994 mHandlerHelper.post(() -> requestLocationUpdateInternal(callback, maximumWaitTimeSec)); 995 } 996 onLocationUpdate(@onNull Location location)997 private void onLocationUpdate(@NonNull Location location) { 998 if (location == null) { 999 /* onLocationUpdate should neverreceive a null location, but, covering all of our 1000 bases here. */ 1001 Log.wtf(mDebugTag, "Location is never supposed to be null"); 1002 return; 1003 } 1004 1005 LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); 1006 float accuracy = location.getAccuracy(); 1007 if (DBG) { 1008 Log.d(mDebugTag, "onLocationUpdate: received location update"); 1009 } 1010 1011 boolean canCancel = true; 1012 for (LocationUpdateCallback callback : mCallbacks) { 1013 callback.onLocationUpdate(latLng, accuracy); 1014 canCancel = canCancel && callback.areAllMessagesHandled(); 1015 } 1016 if (canCancel) { 1017 Log.d(mDebugTag, "call cancel because all messages are handled."); 1018 cancel(); 1019 } 1020 } 1021 onLocationUnavailable()1022 private void onLocationUnavailable() { 1023 Log.d(mDebugTag, "onLocationUnavailable: called"); 1024 locationRequesterCycleComplete(); 1025 } 1026 1027 /* This should only be called if all of the messages are handled. */ cancel()1028 public void cancel() { 1029 if (mLocationUpdateInProgress) { 1030 Log.d(mDebugTag, "cancel: location update in progress"); 1031 mHandlerHelper.removeCallbacks(mLocationUnavailable); 1032 locationRequesterCycleComplete(); 1033 } else { 1034 Log.d(mDebugTag, "cancel: location update NOT in progress"); 1035 } 1036 } 1037 locationRequesterCycleComplete()1038 private void locationRequesterCycleComplete() { 1039 try { 1040 for (LocationUpdateCallback callback : mCallbacks) { 1041 callback.onLocationUnavailable(); 1042 } 1043 } finally { 1044 mLocationManager.removeUpdates(mLocationListener); 1045 // Reset the state of location requester for the next request 1046 mCallbacks.clear(); 1047 mLocationUpdateInProgress = false; 1048 } 1049 } 1050 requestLocationUpdateInternal(@onNull LocationUpdateCallback callback, int maximumWaitTimeS)1051 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback, 1052 int maximumWaitTimeS) { 1053 if (DBG) Log.d(mDebugTag, "requestLocationUpdate"); 1054 if (!hasPermission(ACCESS_FINE_LOCATION) && !hasPermission(ACCESS_COARSE_LOCATION)) { 1055 if (DBG) { 1056 Log.e(mDebugTag, 1057 "Can't request location update because of no location permission"); 1058 } 1059 callback.onLocationUnavailable(); 1060 return; 1061 } 1062 1063 if (!mLocationUpdateInProgress) { 1064 try { 1065 // If the user does not turn on location, immediately report location 1066 // unavailable. 1067 if (!mLocationManager.isLocationEnabled()) { 1068 Log.d(mDebugTag, "Location is turned off."); 1069 callback.onLocationUnavailable(); 1070 return; 1071 } 1072 1073 /* We will continue to send updates until the location timeout is reached. The 1074 location timeout case is handled through onLocationUnavailable. */ 1075 LocationRequest request = LocationRequest.create() 1076 .setProvider(FUSED_PROVIDER) 1077 .setQuality(LocationRequest.ACCURACY_FINE) 1078 .setInterval(LOCATION_REQUEST_INTERVAL_MILLIS); 1079 if (DBG) { 1080 Log.d(mDebugTag, "Location request=" + request); 1081 } 1082 mLocationManager.requestLocationUpdates(request, 1083 new HandlerExecutor(mHandlerHelper.getHandler()), 1084 mLocationListener); 1085 1086 // TODO: Remove the following workaround in S. We need to enforce the timeout 1087 // before location manager adds the support for timeout value which is less 1088 // than 30 seconds. After that we can rely on location manager's timeout 1089 // mechanism. 1090 mHandlerHelper.postDelayed(mLocationUnavailable, 1091 TimeUnit.SECONDS.toMillis(maximumWaitTimeS)); 1092 } catch (IllegalArgumentException e) { 1093 Log.e(mDebugTag, "Cannot get current location. e=" + e); 1094 callback.onLocationUnavailable(); 1095 return; 1096 } 1097 mLocationUpdateInProgress = true; 1098 } 1099 mCallbacks.add(callback); 1100 } 1101 hasPermission(String permission)1102 private boolean hasPermission(String permission) { 1103 // TODO: remove the check. This will always return true because cell broadcast service 1104 // is running under the UID Process.NETWORK_STACK_UID, which is below 10000. It will be 1105 // automatically granted with all runtime permissions. 1106 return mContext.checkPermission(permission, Process.myPid(), Process.myUid()) 1107 == PackageManager.PERMISSION_GRANTED; 1108 } 1109 } 1110 1111 /** 1112 * Provides message identifiers that are helpful when logging messages. 1113 * 1114 * @param message the message to log 1115 * @return a helpful message 1116 */ getMessageString(SmsCbMessage message)1117 protected static String getMessageString(SmsCbMessage message) { 1118 return "msg=(" 1119 + message.getServiceCategory() + "," 1120 + message.getSerialNumber() + ")"; 1121 } 1122 1123 1124 /** 1125 * Wraps the {@code Handler} in order to mock the methods. 1126 */ 1127 @VisibleForTesting 1128 public static class HandlerHelper { 1129 1130 private final Handler mHandler; 1131 HandlerHelper(@onNull final Handler handler)1132 public HandlerHelper(@NonNull final Handler handler) { 1133 mHandler = handler; 1134 } 1135 1136 /** 1137 * Posts {@code r} to {@code handler} with a delay of {@code delayMillis} 1138 * 1139 * @param r the runnable callback 1140 * @param delayMillis the number of milliseconds to delay 1141 */ postDelayed(Runnable r, long delayMillis)1142 public void postDelayed(Runnable r, long delayMillis) { 1143 mHandler.postDelayed(r, delayMillis); 1144 } 1145 1146 /** 1147 * Posts {@code r} to the underlying handler 1148 * 1149 * @param r the runnable callback 1150 */ post(Runnable r)1151 public void post(Runnable r) { 1152 mHandler.post(r); 1153 } 1154 1155 /** 1156 * Gets the underlying handler 1157 * @return the handler 1158 */ getHandler()1159 public Handler getHandler() { 1160 return mHandler; 1161 } 1162 1163 /** 1164 * Remove any pending posts of Runnable r that are in the message queue. 1165 */ removeCallbacks(Runnable r)1166 public void removeCallbacks(Runnable r) { 1167 mHandler.removeCallbacks(r); 1168 } 1169 } 1170 } 1171