blob: 7507386c25533cbc8778bccc1597accd1e121507 [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;
Jordan Liuc872fad2019-10-11 11:42:03 -070038import android.os.Bundle;
39import android.os.Handler;
Jack Yue7591d32019-11-07 22:59:46 -080040import android.os.HandlerExecutor;
Jordan Liuc872fad2019-10-11 11:42:03 -070041import android.os.Looper;
42import android.os.Message;
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -070043import android.os.Process;
Jack Yue7591d32019-11-07 22:59:46 -080044import android.os.SystemClock;
Jordan Liu26e820c2019-10-22 14:42:07 -070045import android.os.SystemProperties;
Jordan Liuc872fad2019-10-11 11:42:03 -070046import android.os.UserHandle;
47import android.provider.Settings;
48import android.provider.Telephony;
49import android.provider.Telephony.CellBroadcasts;
Jordan Liu4a2ff262019-10-21 12:30:56 -070050import android.telephony.CbGeoUtils.Geometry;
51import android.telephony.CbGeoUtils.LatLng;
Jordan Liuc872fad2019-10-11 11:42:03 -070052import android.telephony.SmsCbMessage;
53import android.telephony.SubscriptionManager;
Jack Yu2003c522019-10-31 09:47:20 -070054import android.telephony.cdma.CdmaSmsCbProgramData;
Jack Yua1309272019-10-24 00:19:49 -070055import android.text.TextUtils;
Jordan Liuc872fad2019-10-11 11:42:03 -070056import android.text.format.DateUtils;
57import android.util.LocalLog;
58import android.util.Log;
59
Jack Yua1309272019-10-24 00:19:49 -070060import com.android.internal.annotations.VisibleForTesting;
Jordan Liuc872fad2019-10-11 11:42:03 -070061import com.android.internal.telephony.metrics.TelephonyMetrics;
62
63import java.io.FileDescriptor;
64import java.io.PrintWriter;
Jack Yua1309272019-10-24 00:19:49 -070065import java.text.DateFormat;
Jordan Liuc872fad2019-10-11 11:42:03 -070066import java.util.ArrayList;
67import java.util.Arrays;
Jack Yua1309272019-10-24 00:19:49 -070068import java.util.HashMap;
Jordan Liuc872fad2019-10-11 11:42:03 -070069import java.util.List;
Jack Yua1309272019-10-24 00:19:49 -070070import java.util.Map;
Jack Yu6f9a26e2019-11-07 23:10:02 -080071import java.util.Objects;
Jack Yu2003c522019-10-31 09:47:20 -070072import java.util.stream.Collectors;
73import java.util.stream.Stream;
Jordan Liuc872fad2019-10-11 11:42:03 -070074
75/**
76 * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
77 * completes and our result receiver is called.
78 */
79public class CellBroadcastHandler extends WakeLockStateMachine {
80 private static final String EXTRA_MESSAGE = "message";
81
Jack Yudc607602019-10-31 13:24:43 -070082 /**
83 * To disable cell broadcast duplicate detection for debugging purposes
84 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
85 * --ez enable false</code>
86 *
87 * To enable cell broadcast duplicate detection for debugging purposes
88 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
89 * --ez enable true</code>
90 */
91 private static final String ACTION_DUPLICATE_DETECTION =
92 "com.android.cellbroadcastservice.action.DUPLICATE_DETECTION";
93
94 /**
95 * The extra for cell broadcast duplicate detection enable/disable
96 */
97 private static final String EXTRA_ENABLE = "enable";
98
Jordan Liuc872fad2019-10-11 11:42:03 -070099 private final LocalLog mLocalLog = new LocalLog(100);
100
Jordan Liu26e820c2019-10-22 14:42:07 -0700101 private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
102
Jordan Liuc872fad2019-10-11 11:42:03 -0700103 /** Uses to request the location update. */
104 public final LocationRequester mLocationRequester;
105
Jack Yua1309272019-10-24 00:19:49 -0700106 /** Timestamp of last airplane mode on */
Jack Yue7591d32019-11-07 22:59:46 -0800107 protected long mLastAirplaneModeTime = 0;
Jack Yua1309272019-10-24 00:19:49 -0700108
Jack Yu2003c522019-10-31 09:47:20 -0700109 /** Resource cache */
Jack Yua1309272019-10-24 00:19:49 -0700110 private final Map<Integer, Resources> mResourcesCache = new HashMap<>();
111
Jack Yudc607602019-10-31 13:24:43 -0700112 /** Whether performing duplicate detection or not. Note this is for debugging purposes only. */
113 private boolean mEnableDuplicateDetection = true;
114
Jack Yu2003c522019-10-31 09:47:20 -0700115 /**
116 * Service category equivalent map. The key is the GSM service category, the value is the CDMA
117 * service category.
118 */
119 private final Map<Integer, Integer> mServiceCategoryCrossRATMap;
120
Jordan Liuc872fad2019-10-11 11:42:03 -0700121 private CellBroadcastHandler(Context context) {
Jack Yue7591d32019-11-07 22:59:46 -0800122 this("CellBroadcastHandler", context, Looper.myLooper());
Jordan Liuc872fad2019-10-11 11:42:03 -0700123 }
124
Jack Yue7591d32019-11-07 22:59:46 -0800125 protected CellBroadcastHandler(String debugTag, Context context, Looper looper) {
126 super(debugTag, context, looper);
Jordan Liuc872fad2019-10-11 11:42:03 -0700127 mLocationRequester = new LocationRequester(
128 context,
129 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
130 getHandler().getLooper());
Jack Yua1309272019-10-24 00:19:49 -0700131
Jack Yu2003c522019-10-31 09:47:20 -0700132 // Adding GSM / CDMA service category mapping.
133 mServiceCategoryCrossRATMap = Stream.of(new Integer[][] {
134 // Presidential alert
135 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
136 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
137 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE,
138 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
139
140 // Extreme alert
141 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
142 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
143 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE,
144 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
145 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
146 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
147 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE,
148 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
149
150 // Severe alert
151 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
152 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
153 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE,
154 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
155 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
156 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
157 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE,
158 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
159 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED,
160 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
161 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE,
162 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
163 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
164 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
165 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE,
166 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
167 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
168 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
169 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE,
170 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
171 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
172 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
173 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE,
174 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
175
176 // Amber alert
177 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
178 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
179 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE,
180 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
181
182 // Monthly test alert
183 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
184 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
185 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE,
186 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
187 }).collect(Collectors.toMap(data -> data[0], data -> data[1]));
188
Jack Yudc607602019-10-31 13:24:43 -0700189 IntentFilter intentFilter = new IntentFilter();
190 intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
Jordan Liu4a2ff262019-10-21 12:30:56 -0700191 if (IS_DEBUGGABLE) {
Jack Yudc607602019-10-31 13:24:43 -0700192 intentFilter.addAction(ACTION_DUPLICATE_DETECTION);
193 }
Jack Yua1309272019-10-24 00:19:49 -0700194 mContext.registerReceiver(
195 new BroadcastReceiver() {
196 @Override
197 public void onReceive(Context context, Intent intent) {
Jack Yudc607602019-10-31 13:24:43 -0700198 switch (intent.getAction()) {
199 case Intent.ACTION_AIRPLANE_MODE_CHANGED:
200 boolean airplaneModeOn = intent.getBooleanExtra("state", false);
201 if (airplaneModeOn) {
202 mLastAirplaneModeTime = System.currentTimeMillis();
Jack Yu446d1742019-11-12 17:30:27 -0800203 log("Airplane mode on.");
Jack Yudc607602019-10-31 13:24:43 -0700204 }
205 break;
206 case ACTION_DUPLICATE_DETECTION:
207 mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE,
208 true);
209 break;
Jack Yua1309272019-10-24 00:19:49 -0700210 }
Jack Yudc607602019-10-31 13:24:43 -0700211
Jack Yua1309272019-10-24 00:19:49 -0700212 }
Jack Yudc607602019-10-31 13:24:43 -0700213 }, intentFilter);
Jordan Liuc872fad2019-10-11 11:42:03 -0700214 }
215
216 /**
217 * Create a new CellBroadcastHandler.
218 * @param context the context to use for dispatching Intents
219 * @return the new handler
220 */
221 public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
222 CellBroadcastHandler handler = new CellBroadcastHandler(context);
223 handler.start();
224 return handler;
225 }
226
227 /**
228 * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
229 * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
230 *
231 * @param message the message to process
232 * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
233 */
234 @Override
235 protected boolean handleSmsMessage(Message message) {
236 if (message.obj instanceof SmsCbMessage) {
Jack Yua1309272019-10-24 00:19:49 -0700237 if (!isDuplicate((SmsCbMessage) message.obj)) {
238 handleBroadcastSms((SmsCbMessage) message.obj);
239 return true;
240 }
241 return false;
Jordan Liuc872fad2019-10-11 11:42:03 -0700242 } else {
243 loge("handleMessage got object of type: " + message.obj.getClass().getName());
244 return false;
245 }
246 }
247
248 /**
249 * Dispatch a Cell Broadcast message to listeners.
250 * @param message the Cell Broadcast to broadcast
251 */
252 protected void handleBroadcastSms(SmsCbMessage message) {
253 int slotIndex = message.getSlotIndex();
254 // Log Cellbroadcast msg received event
255 TelephonyMetrics metrics = TelephonyMetrics.getInstance();
256 metrics.writeNewCBSms(slotIndex, message.getMessageFormat(),
257 message.getMessagePriority(), message.isCmasMessage(), message.isEtwsMessage(),
258 message.getServiceCategory(), message.getSerialNumber(),
259 System.currentTimeMillis());
260
261 // TODO: Database inserting can be time consuming, therefore this should be changed to
262 // asynchronous.
263 ContentValues cv = message.getContentValues();
Chen Xu93fdfeb2019-10-21 22:56:37 -0700264 Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv);
Jordan Liuc872fad2019-10-11 11:42:03 -0700265
266 if (message.needGeoFencingCheck()) {
267 if (DBG) {
268 log("Request location update for geo-fencing. serialNumber = "
269 + message.getSerialNumber());
270 }
271
272 requestLocationUpdate(location -> {
273 if (location == null) {
274 // Broadcast the message directly if the location is not available.
275 broadcastMessage(message, uri, slotIndex);
276 } else {
277 performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
278 }
Jordan Liu4a2ff262019-10-21 12:30:56 -0700279 }, message.getMaximumWaitingDuration());
Jordan Liuc872fad2019-10-11 11:42:03 -0700280 } else {
281 if (DBG) {
282 log("Broadcast the message directly because no geo-fencing required, "
283 + "serialNumber = " + message.getSerialNumber()
284 + " needGeoFencing = " + message.needGeoFencingCheck());
285 }
286 broadcastMessage(message, uri, slotIndex);
287 }
288 }
289
290 /**
Jack Yua1309272019-10-24 00:19:49 -0700291 * Check if the message is a duplicate
292 *
293 * @param message Cell broadcast message
294 * @return {@code true} if this message is a duplicate
295 */
296 @VisibleForTesting
297 public boolean isDuplicate(SmsCbMessage message) {
Jack Yudc607602019-10-31 13:24:43 -0700298 if (!mEnableDuplicateDetection) {
299 log("Duplicate detection was disabled for debugging purposes.");
300 return false;
301 }
302
Jack Yua1309272019-10-24 00:19:49 -0700303 // Find the cell broadcast message identify by the message identifier and serial number
304 // and is not broadcasted.
305 String where = CellBroadcasts.RECEIVED_TIME + ">?";
306
307 int slotIndex = message.getSlotIndex();
308 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
309 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
310 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
311 Resources res;
312 if (subIds != null) {
313 res = getResources(subIds[0]);
314 } else {
315 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
316 }
317
318 // Only consider cell broadcast messages received within certain period.
319 // By default it's 24 hours.
320 long expirationDuration = res.getInteger(R.integer.message_expiration_time);
321 long dupCheckTime = System.currentTimeMillis() - expirationDuration;
322
Jack Yue7591d32019-11-07 22:59:46 -0800323 // Some carriers require reset duplication detection after airplane mode or reboot.
324 if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) {
Jack Yua1309272019-10-24 00:19:49 -0700325 dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime);
Jack Yue7591d32019-11-07 22:59:46 -0800326 dupCheckTime = Long.max(dupCheckTime,
327 System.currentTimeMillis() - SystemClock.elapsedRealtime());
Jack Yua1309272019-10-24 00:19:49 -0700328 }
329
330 List<SmsCbMessage> cbMessages = new ArrayList<>();
331
332 try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI,
333 // TODO: QUERY_COLUMNS_FWK is a hidden API, since we are going to move
334 // CellBroadcastProvider to this module we can define those COLUMNS in side
335 // CellBroadcastProvider and reference from there.
336 CellBroadcasts.QUERY_COLUMNS_FWK,
337 where,
338 new String[] {Long.toString(dupCheckTime)},
339 null)) {
340 if (cursor != null) {
341 while (cursor.moveToNext()) {
342 cbMessages.add(SmsCbMessage.createFromCursor(cursor));
343 }
344 }
345 }
346
347 boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body);
348
349 log("Found " + cbMessages.size() + " messages since "
350 + DateFormat.getDateTimeInstance().format(dupCheckTime));
351 for (SmsCbMessage messageToCheck : cbMessages) {
352 // If messages are from different slots, then we only compare the message body.
353 if (message.getSlotIndex() != messageToCheck.getSlotIndex()) {
354 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
355 log("Duplicate message detected from different slot. " + message);
356 return true;
357 }
358 } else {
359 // Check serial number if message is from the same carrier.
360 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
361 // Not a dup. Check next one.
362 log("Serial number check. Not a dup. " + messageToCheck);
363 continue;
364 }
365
Jack Yu2003c522019-10-31 09:47:20 -0700366 // ETWS primary / secondary should be treated differently.
367 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
368 && message.getEtwsWarningInfo().isPrimary()
369 != messageToCheck.getEtwsWarningInfo().isPrimary()) {
Jack Yua1309272019-10-24 00:19:49 -0700370 // Not a dup. Check next one.
Jack Yu6f9a26e2019-11-07 23:10:02 -0800371 log("ETWS primary check. Not a dup. " + messageToCheck);
Jack Yua1309272019-10-24 00:19:49 -0700372 continue;
373 }
374
Jack Yu2003c522019-10-31 09:47:20 -0700375 // Check if the message category is different. Some carriers send cell broadcast
376 // messages on different techs (i.e. GSM / CDMA), so we need to compare service
377 // category cross techs.
378 if (message.getServiceCategory() != messageToCheck.getServiceCategory()
Jack Yu6f9a26e2019-11-07 23:10:02 -0800379 && !Objects.equals(mServiceCategoryCrossRATMap.get(
380 message.getServiceCategory()), messageToCheck.getServiceCategory())
381 && !Objects.equals(mServiceCategoryCrossRATMap.get(
382 messageToCheck.getServiceCategory()),
383 message.getServiceCategory())) {
Jack Yua1309272019-10-24 00:19:49 -0700384 // Not a dup. Check next one.
Jack Yu6f9a26e2019-11-07 23:10:02 -0800385 log("Service category check. Not a dup. " + messageToCheck);
Jack Yua1309272019-10-24 00:19:49 -0700386 continue;
387 }
388
389 // Compare message body if needed.
390 if (!compareMessageBody || TextUtils.equals(
391 message.getMessageBody(), messageToCheck.getMessageBody())) {
392 log("Duplicate message detected. " + message);
393 return true;
394 }
395 }
396 }
397
398 log("Not a duplicate message. " + message);
399 return false;
400 }
401
402 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700403 * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
404 * {@code location} is inside the {@code broadcastArea}.
405 * @param message the message need to geo-fencing check
406 * @param uri the message's uri
407 * @param broadcastArea the broadcast area of the message
408 * @param location current location
409 */
410 protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
411 LatLng location, int slotIndex) {
412
413 if (DBG) {
414 logd("Perform geo-fencing check for message identifier = "
415 + message.getServiceCategory()
416 + " serialNumber = " + message.getSerialNumber());
417 }
418
419 for (Geometry geo : broadcastArea) {
420 if (geo.contains(location)) {
421 broadcastMessage(message, uri, slotIndex);
422 return;
423 }
424 }
425
426 if (DBG) {
427 logd("Device location is outside the broadcast area "
428 + CbGeoUtils.encodeGeometriesToString(broadcastArea));
429 }
Jack Yu2f37c432019-11-04 21:28:24 -0800430
431 sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
Jordan Liuc872fad2019-10-11 11:42:03 -0700432 }
433
434 /**
435 * Request a single location update.
436 * @param callback a callback will be called when the location is available.
437 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
438 * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
439 */
440 protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
441 mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
442 }
443
444 /**
Jordan Liu4a2ff262019-10-21 12:30:56 -0700445 * Get the subscription ID for a phone ID, or INVALID_SUBSCRIPTION_ID if the phone does not
446 * have an active sub
447 * @param phoneId the phoneId to use
448 * @return the associated sub id
449 */
450 protected int getSubIdForPhone(int phoneId) {
451 SubscriptionManager subMan =
452 (SubscriptionManager) mContext.getSystemService(
453 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
454 int[] subIds = subMan.getSubscriptionIds(phoneId);
455 if (subIds != null) {
456 return subIds[0];
457 } else {
458 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
459 }
460 }
461
462 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700463 * Broadcast the {@code message} to the applications.
464 * @param message a message need to broadcast
465 * @param messageUri message's uri
466 */
467 protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
468 int slotIndex) {
469 String receiverPermission;
470 int appOp;
471 String msg;
472 Intent intent;
473 if (message.isEmergencyMessage()) {
474 msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
475 log(msg);
476 mLocalLog.log(msg);
Chen Xueca1f0a2019-11-06 20:53:24 -0800477 intent = new Intent(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED);
Jordan Liuc872fad2019-10-11 11:42:03 -0700478 //Emergency alerts need to be delivered with high priority
479 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
480 receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
481 appOp = AppOpsManager.OP_RECEIVE_EMERGECY_SMS;
482
483 intent.putExtra(EXTRA_MESSAGE, message);
Jordan Liu4a2ff262019-10-21 12:30:56 -0700484 int subId = getSubIdForPhone(slotIndex);
485 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
486 intent.putExtra("subscription", subId);
487 intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
488 }
489 intent.putExtra("phone", slotIndex);
490 intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
Jordan Liuc872fad2019-10-11 11:42:03 -0700491
Jordan Liu4a2ff262019-10-21 12:30:56 -0700492 if (IS_DEBUGGABLE) {
Jordan Liuc872fad2019-10-11 11:42:03 -0700493 // Send additional broadcast intent to the specified package. This is only for sl4a
494 // automation tests.
495 final String additionalPackage = Settings.Secure.getString(
496 mContext.getContentResolver(), CMAS_ADDITIONAL_BROADCAST_PKG);
497 if (additionalPackage != null) {
498 Intent additionalIntent = new Intent(intent);
499 additionalIntent.setPackage(additionalPackage);
500 mContext.sendOrderedBroadcastAsUser(additionalIntent, UserHandle.ALL,
501 receiverPermission, appOp, null, getHandler(), Activity.RESULT_OK,
502 null, null);
503 }
504 }
505
506 String[] pkgs = mContext.getResources().getStringArray(
507 com.android.internal.R.array.config_defaultCellBroadcastReceiverPkgs);
508 mReceiverCount.addAndGet(pkgs.length);
509 for (String pkg : pkgs) {
510 // Explicitly send the intent to all the configured cell broadcast receivers.
511 intent.setPackage(pkg);
512 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission,
Jordan Liu4a2ff262019-10-21 12:30:56 -0700513 appOp, null, mReceiver, getHandler(), Activity.RESULT_OK, null, null);
Jordan Liuc872fad2019-10-11 11:42:03 -0700514 }
515 } else {
516 msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
517 log(msg);
518 mLocalLog.log(msg);
519 intent = new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION);
520 // Send implicit intent since there are various 3rd party carrier apps listen to
521 // this intent.
522 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
523 receiverPermission = Manifest.permission.RECEIVE_SMS;
524 appOp = AppOpsManager.OP_RECEIVE_SMS;
525
526 intent.putExtra(EXTRA_MESSAGE, message);
527 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
528
529 mReceiverCount.incrementAndGet();
Jordan Liu4a2ff262019-10-21 12:30:56 -0700530 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission,
531 appOp, null, mReceiver, getHandler(), Activity.RESULT_OK, null, null);
Jordan Liuc872fad2019-10-11 11:42:03 -0700532 }
533
534 if (messageUri != null) {
535 ContentValues cv = new ContentValues();
536 cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
Chen Xu93fdfeb2019-10-21 22:56:37 -0700537 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
Jordan Liuc872fad2019-10-11 11:42:03 -0700538 CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
539 }
540 }
541
Jack Yua1309272019-10-24 00:19:49 -0700542 /**
543 * Get the device resource based on SIM
544 *
545 * @param subId Subscription index
546 *
547 * @return The resource
548 */
549 public @NonNull Resources getResources(int subId) {
550 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
551 || !SubscriptionManager.isValidSubscriptionId(subId)) {
552 return mContext.getResources();
553 }
554
555 if (mResourcesCache.containsKey(subId)) {
556 return mResourcesCache.get(subId);
557 }
558
559 Resources res = SubscriptionManager.getResourcesForSubId(mContext, subId);
560 mResourcesCache.put(subId, res);
561
562 return res;
563 }
564
Jordan Liuc872fad2019-10-11 11:42:03 -0700565 @Override
566 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
567 pw.println("CellBroadcastHandler:");
568 mLocalLog.dump(fd, pw, args);
569 pw.flush();
570 }
571
572 /** The callback interface of a location request. */
573 public interface LocationUpdateCallback {
574 /**
575 * Call when the location update is available.
576 * @param location a location in (latitude, longitude) format, or {@code null} if the
577 * location service is not available.
578 */
579 void onLocationUpdate(@Nullable LatLng location);
580 }
581
582 private static final class LocationRequester {
583 private static final String TAG = LocationRequester.class.getSimpleName();
584
585 /**
586 * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
587 * Most of the location request should be responded within 20 seconds.
588 */
589 private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 20;
590
591 /**
592 * Trigger this event when the {@link LocationManager} is not responded within the given
593 * time.
594 */
595 private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
596
597 /** Request a single location update. */
598 private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
599
600 /**
601 * Request location update from network or gps location provider. Network provider will be
602 * used if available, otherwise use the gps provider.
603 */
604 private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
605 LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
606
607 private final LocationManager mLocationManager;
608 private final Looper mLooper;
609 private final List<LocationUpdateCallback> mCallbacks;
610 private final Context mContext;
611 private Handler mLocationHandler;
612
613 LocationRequester(Context context, LocationManager locationManager, Looper looper) {
614 mLocationManager = locationManager;
615 mLooper = looper;
616 mCallbacks = new ArrayList<>();
617 mContext = context;
618 mLocationHandler = new LocationHandler(looper);
619 }
620
621 /**
622 * Request a single location update. If the location is not available, a callback with
623 * {@code null} location will be called immediately.
624 *
625 * @param callback a callback to the response when the location is available
626 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
627 * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
628 * called.
629 */
630 void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
631 int maximumWaitTimeSec) {
632 mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
633 0 /* arg2 */, callback).sendToTarget();
634 }
635
636 private void onLocationUpdate(@Nullable LatLng location) {
637 for (LocationUpdateCallback callback : mCallbacks) {
638 callback.onLocationUpdate(location);
639 }
640 mCallbacks.clear();
641 }
642
643 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
644 int maximumWaitTimeSec) {
645 if (DBG) Log.d(TAG, "requestLocationUpdate");
646 if (!isLocationServiceAvailable()) {
647 if (DBG) {
648 Log.d(TAG, "Can't request location update because of no location permission");
649 }
650 callback.onLocationUpdate(null);
651 return;
652 }
653
654 if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
655 if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
656 maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
657 }
658 mLocationHandler.sendMessageDelayed(
659 mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
660 maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
661 }
662
663 mCallbacks.add(callback);
664
665 for (String provider : LOCATION_PROVIDERS) {
666 if (mLocationManager.isProviderEnabled(provider)) {
667 mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper);
668 break;
669 }
670 }
671 }
672
673 private boolean isLocationServiceAvailable() {
674 if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
675 && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
676 for (String provider : LOCATION_PROVIDERS) {
677 if (mLocationManager.isProviderEnabled(provider)) return true;
678 }
679 return false;
680 }
681
682 private boolean hasPermission(String permission) {
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -0700683 return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
684 == PackageManager.PERMISSION_GRANTED;
Jordan Liuc872fad2019-10-11 11:42:03 -0700685 }
686
687 private final LocationListener mLocationListener = new LocationListener() {
688 @Override
689 public void onLocationChanged(Location location) {
690 mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
691 onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude()));
692 }
693
694 @Override
695 public void onStatusChanged(String provider, int status, Bundle extras) {}
696
697 @Override
698 public void onProviderEnabled(String provider) {}
699
700 @Override
701 public void onProviderDisabled(String provider) {}
702 };
703
704 private final class LocationHandler extends Handler {
705 LocationHandler(Looper looper) {
706 super(looper);
707 }
708
709 @Override
710 public void handleMessage(Message msg) {
711 switch (msg.what) {
712 case EVENT_LOCATION_REQUEST_TIMEOUT:
713 if (DBG) Log.d(TAG, "location request timeout");
714 onLocationUpdate(null);
715 break;
716 case EVENT_REQUEST_LOCATION_UPDATE:
717 requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
718 break;
719 default:
720 Log.e(TAG, "Unsupported message type " + msg.what);
721 }
722 }
723 }
724 }
725}