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 && message.getSlotIndex() != messageToCheck.getSlotIndex()) { 568 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) { 569 log("Duplicate message detected from different slot and subId " + 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 additionalIntent, 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 */ 858 @VisibleForTesting getDefaultCBRPackageName(Context context, Intent intent)859 public static String getDefaultCBRPackageName(Context context, Intent intent) { 860 PackageManager packageManager = context.getPackageManager(); 861 List<ResolveInfo> cbrPackages = packageManager.queryBroadcastReceivers(intent, 862 PackageManager.MATCH_SYSTEM_ONLY); 863 864 // remove apps that don't live in the CellBroadcast apex 865 cbrPackages.removeIf(info -> 866 !isAppInCBApexOrAlternativeApp(info.activityInfo.applicationInfo)); 867 868 if (cbrPackages.isEmpty()) { 869 CellBroadcastServiceMetrics.getInstance().logModuleError( 870 ERRSRC_CBS, ERRTYPE_NOTFOUND_DEFAULTCBRPKGS); 871 Log.e(TAG, "getCBRPackageNames: no package found"); 872 return null; 873 } 874 875 if (cbrPackages.size() > 1) { 876 // multiple apps found, log an error but continue 877 CellBroadcastServiceMetrics.getInstance().logModuleError( 878 ERRSRC_CBS, ERRTYPE_FOUND_MULTIPLECBRPKGS); 879 Log.e(TAG, "Found > 1 APK in CB apex that can resolve " + intent.getAction() + ": " 880 + cbrPackages.stream() 881 .map(info -> info.activityInfo.applicationInfo.packageName) 882 .collect(Collectors.joining(", "))); 883 } 884 885 // Assume the first ResolveInfo is the one we're looking for 886 ResolveInfo info = cbrPackages.get(0); 887 return info.activityInfo.applicationInfo.packageName; 888 } 889 890 /** 891 * Get the device resource based on SIM 892 * 893 * @param subId Subscription index 894 * 895 * @return The resource 896 */ getResources(int subId)897 public @NonNull Resources getResources(int subId) { 898 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID 899 || !SubscriptionManager.isValidSubscriptionId(subId)) { 900 return mContext.getResources(); 901 } 902 903 if (mResourcesCache.containsKey(subId)) { 904 return mResourcesCache.get(subId); 905 } 906 907 return SubscriptionManager.getResourcesForSubId(mContext, subId); 908 } 909 910 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)911 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 912 pw.println("CellBroadcastHandler:"); 913 mLocalLog.dump(fd, pw, args); 914 pw.flush(); 915 try { 916 super.dump(fd, pw, args); 917 } catch (NullPointerException e) { 918 // StateMachine.dump() throws a NPE if there is no current state in the stack. Since 919 // StateMachine is defined in the framework and CBS is updated through mailine, we 920 // catch the NPE here as well as fixing the exception in the framework. 921 pw.println("StateMachine: no state info"); 922 } 923 } 924 925 /** The callback interface of a location request. */ 926 public interface LocationUpdateCallback { 927 /** 928 * Called when the location update is available. 929 * @param location a location in (latitude, longitude) format. 930 * @param accuracy the accuracy of the location given from location manager. Given in 931 * meters. 932 */ onLocationUpdate(@onNull LatLng location, float accuracy)933 void onLocationUpdate(@NonNull LatLng location, float accuracy); 934 935 /** 936 * This is called in the following scenarios: 937 * 1. The max time limit of the LocationRequester was reached, and consequently, 938 * no more location updates will be sent. 939 * 2. The service does not have permission to request a location update. 940 * 3. The LocationRequester was explicitly cancelled. 941 */ onLocationUnavailable()942 void onLocationUnavailable(); 943 944 /** 945 * Returns true if all messages are handled. 946 */ areAllMessagesHandled()947 boolean areAllMessagesHandled(); 948 } 949 950 private static final class LocationRequester { 951 /** 952 * Fused location provider, which means GPS plus network based providers (cell, wifi, etc..) 953 */ 954 //TODO: Should make LocationManager.FUSED_PROVIDER system API in S. 955 private static final String FUSED_PROVIDER = "fused"; 956 957 /** 958 * The interval in which location requests will be sent. 959 * see more: <code>LocationRequest#setInterval</code> 960 */ 961 private static final long LOCATION_REQUEST_INTERVAL_MILLIS = 1000; 962 963 private final LocationManager mLocationManager; 964 private final List<LocationUpdateCallback> mCallbacks; 965 private final HandlerHelper mHandlerHelper; 966 private final Context mContext; 967 private final LocationListener mLocationListener; 968 969 private boolean mLocationUpdateInProgress; 970 private final Runnable mLocationUnavailable; 971 private final String mDebugTag; 972 LocationRequester(Context context, LocationManager locationManager, HandlerHelper handlerHelper, String debugTag)973 LocationRequester(Context context, LocationManager locationManager, 974 HandlerHelper handlerHelper, String debugTag) { 975 mLocationManager = locationManager; 976 mCallbacks = new ArrayList<>(); 977 mContext = context; 978 mHandlerHelper = handlerHelper; 979 mLocationUpdateInProgress = false; 980 mLocationUnavailable = this::onLocationUnavailable; 981 982 // Location request did not cancel itself when using this::onLocationListener 983 mLocationListener = this::onLocationUpdate; 984 mDebugTag = debugTag; 985 } 986 987 /** 988 * Requests a stream of updates for {@code maximumWaitTimeSec} seconds. 989 * @param callback the callback used to communicate back to the caller 990 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not 991 * updated within the maximum wait time, 992 * {@code callback#onLocationUnavailable()} will be called. 993 */ requestLocationUpdate(@onNull LocationUpdateCallback callback, int maximumWaitTimeSec)994 void requestLocationUpdate(@NonNull LocationUpdateCallback callback, 995 int maximumWaitTimeSec) { 996 mHandlerHelper.post(() -> requestLocationUpdateInternal(callback, maximumWaitTimeSec)); 997 } 998 onLocationUpdate(@onNull Location location)999 private void onLocationUpdate(@NonNull Location location) { 1000 if (location == null) { 1001 /* onLocationUpdate should neverreceive a null location, but, covering all of our 1002 bases here. */ 1003 Log.wtf(mDebugTag, "Location is never supposed to be null"); 1004 return; 1005 } 1006 1007 LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); 1008 float accuracy = location.getAccuracy(); 1009 if (DBG) { 1010 Log.d(mDebugTag, "onLocationUpdate: received location update"); 1011 } 1012 1013 boolean canCancel = true; 1014 for (LocationUpdateCallback callback : mCallbacks) { 1015 callback.onLocationUpdate(latLng, accuracy); 1016 canCancel = canCancel && callback.areAllMessagesHandled(); 1017 } 1018 if (canCancel) { 1019 Log.d(mDebugTag, "call cancel because all messages are handled."); 1020 cancel(); 1021 } 1022 } 1023 onLocationUnavailable()1024 private void onLocationUnavailable() { 1025 Log.d(mDebugTag, "onLocationUnavailable: called"); 1026 locationRequesterCycleComplete(); 1027 } 1028 1029 /* This should only be called if all of the messages are handled. */ cancel()1030 public void cancel() { 1031 if (mLocationUpdateInProgress) { 1032 Log.d(mDebugTag, "cancel: location update in progress"); 1033 mHandlerHelper.removeCallbacks(mLocationUnavailable); 1034 locationRequesterCycleComplete(); 1035 } else { 1036 Log.d(mDebugTag, "cancel: location update NOT in progress"); 1037 } 1038 } 1039 locationRequesterCycleComplete()1040 private void locationRequesterCycleComplete() { 1041 try { 1042 for (LocationUpdateCallback callback : mCallbacks) { 1043 callback.onLocationUnavailable(); 1044 } 1045 } finally { 1046 mLocationManager.removeUpdates(mLocationListener); 1047 // Reset the state of location requester for the next request 1048 mCallbacks.clear(); 1049 mLocationUpdateInProgress = false; 1050 } 1051 } 1052 requestLocationUpdateInternal(@onNull LocationUpdateCallback callback, int maximumWaitTimeS)1053 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback, 1054 int maximumWaitTimeS) { 1055 if (DBG) Log.d(mDebugTag, "requestLocationUpdate"); 1056 if (!hasPermission(ACCESS_FINE_LOCATION) && !hasPermission(ACCESS_COARSE_LOCATION)) { 1057 if (DBG) { 1058 Log.e(mDebugTag, 1059 "Can't request location update because of no location permission"); 1060 } 1061 callback.onLocationUnavailable(); 1062 return; 1063 } 1064 1065 if (!mLocationUpdateInProgress) { 1066 try { 1067 // If the user does not turn on location, immediately report location 1068 // unavailable. 1069 if (!mLocationManager.isLocationEnabled()) { 1070 Log.d(mDebugTag, "Location is turned off."); 1071 callback.onLocationUnavailable(); 1072 return; 1073 } 1074 1075 /* We will continue to send updates until the location timeout is reached. The 1076 location timeout case is handled through onLocationUnavailable. */ 1077 LocationRequest request = LocationRequest.create() 1078 .setProvider(FUSED_PROVIDER) 1079 .setQuality(LocationRequest.ACCURACY_FINE) 1080 .setInterval(LOCATION_REQUEST_INTERVAL_MILLIS); 1081 if (DBG) { 1082 Log.d(mDebugTag, "Location request=" + request); 1083 } 1084 mLocationManager.requestLocationUpdates(request, 1085 new HandlerExecutor(mHandlerHelper.getHandler()), 1086 mLocationListener); 1087 1088 // TODO: Remove the following workaround in S. We need to enforce the timeout 1089 // before location manager adds the support for timeout value which is less 1090 // than 30 seconds. After that we can rely on location manager's timeout 1091 // mechanism. 1092 mHandlerHelper.postDelayed(mLocationUnavailable, 1093 TimeUnit.SECONDS.toMillis(maximumWaitTimeS)); 1094 } catch (IllegalArgumentException e) { 1095 Log.e(mDebugTag, "Cannot get current location. e=" + e); 1096 callback.onLocationUnavailable(); 1097 return; 1098 } 1099 mLocationUpdateInProgress = true; 1100 } 1101 mCallbacks.add(callback); 1102 } 1103 hasPermission(String permission)1104 private boolean hasPermission(String permission) { 1105 // TODO: remove the check. This will always return true because cell broadcast service 1106 // is running under the UID Process.NETWORK_STACK_UID, which is below 10000. It will be 1107 // automatically granted with all runtime permissions. 1108 return mContext.checkPermission(permission, Process.myPid(), Process.myUid()) 1109 == PackageManager.PERMISSION_GRANTED; 1110 } 1111 } 1112 1113 /** 1114 * Provides message identifiers that are helpful when logging messages. 1115 * 1116 * @param message the message to log 1117 * @return a helpful message 1118 */ getMessageString(SmsCbMessage message)1119 protected static String getMessageString(SmsCbMessage message) { 1120 return "msg=(" 1121 + message.getServiceCategory() + "," 1122 + message.getSerialNumber() + ")"; 1123 } 1124 1125 1126 /** 1127 * Wraps the {@code Handler} in order to mock the methods. 1128 */ 1129 @VisibleForTesting 1130 public static class HandlerHelper { 1131 1132 private final Handler mHandler; 1133 HandlerHelper(@onNull final Handler handler)1134 public HandlerHelper(@NonNull final Handler handler) { 1135 mHandler = handler; 1136 } 1137 1138 /** 1139 * Posts {@code r} to {@code handler} with a delay of {@code delayMillis} 1140 * 1141 * @param r the runnable callback 1142 * @param delayMillis the number of milliseconds to delay 1143 */ postDelayed(Runnable r, long delayMillis)1144 public void postDelayed(Runnable r, long delayMillis) { 1145 mHandler.postDelayed(r, delayMillis); 1146 } 1147 1148 /** 1149 * Posts {@code r} to the underlying handler 1150 * 1151 * @param r the runnable callback 1152 */ post(Runnable r)1153 public void post(Runnable r) { 1154 mHandler.post(r); 1155 } 1156 1157 /** 1158 * Gets the underlying handler 1159 * @return the handler 1160 */ getHandler()1161 public Handler getHandler() { 1162 return mHandler; 1163 } 1164 1165 /** 1166 * Remove any pending posts of Runnable r that are in the message queue. 1167 */ removeCallbacks(Runnable r)1168 public void removeCallbacks(Runnable r) { 1169 mHandler.removeCallbacks(r); 1170 } 1171 } 1172 } 1173