blob: 8ca6d1bc92c0c8beed4f416a6eeeb57b909dff5d [file] [log] [blame]
Jordan Liuc872fad2019-10-11 11:42:03 -07001/*
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
17package com.android.cellbroadcastservice;
18
Jordan Liuc872fad2019-10-11 11:42:03 -070019import static android.provider.Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG;
20
21import android.Manifest;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.app.Activity;
25import android.app.AppOpsManager;
Jack Yua1309272019-10-24 00:19:49 -070026import android.content.BroadcastReceiver;
Jordan Liuc872fad2019-10-11 11:42:03 -070027import android.content.ContentValues;
28import android.content.Context;
29import android.content.Intent;
Jack Yua1309272019-10-24 00:19:49 -070030import android.content.IntentFilter;
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -070031import android.content.pm.PackageManager;
Jack Yua1309272019-10-24 00:19:49 -070032import android.content.res.Resources;
33import android.database.Cursor;
Jordan Liuc872fad2019-10-11 11:42:03 -070034import android.location.Location;
35import android.location.LocationListener;
36import android.location.LocationManager;
37import android.net.Uri;
38import android.os.Build;
39import android.os.Bundle;
40import android.os.Handler;
41import android.os.Looper;
42import android.os.Message;
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -070043import android.os.Process;
Jordan Liu26e820c2019-10-22 14:42:07 -070044import android.os.SystemProperties;
Jordan Liuc872fad2019-10-11 11:42:03 -070045import android.os.UserHandle;
46import android.provider.Settings;
47import android.provider.Telephony;
48import android.provider.Telephony.CellBroadcasts;
49import android.telephony.SmsCbMessage;
50import android.telephony.SubscriptionManager;
Jack Yu2003c522019-10-31 09:47:20 -070051import android.telephony.cdma.CdmaSmsCbProgramData;
Jack Yua1309272019-10-24 00:19:49 -070052import android.text.TextUtils;
Jordan Liuc872fad2019-10-11 11:42:03 -070053import android.text.format.DateUtils;
54import android.util.LocalLog;
55import android.util.Log;
56
Jack Yua1309272019-10-24 00:19:49 -070057import com.android.internal.annotations.VisibleForTesting;
Jordan Liuc872fad2019-10-11 11:42:03 -070058import com.android.internal.telephony.CbGeoUtils.Geometry;
59import com.android.internal.telephony.CbGeoUtils.LatLng;
60import com.android.internal.telephony.metrics.TelephonyMetrics;
61
62import java.io.FileDescriptor;
63import java.io.PrintWriter;
Jack Yua1309272019-10-24 00:19:49 -070064import java.text.DateFormat;
Jordan Liuc872fad2019-10-11 11:42:03 -070065import java.util.ArrayList;
66import java.util.Arrays;
Jack Yua1309272019-10-24 00:19:49 -070067import java.util.HashMap;
Jordan Liuc872fad2019-10-11 11:42:03 -070068import java.util.List;
Jack Yua1309272019-10-24 00:19:49 -070069import java.util.Map;
Jack Yu6f9a26e2019-11-07 23:10:02 -080070import java.util.Objects;
Jack Yu2003c522019-10-31 09:47:20 -070071import java.util.stream.Collectors;
72import java.util.stream.Stream;
Jordan Liuc872fad2019-10-11 11:42:03 -070073
74/**
75 * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
76 * completes and our result receiver is called.
77 */
78public class CellBroadcastHandler extends WakeLockStateMachine {
79 private static final String EXTRA_MESSAGE = "message";
80
Jack Yudc607602019-10-31 13:24:43 -070081 /**
82 * To disable cell broadcast duplicate detection for debugging purposes
83 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
84 * --ez enable false</code>
85 *
86 * To enable cell broadcast duplicate detection for debugging purposes
87 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
88 * --ez enable true</code>
89 */
90 private static final String ACTION_DUPLICATE_DETECTION =
91 "com.android.cellbroadcastservice.action.DUPLICATE_DETECTION";
92
93 /**
94 * The extra for cell broadcast duplicate detection enable/disable
95 */
96 private static final String EXTRA_ENABLE = "enable";
97
Jordan Liuc872fad2019-10-11 11:42:03 -070098 private final LocalLog mLocalLog = new LocalLog(100);
99
Jordan Liu26e820c2019-10-22 14:42:07 -0700100 private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
101
Jordan Liuc872fad2019-10-11 11:42:03 -0700102 /** Uses to request the location update. */
103 public final LocationRequester mLocationRequester;
104
Jack Yua1309272019-10-24 00:19:49 -0700105 /** Timestamp of last airplane mode on */
106 private long mLastAirplaneModeTime = 0;
107
Jack Yu2003c522019-10-31 09:47:20 -0700108 /** Resource cache */
Jack Yua1309272019-10-24 00:19:49 -0700109 private final Map<Integer, Resources> mResourcesCache = new HashMap<>();
110
Jack Yudc607602019-10-31 13:24:43 -0700111 /** Whether performing duplicate detection or not. Note this is for debugging purposes only. */
112 private boolean mEnableDuplicateDetection = true;
113
Jack Yu2003c522019-10-31 09:47:20 -0700114 /**
115 * Service category equivalent map. The key is the GSM service category, the value is the CDMA
116 * service category.
117 */
118 private final Map<Integer, Integer> mServiceCategoryCrossRATMap;
119
Jordan Liuc872fad2019-10-11 11:42:03 -0700120 private CellBroadcastHandler(Context context) {
121 this("CellBroadcastHandler", context);
122 }
123
124 protected CellBroadcastHandler(String debugTag, Context context) {
125 super(debugTag, context);
126 mLocationRequester = new LocationRequester(
127 context,
128 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
129 getHandler().getLooper());
Jack Yua1309272019-10-24 00:19:49 -0700130
Jack Yu2003c522019-10-31 09:47:20 -0700131 // Adding GSM / CDMA service category mapping.
132 mServiceCategoryCrossRATMap = Stream.of(new Integer[][] {
133 // Presidential alert
134 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
135 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
136 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE,
137 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
138
139 // Extreme alert
140 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
141 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
142 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE,
143 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
144 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
145 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
146 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE,
147 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
148
149 // Severe alert
150 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
151 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
152 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE,
153 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
154 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
155 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
156 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE,
157 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
158 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED,
159 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
160 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE,
161 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
162 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
163 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
164 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE,
165 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
166 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
167 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
168 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE,
169 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
170 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
171 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
172 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE,
173 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
174
175 // Amber alert
176 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
177 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
178 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE,
179 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
180
181 // Monthly test alert
182 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
183 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
184 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE,
185 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
186 }).collect(Collectors.toMap(data -> data[0], data -> data[1]));
187
Jack Yudc607602019-10-31 13:24:43 -0700188 IntentFilter intentFilter = new IntentFilter();
189 intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
190 if (Build.IS_DEBUGGABLE) {
191 intentFilter.addAction(ACTION_DUPLICATE_DETECTION);
192 }
Jack Yua1309272019-10-24 00:19:49 -0700193 mContext.registerReceiver(
194 new BroadcastReceiver() {
195 @Override
196 public void onReceive(Context context, Intent intent) {
Jack Yudc607602019-10-31 13:24:43 -0700197 switch (intent.getAction()) {
198 case Intent.ACTION_AIRPLANE_MODE_CHANGED:
199 boolean airplaneModeOn = intent.getBooleanExtra("state", false);
200 if (airplaneModeOn) {
201 mLastAirplaneModeTime = System.currentTimeMillis();
202 log("Airplane mode on. Reset duplicate detection.");
203 }
204 break;
205 case ACTION_DUPLICATE_DETECTION:
206 mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE,
207 true);
208 break;
Jack Yua1309272019-10-24 00:19:49 -0700209 }
Jack Yudc607602019-10-31 13:24:43 -0700210
Jack Yua1309272019-10-24 00:19:49 -0700211 }
Jack Yudc607602019-10-31 13:24:43 -0700212 }, intentFilter);
Jordan Liuc872fad2019-10-11 11:42:03 -0700213 }
214
215 /**
216 * Create a new CellBroadcastHandler.
217 * @param context the context to use for dispatching Intents
218 * @return the new handler
219 */
220 public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
221 CellBroadcastHandler handler = new CellBroadcastHandler(context);
222 handler.start();
223 return handler;
224 }
225
226 /**
227 * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
228 * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
229 *
230 * @param message the message to process
231 * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
232 */
233 @Override
234 protected boolean handleSmsMessage(Message message) {
235 if (message.obj instanceof SmsCbMessage) {
Jack Yua1309272019-10-24 00:19:49 -0700236 if (!isDuplicate((SmsCbMessage) message.obj)) {
237 handleBroadcastSms((SmsCbMessage) message.obj);
238 return true;
239 }
240 return false;
Jordan Liuc872fad2019-10-11 11:42:03 -0700241 } else {
242 loge("handleMessage got object of type: " + message.obj.getClass().getName());
243 return false;
244 }
245 }
246
247 /**
248 * Dispatch a Cell Broadcast message to listeners.
249 * @param message the Cell Broadcast to broadcast
250 */
251 protected void handleBroadcastSms(SmsCbMessage message) {
252 int slotIndex = message.getSlotIndex();
253 // Log Cellbroadcast msg received event
254 TelephonyMetrics metrics = TelephonyMetrics.getInstance();
255 metrics.writeNewCBSms(slotIndex, message.getMessageFormat(),
256 message.getMessagePriority(), message.isCmasMessage(), message.isEtwsMessage(),
257 message.getServiceCategory(), message.getSerialNumber(),
258 System.currentTimeMillis());
259
260 // TODO: Database inserting can be time consuming, therefore this should be changed to
261 // asynchronous.
262 ContentValues cv = message.getContentValues();
Chen Xu93fdfeb2019-10-21 22:56:37 -0700263 Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv);
Jordan Liuc872fad2019-10-11 11:42:03 -0700264
265 if (message.needGeoFencingCheck()) {
266 if (DBG) {
267 log("Request location update for geo-fencing. serialNumber = "
268 + message.getSerialNumber());
269 }
270
271 requestLocationUpdate(location -> {
272 if (location == null) {
273 // Broadcast the message directly if the location is not available.
274 broadcastMessage(message, uri, slotIndex);
275 } else {
276 performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
277 }
278 }, message.getMaximumWaitingTime());
279 } else {
280 if (DBG) {
281 log("Broadcast the message directly because no geo-fencing required, "
282 + "serialNumber = " + message.getSerialNumber()
283 + " needGeoFencing = " + message.needGeoFencingCheck());
284 }
285 broadcastMessage(message, uri, slotIndex);
286 }
287 }
288
289 /**
Jack Yua1309272019-10-24 00:19:49 -0700290 * Check if the message is a duplicate
291 *
292 * @param message Cell broadcast message
293 * @return {@code true} if this message is a duplicate
294 */
295 @VisibleForTesting
296 public boolean isDuplicate(SmsCbMessage message) {
Jack Yudc607602019-10-31 13:24:43 -0700297 if (!mEnableDuplicateDetection) {
298 log("Duplicate detection was disabled for debugging purposes.");
299 return false;
300 }
301
Jack Yua1309272019-10-24 00:19:49 -0700302 // Find the cell broadcast message identify by the message identifier and serial number
303 // and is not broadcasted.
304 String where = CellBroadcasts.RECEIVED_TIME + ">?";
305
306 int slotIndex = message.getSlotIndex();
307 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
308 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
309 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
310 Resources res;
311 if (subIds != null) {
312 res = getResources(subIds[0]);
313 } else {
314 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
315 }
316
317 // Only consider cell broadcast messages received within certain period.
318 // By default it's 24 hours.
319 long expirationDuration = res.getInteger(R.integer.message_expiration_time);
320 long dupCheckTime = System.currentTimeMillis() - expirationDuration;
321
322 // Some carriers require reset duplication detection after airplane mode.
323 if (res.getBoolean(R.bool.reset_duplicate_detection_on_airplane_mode)) {
324 dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime);
325 }
326
327 List<SmsCbMessage> cbMessages = new ArrayList<>();
328
329 try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI,
330 // TODO: QUERY_COLUMNS_FWK is a hidden API, since we are going to move
331 // CellBroadcastProvider to this module we can define those COLUMNS in side
332 // CellBroadcastProvider and reference from there.
333 CellBroadcasts.QUERY_COLUMNS_FWK,
334 where,
335 new String[] {Long.toString(dupCheckTime)},
336 null)) {
337 if (cursor != null) {
338 while (cursor.moveToNext()) {
339 cbMessages.add(SmsCbMessage.createFromCursor(cursor));
340 }
341 }
342 }
343
344 boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body);
345
346 log("Found " + cbMessages.size() + " messages since "
347 + DateFormat.getDateTimeInstance().format(dupCheckTime));
348 for (SmsCbMessage messageToCheck : cbMessages) {
349 // If messages are from different slots, then we only compare the message body.
350 if (message.getSlotIndex() != messageToCheck.getSlotIndex()) {
351 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
352 log("Duplicate message detected from different slot. " + message);
353 return true;
354 }
355 } else {
356 // Check serial number if message is from the same carrier.
357 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
358 // Not a dup. Check next one.
359 log("Serial number check. Not a dup. " + messageToCheck);
360 continue;
361 }
362
Jack Yu2003c522019-10-31 09:47:20 -0700363 // ETWS primary / secondary should be treated differently.
364 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
365 && message.getEtwsWarningInfo().isPrimary()
366 != messageToCheck.getEtwsWarningInfo().isPrimary()) {
Jack Yua1309272019-10-24 00:19:49 -0700367 // Not a dup. Check next one.
Jack Yu6f9a26e2019-11-07 23:10:02 -0800368 log("ETWS primary check. Not a dup. " + messageToCheck);
Jack Yua1309272019-10-24 00:19:49 -0700369 continue;
370 }
371
Jack Yu2003c522019-10-31 09:47:20 -0700372 // Check if the message category is different. Some carriers send cell broadcast
373 // messages on different techs (i.e. GSM / CDMA), so we need to compare service
374 // category cross techs.
375 if (message.getServiceCategory() != messageToCheck.getServiceCategory()
Jack Yu6f9a26e2019-11-07 23:10:02 -0800376 && !Objects.equals(mServiceCategoryCrossRATMap.get(
377 message.getServiceCategory()), messageToCheck.getServiceCategory())
378 && !Objects.equals(mServiceCategoryCrossRATMap.get(
379 messageToCheck.getServiceCategory()),
380 message.getServiceCategory())) {
Jack Yua1309272019-10-24 00:19:49 -0700381 // Not a dup. Check next one.
Jack Yu6f9a26e2019-11-07 23:10:02 -0800382 log("Service category check. Not a dup. " + messageToCheck);
Jack Yua1309272019-10-24 00:19:49 -0700383 continue;
384 }
385
386 // Compare message body if needed.
387 if (!compareMessageBody || TextUtils.equals(
388 message.getMessageBody(), messageToCheck.getMessageBody())) {
389 log("Duplicate message detected. " + message);
390 return true;
391 }
392 }
393 }
394
395 log("Not a duplicate message. " + message);
396 return false;
397 }
398
399 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700400 * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
401 * {@code location} is inside the {@code broadcastArea}.
402 * @param message the message need to geo-fencing check
403 * @param uri the message's uri
404 * @param broadcastArea the broadcast area of the message
405 * @param location current location
406 */
407 protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
408 LatLng location, int slotIndex) {
409
410 if (DBG) {
411 logd("Perform geo-fencing check for message identifier = "
412 + message.getServiceCategory()
413 + " serialNumber = " + message.getSerialNumber());
414 }
415
416 for (Geometry geo : broadcastArea) {
417 if (geo.contains(location)) {
418 broadcastMessage(message, uri, slotIndex);
419 return;
420 }
421 }
422
423 if (DBG) {
424 logd("Device location is outside the broadcast area "
425 + CbGeoUtils.encodeGeometriesToString(broadcastArea));
426 }
427 }
428
429 /**
430 * Request a single location update.
431 * @param callback a callback will be called when the location is available.
432 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
433 * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
434 */
435 protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
436 mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
437 }
438
439 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700440 * Broadcast the {@code message} to the applications.
441 * @param message a message need to broadcast
442 * @param messageUri message's uri
443 */
444 protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
445 int slotIndex) {
446 String receiverPermission;
447 int appOp;
448 String msg;
449 Intent intent;
450 if (message.isEmergencyMessage()) {
451 msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
452 log(msg);
453 mLocalLog.log(msg);
454 intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
455 //Emergency alerts need to be delivered with high priority
456 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
457 receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
458 appOp = AppOpsManager.OP_RECEIVE_EMERGECY_SMS;
459
460 intent.putExtra(EXTRA_MESSAGE, message);
461 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
462
463 if (Build.IS_DEBUGGABLE) {
464 // Send additional broadcast intent to the specified package. This is only for sl4a
465 // automation tests.
466 final String additionalPackage = Settings.Secure.getString(
467 mContext.getContentResolver(), CMAS_ADDITIONAL_BROADCAST_PKG);
468 if (additionalPackage != null) {
469 Intent additionalIntent = new Intent(intent);
470 additionalIntent.setPackage(additionalPackage);
471 mContext.sendOrderedBroadcastAsUser(additionalIntent, UserHandle.ALL,
472 receiverPermission, appOp, null, getHandler(), Activity.RESULT_OK,
473 null, null);
474 }
475 }
476
477 String[] pkgs = mContext.getResources().getStringArray(
478 com.android.internal.R.array.config_defaultCellBroadcastReceiverPkgs);
479 mReceiverCount.addAndGet(pkgs.length);
480 for (String pkg : pkgs) {
481 // Explicitly send the intent to all the configured cell broadcast receivers.
482 intent.setPackage(pkg);
483 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission,
484 appOp, mReceiver, getHandler(), Activity.RESULT_OK, null, null);
485 }
486 } else {
487 msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
488 log(msg);
489 mLocalLog.log(msg);
490 intent = new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION);
491 // Send implicit intent since there are various 3rd party carrier apps listen to
492 // this intent.
493 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
494 receiverPermission = Manifest.permission.RECEIVE_SMS;
495 appOp = AppOpsManager.OP_RECEIVE_SMS;
496
497 intent.putExtra(EXTRA_MESSAGE, message);
498 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
499
500 mReceiverCount.incrementAndGet();
501 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission, appOp,
502 mReceiver, getHandler(), Activity.RESULT_OK, null, null);
503 }
504
505 if (messageUri != null) {
506 ContentValues cv = new ContentValues();
507 cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
Chen Xu93fdfeb2019-10-21 22:56:37 -0700508 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
Jordan Liuc872fad2019-10-11 11:42:03 -0700509 CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
510 }
511 }
512
Jack Yua1309272019-10-24 00:19:49 -0700513 /**
514 * Get the device resource based on SIM
515 *
516 * @param subId Subscription index
517 *
518 * @return The resource
519 */
520 public @NonNull Resources getResources(int subId) {
521 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
522 || !SubscriptionManager.isValidSubscriptionId(subId)) {
523 return mContext.getResources();
524 }
525
526 if (mResourcesCache.containsKey(subId)) {
527 return mResourcesCache.get(subId);
528 }
529
530 Resources res = SubscriptionManager.getResourcesForSubId(mContext, subId);
531 mResourcesCache.put(subId, res);
532
533 return res;
534 }
535
Jordan Liuc872fad2019-10-11 11:42:03 -0700536 @Override
537 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
538 pw.println("CellBroadcastHandler:");
539 mLocalLog.dump(fd, pw, args);
540 pw.flush();
541 }
542
543 /** The callback interface of a location request. */
544 public interface LocationUpdateCallback {
545 /**
546 * Call when the location update is available.
547 * @param location a location in (latitude, longitude) format, or {@code null} if the
548 * location service is not available.
549 */
550 void onLocationUpdate(@Nullable LatLng location);
551 }
552
553 private static final class LocationRequester {
554 private static final String TAG = LocationRequester.class.getSimpleName();
555
556 /**
557 * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
558 * Most of the location request should be responded within 20 seconds.
559 */
560 private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 20;
561
562 /**
563 * Trigger this event when the {@link LocationManager} is not responded within the given
564 * time.
565 */
566 private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
567
568 /** Request a single location update. */
569 private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
570
571 /**
572 * Request location update from network or gps location provider. Network provider will be
573 * used if available, otherwise use the gps provider.
574 */
575 private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
576 LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
577
578 private final LocationManager mLocationManager;
579 private final Looper mLooper;
580 private final List<LocationUpdateCallback> mCallbacks;
581 private final Context mContext;
582 private Handler mLocationHandler;
583
584 LocationRequester(Context context, LocationManager locationManager, Looper looper) {
585 mLocationManager = locationManager;
586 mLooper = looper;
587 mCallbacks = new ArrayList<>();
588 mContext = context;
589 mLocationHandler = new LocationHandler(looper);
590 }
591
592 /**
593 * Request a single location update. If the location is not available, a callback with
594 * {@code null} location will be called immediately.
595 *
596 * @param callback a callback to the response when the location is available
597 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
598 * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
599 * called.
600 */
601 void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
602 int maximumWaitTimeSec) {
603 mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
604 0 /* arg2 */, callback).sendToTarget();
605 }
606
607 private void onLocationUpdate(@Nullable LatLng location) {
608 for (LocationUpdateCallback callback : mCallbacks) {
609 callback.onLocationUpdate(location);
610 }
611 mCallbacks.clear();
612 }
613
614 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
615 int maximumWaitTimeSec) {
616 if (DBG) Log.d(TAG, "requestLocationUpdate");
617 if (!isLocationServiceAvailable()) {
618 if (DBG) {
619 Log.d(TAG, "Can't request location update because of no location permission");
620 }
621 callback.onLocationUpdate(null);
622 return;
623 }
624
625 if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
626 if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
627 maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
628 }
629 mLocationHandler.sendMessageDelayed(
630 mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
631 maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
632 }
633
634 mCallbacks.add(callback);
635
636 for (String provider : LOCATION_PROVIDERS) {
637 if (mLocationManager.isProviderEnabled(provider)) {
638 mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper);
639 break;
640 }
641 }
642 }
643
644 private boolean isLocationServiceAvailable() {
645 if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
646 && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
647 for (String provider : LOCATION_PROVIDERS) {
648 if (mLocationManager.isProviderEnabled(provider)) return true;
649 }
650 return false;
651 }
652
653 private boolean hasPermission(String permission) {
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -0700654 return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
655 == PackageManager.PERMISSION_GRANTED;
Jordan Liuc872fad2019-10-11 11:42:03 -0700656 }
657
658 private final LocationListener mLocationListener = new LocationListener() {
659 @Override
660 public void onLocationChanged(Location location) {
661 mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
662 onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude()));
663 }
664
665 @Override
666 public void onStatusChanged(String provider, int status, Bundle extras) {}
667
668 @Override
669 public void onProviderEnabled(String provider) {}
670
671 @Override
672 public void onProviderDisabled(String provider) {}
673 };
674
675 private final class LocationHandler extends Handler {
676 LocationHandler(Looper looper) {
677 super(looper);
678 }
679
680 @Override
681 public void handleMessage(Message msg) {
682 switch (msg.what) {
683 case EVENT_LOCATION_REQUEST_TIMEOUT:
684 if (DBG) Log.d(TAG, "location request timeout");
685 onLocationUpdate(null);
686 break;
687 case EVENT_REQUEST_LOCATION_UPDATE:
688 requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
689 break;
690 default:
691 Log.e(TAG, "Unsupported message type " + msg.what);
692 }
693 }
694 }
695 }
696}