blob: 8966417f4be2de963e844f85482a56bfa19a3b41 [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;
40import android.os.Looper;
41import android.os.Message;
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -070042import android.os.Process;
Jack Yue7591d32019-11-07 22:59:46 -080043import android.os.SystemClock;
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;
Jordan Liu4a2ff262019-10-21 12:30:56 -070049import android.telephony.CbGeoUtils.Geometry;
50import android.telephony.CbGeoUtils.LatLng;
Jordan Liuc872fad2019-10-11 11:42:03 -070051import android.telephony.SmsCbMessage;
52import android.telephony.SubscriptionManager;
Jack Yu2003c522019-10-31 09:47:20 -070053import android.telephony.cdma.CdmaSmsCbProgramData;
Jack Yua1309272019-10-24 00:19:49 -070054import android.text.TextUtils;
Jordan Liuc872fad2019-10-11 11:42:03 -070055import android.text.format.DateUtils;
56import android.util.LocalLog;
57import android.util.Log;
58
Jack Yua1309272019-10-24 00:19:49 -070059import com.android.internal.annotations.VisibleForTesting;
Jordan Liuc872fad2019-10-11 11:42:03 -070060
61import java.io.FileDescriptor;
62import java.io.PrintWriter;
Jack Yua1309272019-10-24 00:19:49 -070063import java.text.DateFormat;
Jordan Liuc872fad2019-10-11 11:42:03 -070064import java.util.ArrayList;
65import java.util.Arrays;
Jack Yua1309272019-10-24 00:19:49 -070066import java.util.HashMap;
Jordan Liuc872fad2019-10-11 11:42:03 -070067import java.util.List;
Jack Yua1309272019-10-24 00:19:49 -070068import java.util.Map;
Jack Yu6f9a26e2019-11-07 23:10:02 -080069import java.util.Objects;
Jack Yu2003c522019-10-31 09:47:20 -070070import java.util.stream.Collectors;
71import java.util.stream.Stream;
Jordan Liuc872fad2019-10-11 11:42:03 -070072
73/**
74 * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
75 * completes and our result receiver is called.
76 */
77public class CellBroadcastHandler extends WakeLockStateMachine {
78 private static final String EXTRA_MESSAGE = "message";
79
Jack Yudc607602019-10-31 13:24:43 -070080 /**
81 * To disable cell broadcast duplicate detection for debugging purposes
82 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
83 * --ez enable false</code>
84 *
85 * To enable cell broadcast duplicate detection for debugging purposes
86 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
87 * --ez enable true</code>
88 */
89 private static final String ACTION_DUPLICATE_DETECTION =
90 "com.android.cellbroadcastservice.action.DUPLICATE_DETECTION";
91
92 /**
93 * The extra for cell broadcast duplicate detection enable/disable
94 */
95 private static final String EXTRA_ENABLE = "enable";
96
Jordan Liuc872fad2019-10-11 11:42:03 -070097 private final LocalLog mLocalLog = new LocalLog(100);
98
Jordan Liu26e820c2019-10-22 14:42:07 -070099 private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
100
Jordan Liuc872fad2019-10-11 11:42:03 -0700101 /** Uses to request the location update. */
102 public final LocationRequester mLocationRequester;
103
Jack Yua1309272019-10-24 00:19:49 -0700104 /** Timestamp of last airplane mode on */
Jack Yue7591d32019-11-07 22:59:46 -0800105 protected long mLastAirplaneModeTime = 0;
Jack Yua1309272019-10-24 00:19:49 -0700106
Jack Yu2003c522019-10-31 09:47:20 -0700107 /** Resource cache */
Jack Yua1309272019-10-24 00:19:49 -0700108 private final Map<Integer, Resources> mResourcesCache = new HashMap<>();
109
Jack Yudc607602019-10-31 13:24:43 -0700110 /** Whether performing duplicate detection or not. Note this is for debugging purposes only. */
111 private boolean mEnableDuplicateDetection = true;
112
Jack Yu2003c522019-10-31 09:47:20 -0700113 /**
114 * Service category equivalent map. The key is the GSM service category, the value is the CDMA
115 * service category.
116 */
117 private final Map<Integer, Integer> mServiceCategoryCrossRATMap;
118
Jordan Liuc872fad2019-10-11 11:42:03 -0700119 private CellBroadcastHandler(Context context) {
Jack Yue7591d32019-11-07 22:59:46 -0800120 this("CellBroadcastHandler", context, Looper.myLooper());
Jordan Liuc872fad2019-10-11 11:42:03 -0700121 }
122
Jack Yue7591d32019-11-07 22:59:46 -0800123 protected CellBroadcastHandler(String debugTag, Context context, Looper looper) {
124 super(debugTag, context, looper);
Jordan Liuc872fad2019-10-11 11:42:03 -0700125 mLocationRequester = new LocationRequester(
126 context,
127 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
128 getHandler().getLooper());
Jack Yua1309272019-10-24 00:19:49 -0700129
Jack Yu2003c522019-10-31 09:47:20 -0700130 // Adding GSM / CDMA service category mapping.
131 mServiceCategoryCrossRATMap = Stream.of(new Integer[][] {
132 // Presidential alert
133 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
134 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
135 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE,
136 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
137
138 // Extreme alert
139 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
140 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
141 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE,
142 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
143 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
144 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
145 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE,
146 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
147
148 // Severe alert
149 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
150 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
151 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE,
152 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
153 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
154 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
155 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE,
156 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
157 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED,
158 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
159 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE,
160 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
161 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
162 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
163 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE,
164 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
165 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
166 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
167 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE,
168 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
169 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
170 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
171 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE,
172 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
173
174 // Amber alert
175 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
176 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
177 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE,
178 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
179
180 // Monthly test alert
181 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
182 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
183 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE,
184 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
185 }).collect(Collectors.toMap(data -> data[0], data -> data[1]));
186
Jack Yudc607602019-10-31 13:24:43 -0700187 IntentFilter intentFilter = new IntentFilter();
188 intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
Jordan Liu4a2ff262019-10-21 12:30:56 -0700189 if (IS_DEBUGGABLE) {
Jack Yudc607602019-10-31 13:24:43 -0700190 intentFilter.addAction(ACTION_DUPLICATE_DETECTION);
191 }
Jack Yua1309272019-10-24 00:19:49 -0700192 mContext.registerReceiver(
193 new BroadcastReceiver() {
194 @Override
195 public void onReceive(Context context, Intent intent) {
Jack Yudc607602019-10-31 13:24:43 -0700196 switch (intent.getAction()) {
197 case Intent.ACTION_AIRPLANE_MODE_CHANGED:
198 boolean airplaneModeOn = intent.getBooleanExtra("state", false);
199 if (airplaneModeOn) {
200 mLastAirplaneModeTime = System.currentTimeMillis();
Jack Yu446d1742019-11-12 17:30:27 -0800201 log("Airplane mode on.");
Jack Yudc607602019-10-31 13:24:43 -0700202 }
203 break;
204 case ACTION_DUPLICATE_DETECTION:
205 mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE,
206 true);
207 break;
Jack Yua1309272019-10-24 00:19:49 -0700208 }
Jack Yudc607602019-10-31 13:24:43 -0700209
Jack Yua1309272019-10-24 00:19:49 -0700210 }
Jack Yudc607602019-10-31 13:24:43 -0700211 }, intentFilter);
Jordan Liuc872fad2019-10-11 11:42:03 -0700212 }
213
214 /**
215 * Create a new CellBroadcastHandler.
216 * @param context the context to use for dispatching Intents
217 * @return the new handler
218 */
219 public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
220 CellBroadcastHandler handler = new CellBroadcastHandler(context);
221 handler.start();
222 return handler;
223 }
224
225 /**
226 * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
227 * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
228 *
229 * @param message the message to process
230 * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
231 */
232 @Override
233 protected boolean handleSmsMessage(Message message) {
234 if (message.obj instanceof SmsCbMessage) {
Jack Yua1309272019-10-24 00:19:49 -0700235 if (!isDuplicate((SmsCbMessage) message.obj)) {
236 handleBroadcastSms((SmsCbMessage) message.obj);
237 return true;
238 }
239 return false;
Jordan Liuc872fad2019-10-11 11:42:03 -0700240 } else {
241 loge("handleMessage got object of type: " + message.obj.getClass().getName());
242 return false;
243 }
244 }
245
246 /**
247 * Dispatch a Cell Broadcast message to listeners.
248 * @param message the Cell Broadcast to broadcast
249 */
250 protected void handleBroadcastSms(SmsCbMessage message) {
251 int slotIndex = message.getSlotIndex();
Jordan Liuc872fad2019-10-11 11:42:03 -0700252
253 // TODO: Database inserting can be time consuming, therefore this should be changed to
254 // asynchronous.
255 ContentValues cv = message.getContentValues();
Chen Xu93fdfeb2019-10-21 22:56:37 -0700256 Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv);
Jordan Liuc872fad2019-10-11 11:42:03 -0700257
258 if (message.needGeoFencingCheck()) {
259 if (DBG) {
260 log("Request location update for geo-fencing. serialNumber = "
261 + message.getSerialNumber());
262 }
263
264 requestLocationUpdate(location -> {
265 if (location == null) {
266 // Broadcast the message directly if the location is not available.
267 broadcastMessage(message, uri, slotIndex);
268 } else {
269 performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
270 }
Jordan Liu4a2ff262019-10-21 12:30:56 -0700271 }, message.getMaximumWaitingDuration());
Jordan Liuc872fad2019-10-11 11:42:03 -0700272 } else {
273 if (DBG) {
274 log("Broadcast the message directly because no geo-fencing required, "
275 + "serialNumber = " + message.getSerialNumber()
276 + " needGeoFencing = " + message.needGeoFencingCheck());
277 }
278 broadcastMessage(message, uri, slotIndex);
279 }
280 }
281
282 /**
Jack Yua1309272019-10-24 00:19:49 -0700283 * Check if the message is a duplicate
284 *
285 * @param message Cell broadcast message
286 * @return {@code true} if this message is a duplicate
287 */
288 @VisibleForTesting
289 public boolean isDuplicate(SmsCbMessage message) {
Jack Yudc607602019-10-31 13:24:43 -0700290 if (!mEnableDuplicateDetection) {
291 log("Duplicate detection was disabled for debugging purposes.");
292 return false;
293 }
294
Jack Yua1309272019-10-24 00:19:49 -0700295 // Find the cell broadcast message identify by the message identifier and serial number
296 // and is not broadcasted.
297 String where = CellBroadcasts.RECEIVED_TIME + ">?";
298
299 int slotIndex = message.getSlotIndex();
300 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
301 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
302 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
303 Resources res;
304 if (subIds != null) {
305 res = getResources(subIds[0]);
306 } else {
307 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
308 }
309
310 // Only consider cell broadcast messages received within certain period.
311 // By default it's 24 hours.
312 long expirationDuration = res.getInteger(R.integer.message_expiration_time);
313 long dupCheckTime = System.currentTimeMillis() - expirationDuration;
314
Jack Yue7591d32019-11-07 22:59:46 -0800315 // Some carriers require reset duplication detection after airplane mode or reboot.
316 if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) {
Jack Yua1309272019-10-24 00:19:49 -0700317 dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime);
Jack Yue7591d32019-11-07 22:59:46 -0800318 dupCheckTime = Long.max(dupCheckTime,
319 System.currentTimeMillis() - SystemClock.elapsedRealtime());
Jack Yua1309272019-10-24 00:19:49 -0700320 }
321
322 List<SmsCbMessage> cbMessages = new ArrayList<>();
323
324 try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI,
Jordan Liud3eeb402019-11-15 11:13:59 -0800325 CellBroadcastProvider.QUERY_COLUMNS,
Jack Yua1309272019-10-24 00:19:49 -0700326 where,
327 new String[] {Long.toString(dupCheckTime)},
328 null)) {
329 if (cursor != null) {
330 while (cursor.moveToNext()) {
331 cbMessages.add(SmsCbMessage.createFromCursor(cursor));
332 }
333 }
334 }
335
336 boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body);
337
338 log("Found " + cbMessages.size() + " messages since "
339 + DateFormat.getDateTimeInstance().format(dupCheckTime));
340 for (SmsCbMessage messageToCheck : cbMessages) {
341 // If messages are from different slots, then we only compare the message body.
342 if (message.getSlotIndex() != messageToCheck.getSlotIndex()) {
343 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
344 log("Duplicate message detected from different slot. " + message);
345 return true;
346 }
347 } else {
348 // Check serial number if message is from the same carrier.
349 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
350 // Not a dup. Check next one.
351 log("Serial number check. Not a dup. " + messageToCheck);
352 continue;
353 }
354
Jack Yu2003c522019-10-31 09:47:20 -0700355 // ETWS primary / secondary should be treated differently.
356 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
357 && message.getEtwsWarningInfo().isPrimary()
358 != messageToCheck.getEtwsWarningInfo().isPrimary()) {
Jack Yua1309272019-10-24 00:19:49 -0700359 // Not a dup. Check next one.
Jack Yu6f9a26e2019-11-07 23:10:02 -0800360 log("ETWS primary check. Not a dup. " + messageToCheck);
Jack Yua1309272019-10-24 00:19:49 -0700361 continue;
362 }
363
Jack Yu2003c522019-10-31 09:47:20 -0700364 // Check if the message category is different. Some carriers send cell broadcast
365 // messages on different techs (i.e. GSM / CDMA), so we need to compare service
366 // category cross techs.
367 if (message.getServiceCategory() != messageToCheck.getServiceCategory()
Jack Yu6f9a26e2019-11-07 23:10:02 -0800368 && !Objects.equals(mServiceCategoryCrossRATMap.get(
369 message.getServiceCategory()), messageToCheck.getServiceCategory())
370 && !Objects.equals(mServiceCategoryCrossRATMap.get(
371 messageToCheck.getServiceCategory()),
372 message.getServiceCategory())) {
Jack Yua1309272019-10-24 00:19:49 -0700373 // Not a dup. Check next one.
Jack Yu6f9a26e2019-11-07 23:10:02 -0800374 log("Service category check. Not a dup. " + messageToCheck);
Jack Yua1309272019-10-24 00:19:49 -0700375 continue;
376 }
377
378 // Compare message body if needed.
379 if (!compareMessageBody || TextUtils.equals(
380 message.getMessageBody(), messageToCheck.getMessageBody())) {
381 log("Duplicate message detected. " + message);
382 return true;
383 }
384 }
385 }
386
387 log("Not a duplicate message. " + message);
388 return false;
389 }
390
391 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700392 * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
393 * {@code location} is inside the {@code broadcastArea}.
394 * @param message the message need to geo-fencing check
395 * @param uri the message's uri
396 * @param broadcastArea the broadcast area of the message
397 * @param location current location
398 */
399 protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
400 LatLng location, int slotIndex) {
401
402 if (DBG) {
403 logd("Perform geo-fencing check for message identifier = "
404 + message.getServiceCategory()
405 + " serialNumber = " + message.getSerialNumber());
406 }
407
408 for (Geometry geo : broadcastArea) {
409 if (geo.contains(location)) {
410 broadcastMessage(message, uri, slotIndex);
411 return;
412 }
413 }
414
415 if (DBG) {
416 logd("Device location is outside the broadcast area "
417 + CbGeoUtils.encodeGeometriesToString(broadcastArea));
418 }
Jack Yu2f37c432019-11-04 21:28:24 -0800419
420 sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
Jordan Liuc872fad2019-10-11 11:42:03 -0700421 }
422
423 /**
424 * Request a single location update.
425 * @param callback a callback will be called when the location is available.
426 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
427 * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
428 */
429 protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
430 mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
431 }
432
433 /**
Jordan Liu4a2ff262019-10-21 12:30:56 -0700434 * Get the subscription ID for a phone ID, or INVALID_SUBSCRIPTION_ID if the phone does not
435 * have an active sub
436 * @param phoneId the phoneId to use
437 * @return the associated sub id
438 */
439 protected int getSubIdForPhone(int phoneId) {
440 SubscriptionManager subMan =
441 (SubscriptionManager) mContext.getSystemService(
442 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
443 int[] subIds = subMan.getSubscriptionIds(phoneId);
444 if (subIds != null) {
445 return subIds[0];
446 } else {
447 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
448 }
449 }
450
451 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700452 * Broadcast the {@code message} to the applications.
453 * @param message a message need to broadcast
454 * @param messageUri message's uri
455 */
456 protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
457 int slotIndex) {
458 String receiverPermission;
Jordan Liud4c7af32019-11-06 10:12:32 -0800459 String appOp;
Jordan Liuc872fad2019-10-11 11:42:03 -0700460 String msg;
461 Intent intent;
462 if (message.isEmergencyMessage()) {
463 msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
464 log(msg);
465 mLocalLog.log(msg);
Chen Xueca1f0a2019-11-06 20:53:24 -0800466 intent = new Intent(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED);
Jordan Liuc872fad2019-10-11 11:42:03 -0700467 //Emergency alerts need to be delivered with high priority
468 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
469 receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
Jordan Liud4c7af32019-11-06 10:12:32 -0800470 appOp = AppOpsManager.OPSTR_RECEIVE_EMERGENCY_BROADCAST;
Jordan Liuc872fad2019-10-11 11:42:03 -0700471
472 intent.putExtra(EXTRA_MESSAGE, message);
Jordan Liu4a2ff262019-10-21 12:30:56 -0700473 int subId = getSubIdForPhone(slotIndex);
474 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
475 intent.putExtra("subscription", subId);
476 intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
477 }
478 intent.putExtra("phone", slotIndex);
479 intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
Jordan Liuc872fad2019-10-11 11:42:03 -0700480
Jordan Liu4a2ff262019-10-21 12:30:56 -0700481 if (IS_DEBUGGABLE) {
Jordan Liuc872fad2019-10-11 11:42:03 -0700482 // Send additional broadcast intent to the specified package. This is only for sl4a
483 // automation tests.
484 final String additionalPackage = Settings.Secure.getString(
485 mContext.getContentResolver(), CMAS_ADDITIONAL_BROADCAST_PKG);
486 if (additionalPackage != null) {
487 Intent additionalIntent = new Intent(intent);
488 additionalIntent.setPackage(additionalPackage);
Jordan Liud4c7af32019-11-06 10:12:32 -0800489 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
490 additionalIntent, receiverPermission, appOp, null, getHandler(),
491 Activity.RESULT_OK, null, null);
Jordan Liuc872fad2019-10-11 11:42:03 -0700492 }
493 }
494
495 String[] pkgs = mContext.getResources().getStringArray(
496 com.android.internal.R.array.config_defaultCellBroadcastReceiverPkgs);
497 mReceiverCount.addAndGet(pkgs.length);
498 for (String pkg : pkgs) {
499 // Explicitly send the intent to all the configured cell broadcast receivers.
500 intent.setPackage(pkg);
Jordan Liud4c7af32019-11-06 10:12:32 -0800501 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
502 intent, receiverPermission, appOp, null, getHandler(),
503 Activity.RESULT_OK, null, null);
Jordan Liuc872fad2019-10-11 11:42:03 -0700504 }
505 } else {
506 msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
507 log(msg);
508 mLocalLog.log(msg);
509 intent = new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION);
510 // Send implicit intent since there are various 3rd party carrier apps listen to
511 // this intent.
512 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
513 receiverPermission = Manifest.permission.RECEIVE_SMS;
Jordan Liud4c7af32019-11-06 10:12:32 -0800514 appOp = AppOpsManager.OPSTR_RECEIVE_SMS;
Jordan Liuc872fad2019-10-11 11:42:03 -0700515
516 intent.putExtra(EXTRA_MESSAGE, message);
517 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
518
519 mReceiverCount.incrementAndGet();
Jordan Liud4c7af32019-11-06 10:12:32 -0800520 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
521 intent, receiverPermission, appOp, mReceiver, getHandler(),
522 Activity.RESULT_OK, null, null);
Jordan Liuc872fad2019-10-11 11:42:03 -0700523 }
524
525 if (messageUri != null) {
526 ContentValues cv = new ContentValues();
527 cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
Chen Xu93fdfeb2019-10-21 22:56:37 -0700528 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
Jordan Liuc872fad2019-10-11 11:42:03 -0700529 CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
530 }
531 }
532
Jack Yua1309272019-10-24 00:19:49 -0700533 /**
534 * Get the device resource based on SIM
535 *
536 * @param subId Subscription index
537 *
538 * @return The resource
539 */
540 public @NonNull Resources getResources(int subId) {
541 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
542 || !SubscriptionManager.isValidSubscriptionId(subId)) {
543 return mContext.getResources();
544 }
545
546 if (mResourcesCache.containsKey(subId)) {
547 return mResourcesCache.get(subId);
548 }
549
550 Resources res = SubscriptionManager.getResourcesForSubId(mContext, subId);
551 mResourcesCache.put(subId, res);
552
553 return res;
554 }
555
Jordan Liuc872fad2019-10-11 11:42:03 -0700556 @Override
557 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
558 pw.println("CellBroadcastHandler:");
559 mLocalLog.dump(fd, pw, args);
560 pw.flush();
561 }
562
563 /** The callback interface of a location request. */
564 public interface LocationUpdateCallback {
565 /**
566 * Call when the location update is available.
567 * @param location a location in (latitude, longitude) format, or {@code null} if the
568 * location service is not available.
569 */
570 void onLocationUpdate(@Nullable LatLng location);
571 }
572
573 private static final class LocationRequester {
574 private static final String TAG = LocationRequester.class.getSimpleName();
575
576 /**
577 * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
578 * Most of the location request should be responded within 20 seconds.
579 */
580 private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 20;
581
582 /**
583 * Trigger this event when the {@link LocationManager} is not responded within the given
584 * time.
585 */
586 private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
587
588 /** Request a single location update. */
589 private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
590
591 /**
592 * Request location update from network or gps location provider. Network provider will be
593 * used if available, otherwise use the gps provider.
594 */
595 private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
596 LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
597
598 private final LocationManager mLocationManager;
599 private final Looper mLooper;
600 private final List<LocationUpdateCallback> mCallbacks;
601 private final Context mContext;
602 private Handler mLocationHandler;
603
604 LocationRequester(Context context, LocationManager locationManager, Looper looper) {
605 mLocationManager = locationManager;
606 mLooper = looper;
607 mCallbacks = new ArrayList<>();
608 mContext = context;
609 mLocationHandler = new LocationHandler(looper);
610 }
611
612 /**
613 * Request a single location update. If the location is not available, a callback with
614 * {@code null} location will be called immediately.
615 *
616 * @param callback a callback to the response when the location is available
617 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
618 * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
619 * called.
620 */
621 void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
622 int maximumWaitTimeSec) {
623 mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
624 0 /* arg2 */, callback).sendToTarget();
625 }
626
627 private void onLocationUpdate(@Nullable LatLng location) {
628 for (LocationUpdateCallback callback : mCallbacks) {
629 callback.onLocationUpdate(location);
630 }
631 mCallbacks.clear();
632 }
633
634 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
635 int maximumWaitTimeSec) {
636 if (DBG) Log.d(TAG, "requestLocationUpdate");
637 if (!isLocationServiceAvailable()) {
638 if (DBG) {
639 Log.d(TAG, "Can't request location update because of no location permission");
640 }
641 callback.onLocationUpdate(null);
642 return;
643 }
644
645 if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
646 if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
647 maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
648 }
649 mLocationHandler.sendMessageDelayed(
650 mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
651 maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
652 }
653
654 mCallbacks.add(callback);
655
656 for (String provider : LOCATION_PROVIDERS) {
657 if (mLocationManager.isProviderEnabled(provider)) {
658 mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper);
659 break;
660 }
661 }
662 }
663
664 private boolean isLocationServiceAvailable() {
665 if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
666 && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
667 for (String provider : LOCATION_PROVIDERS) {
668 if (mLocationManager.isProviderEnabled(provider)) return true;
669 }
670 return false;
671 }
672
673 private boolean hasPermission(String permission) {
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -0700674 return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
675 == PackageManager.PERMISSION_GRANTED;
Jordan Liuc872fad2019-10-11 11:42:03 -0700676 }
677
678 private final LocationListener mLocationListener = new LocationListener() {
679 @Override
680 public void onLocationChanged(Location location) {
681 mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
682 onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude()));
683 }
684
685 @Override
686 public void onStatusChanged(String provider, int status, Bundle extras) {}
687
688 @Override
689 public void onProviderEnabled(String provider) {}
690
691 @Override
692 public void onProviderDisabled(String provider) {}
693 };
694
695 private final class LocationHandler extends Handler {
696 LocationHandler(Looper looper) {
697 super(looper);
698 }
699
700 @Override
701 public void handleMessage(Message msg) {
702 switch (msg.what) {
703 case EVENT_LOCATION_REQUEST_TIMEOUT:
704 if (DBG) Log.d(TAG, "location request timeout");
705 onLocationUpdate(null);
706 break;
707 case EVENT_REQUEST_LOCATION_UPDATE:
708 requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
709 break;
710 default:
711 Log.e(TAG, "Unsupported message type " + msg.what);
712 }
713 }
714 }
715 }
716}