blob: 1615f3da8af2ecb055af02e4eae4db8ab2939192 [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 android.Manifest;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.app.Activity;
23import android.app.AppOpsManager;
Jack Yua1309272019-10-24 00:19:49 -070024import android.content.BroadcastReceiver;
Jordan Liuc872fad2019-10-11 11:42:03 -070025import android.content.ContentValues;
26import android.content.Context;
27import android.content.Intent;
Jack Yua1309272019-10-24 00:19:49 -070028import android.content.IntentFilter;
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -070029import android.content.pm.PackageManager;
Jack Yua1309272019-10-24 00:19:49 -070030import android.content.res.Resources;
31import android.database.Cursor;
Jordan Liuc872fad2019-10-11 11:42:03 -070032import android.location.Location;
33import android.location.LocationListener;
34import android.location.LocationManager;
35import android.net.Uri;
Jordan Liuc872fad2019-10-11 11:42:03 -070036import android.os.Bundle;
37import android.os.Handler;
38import android.os.Looper;
39import android.os.Message;
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -070040import android.os.Process;
Jack Yue7591d32019-11-07 22:59:46 -080041import android.os.SystemClock;
Jordan Liu26e820c2019-10-22 14:42:07 -070042import android.os.SystemProperties;
Jordan Liuc872fad2019-10-11 11:42:03 -070043import android.os.UserHandle;
Jordan Liuc872fad2019-10-11 11:42:03 -070044import android.provider.Telephony;
45import android.provider.Telephony.CellBroadcasts;
Jordan Liu4a2ff262019-10-21 12:30:56 -070046import android.telephony.CbGeoUtils.Geometry;
47import android.telephony.CbGeoUtils.LatLng;
Jordan Liuc872fad2019-10-11 11:42:03 -070048import android.telephony.SmsCbMessage;
49import android.telephony.SubscriptionManager;
Jack Yu2003c522019-10-31 09:47:20 -070050import android.telephony.cdma.CdmaSmsCbProgramData;
Jack Yua1309272019-10-24 00:19:49 -070051import android.text.TextUtils;
Jordan Liuc872fad2019-10-11 11:42:03 -070052import android.text.format.DateUtils;
53import android.util.LocalLog;
54import android.util.Log;
55
Jack Yua1309272019-10-24 00:19:49 -070056import com.android.internal.annotations.VisibleForTesting;
Jordan Liuc872fad2019-10-11 11:42:03 -070057
58import java.io.FileDescriptor;
59import java.io.PrintWriter;
Jack Yua1309272019-10-24 00:19:49 -070060import java.text.DateFormat;
Jordan Liuc872fad2019-10-11 11:42:03 -070061import java.util.ArrayList;
62import java.util.Arrays;
Jack Yua1309272019-10-24 00:19:49 -070063import java.util.HashMap;
Jordan Liuc872fad2019-10-11 11:42:03 -070064import java.util.List;
Jack Yua1309272019-10-24 00:19:49 -070065import java.util.Map;
Jack Yu6f9a26e2019-11-07 23:10:02 -080066import java.util.Objects;
Jack Yu2003c522019-10-31 09:47:20 -070067import java.util.stream.Collectors;
68import java.util.stream.Stream;
Jordan Liuc872fad2019-10-11 11:42:03 -070069
70/**
71 * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
72 * completes and our result receiver is called.
73 */
74public class CellBroadcastHandler extends WakeLockStateMachine {
75 private static final String EXTRA_MESSAGE = "message";
76
Jack Yudc607602019-10-31 13:24:43 -070077 /**
78 * To disable cell broadcast duplicate detection for debugging purposes
79 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
80 * --ez enable false</code>
81 *
82 * To enable cell broadcast duplicate detection for debugging purposes
83 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
84 * --ez enable true</code>
85 */
86 private static final String ACTION_DUPLICATE_DETECTION =
87 "com.android.cellbroadcastservice.action.DUPLICATE_DETECTION";
88
89 /**
90 * The extra for cell broadcast duplicate detection enable/disable
91 */
92 private static final String EXTRA_ENABLE = "enable";
93
Jordan Liuc872fad2019-10-11 11:42:03 -070094 private final LocalLog mLocalLog = new LocalLog(100);
95
Jordan Liu26e820c2019-10-22 14:42:07 -070096 private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
97
Jordan Liuc872fad2019-10-11 11:42:03 -070098 /** Uses to request the location update. */
99 public final LocationRequester mLocationRequester;
100
Jack Yua1309272019-10-24 00:19:49 -0700101 /** Timestamp of last airplane mode on */
Jack Yue7591d32019-11-07 22:59:46 -0800102 protected long mLastAirplaneModeTime = 0;
Jack Yua1309272019-10-24 00:19:49 -0700103
Jack Yu2003c522019-10-31 09:47:20 -0700104 /** Resource cache */
Jack Yua1309272019-10-24 00:19:49 -0700105 private final Map<Integer, Resources> mResourcesCache = new HashMap<>();
106
Jack Yudc607602019-10-31 13:24:43 -0700107 /** Whether performing duplicate detection or not. Note this is for debugging purposes only. */
108 private boolean mEnableDuplicateDetection = true;
109
Jack Yu2003c522019-10-31 09:47:20 -0700110 /**
111 * Service category equivalent map. The key is the GSM service category, the value is the CDMA
112 * service category.
113 */
114 private final Map<Integer, Integer> mServiceCategoryCrossRATMap;
115
Jordan Liuc872fad2019-10-11 11:42:03 -0700116 private CellBroadcastHandler(Context context) {
Jack Yue7591d32019-11-07 22:59:46 -0800117 this("CellBroadcastHandler", context, Looper.myLooper());
Jordan Liuc872fad2019-10-11 11:42:03 -0700118 }
119
Jordan Liuf24590f2019-11-14 14:23:49 -0800120 @VisibleForTesting
121 public CellBroadcastHandler(String debugTag, Context context, Looper looper) {
Jack Yue7591d32019-11-07 22:59:46 -0800122 super(debugTag, context, looper);
Jordan Liuc872fad2019-10-11 11:42:03 -0700123 mLocationRequester = new LocationRequester(
124 context,
125 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
126 getHandler().getLooper());
Jack Yua1309272019-10-24 00:19:49 -0700127
Jack Yu2003c522019-10-31 09:47:20 -0700128 // Adding GSM / CDMA service category mapping.
129 mServiceCategoryCrossRATMap = Stream.of(new Integer[][] {
130 // Presidential alert
131 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
132 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
133 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE,
134 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
135
136 // Extreme alert
137 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
138 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
139 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE,
140 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
141 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
142 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
143 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE,
144 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
145
146 // Severe alert
147 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
148 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
149 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE,
150 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
151 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
152 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
153 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE,
154 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
155 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED,
156 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
157 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE,
158 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
159 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
160 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
161 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE,
162 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
163 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
164 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
165 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE,
166 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
167 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
168 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
169 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE,
170 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
171
172 // Amber alert
173 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
174 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
175 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE,
176 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
177
178 // Monthly test alert
179 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
180 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
181 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE,
182 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
183 }).collect(Collectors.toMap(data -> data[0], data -> data[1]));
184
Jack Yudc607602019-10-31 13:24:43 -0700185 IntentFilter intentFilter = new IntentFilter();
186 intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
Jordan Liu4a2ff262019-10-21 12:30:56 -0700187 if (IS_DEBUGGABLE) {
Jack Yudc607602019-10-31 13:24:43 -0700188 intentFilter.addAction(ACTION_DUPLICATE_DETECTION);
189 }
Jack Yua1309272019-10-24 00:19:49 -0700190 mContext.registerReceiver(
191 new BroadcastReceiver() {
192 @Override
193 public void onReceive(Context context, Intent intent) {
Jack Yudc607602019-10-31 13:24:43 -0700194 switch (intent.getAction()) {
195 case Intent.ACTION_AIRPLANE_MODE_CHANGED:
196 boolean airplaneModeOn = intent.getBooleanExtra("state", false);
197 if (airplaneModeOn) {
198 mLastAirplaneModeTime = System.currentTimeMillis();
Jack Yu446d1742019-11-12 17:30:27 -0800199 log("Airplane mode on.");
Jack Yudc607602019-10-31 13:24:43 -0700200 }
201 break;
202 case ACTION_DUPLICATE_DETECTION:
203 mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE,
204 true);
205 break;
Jack Yua1309272019-10-24 00:19:49 -0700206 }
Jack Yudc607602019-10-31 13:24:43 -0700207
Jack Yua1309272019-10-24 00:19:49 -0700208 }
Jack Yudc607602019-10-31 13:24:43 -0700209 }, intentFilter);
Jordan Liuc872fad2019-10-11 11:42:03 -0700210 }
211
212 /**
213 * Create a new CellBroadcastHandler.
214 * @param context the context to use for dispatching Intents
215 * @return the new handler
216 */
217 public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
218 CellBroadcastHandler handler = new CellBroadcastHandler(context);
219 handler.start();
220 return handler;
221 }
222
223 /**
224 * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
225 * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
226 *
227 * @param message the message to process
228 * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
229 */
230 @Override
231 protected boolean handleSmsMessage(Message message) {
232 if (message.obj instanceof SmsCbMessage) {
Jack Yua1309272019-10-24 00:19:49 -0700233 if (!isDuplicate((SmsCbMessage) message.obj)) {
234 handleBroadcastSms((SmsCbMessage) message.obj);
235 return true;
236 }
237 return false;
Jordan Liuc872fad2019-10-11 11:42:03 -0700238 } else {
239 loge("handleMessage got object of type: " + message.obj.getClass().getName());
240 return false;
241 }
242 }
243
244 /**
245 * Dispatch a Cell Broadcast message to listeners.
246 * @param message the Cell Broadcast to broadcast
247 */
248 protected void handleBroadcastSms(SmsCbMessage message) {
249 int slotIndex = message.getSlotIndex();
Jordan Liuc872fad2019-10-11 11:42:03 -0700250
251 // TODO: Database inserting can be time consuming, therefore this should be changed to
252 // asynchronous.
253 ContentValues cv = message.getContentValues();
Chen Xu93fdfeb2019-10-21 22:56:37 -0700254 Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv);
Jordan Liuc872fad2019-10-11 11:42:03 -0700255
256 if (message.needGeoFencingCheck()) {
257 if (DBG) {
258 log("Request location update for geo-fencing. serialNumber = "
259 + message.getSerialNumber());
260 }
261
262 requestLocationUpdate(location -> {
263 if (location == null) {
264 // Broadcast the message directly if the location is not available.
265 broadcastMessage(message, uri, slotIndex);
266 } else {
267 performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
268 }
Jordan Liu4a2ff262019-10-21 12:30:56 -0700269 }, message.getMaximumWaitingDuration());
Jordan Liuc872fad2019-10-11 11:42:03 -0700270 } else {
271 if (DBG) {
272 log("Broadcast the message directly because no geo-fencing required, "
273 + "serialNumber = " + message.getSerialNumber()
274 + " needGeoFencing = " + message.needGeoFencingCheck());
275 }
276 broadcastMessage(message, uri, slotIndex);
277 }
278 }
279
280 /**
Jack Yua1309272019-10-24 00:19:49 -0700281 * Check if the message is a duplicate
282 *
283 * @param message Cell broadcast message
284 * @return {@code true} if this message is a duplicate
285 */
286 @VisibleForTesting
287 public boolean isDuplicate(SmsCbMessage message) {
Jack Yudc607602019-10-31 13:24:43 -0700288 if (!mEnableDuplicateDetection) {
289 log("Duplicate detection was disabled for debugging purposes.");
290 return false;
291 }
292
Jack Yua1309272019-10-24 00:19:49 -0700293 // Find the cell broadcast message identify by the message identifier and serial number
294 // and is not broadcasted.
295 String where = CellBroadcasts.RECEIVED_TIME + ">?";
296
297 int slotIndex = message.getSlotIndex();
298 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
299 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
300 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
301 Resources res;
302 if (subIds != null) {
303 res = getResources(subIds[0]);
304 } else {
305 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
306 }
307
308 // Only consider cell broadcast messages received within certain period.
309 // By default it's 24 hours.
310 long expirationDuration = res.getInteger(R.integer.message_expiration_time);
311 long dupCheckTime = System.currentTimeMillis() - expirationDuration;
312
Jack Yue7591d32019-11-07 22:59:46 -0800313 // Some carriers require reset duplication detection after airplane mode or reboot.
314 if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) {
Jack Yua1309272019-10-24 00:19:49 -0700315 dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime);
Jack Yue7591d32019-11-07 22:59:46 -0800316 dupCheckTime = Long.max(dupCheckTime,
317 System.currentTimeMillis() - SystemClock.elapsedRealtime());
Jack Yua1309272019-10-24 00:19:49 -0700318 }
319
320 List<SmsCbMessage> cbMessages = new ArrayList<>();
321
322 try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI,
Jordan Liud3eeb402019-11-15 11:13:59 -0800323 CellBroadcastProvider.QUERY_COLUMNS,
Jack Yua1309272019-10-24 00:19:49 -0700324 where,
325 new String[] {Long.toString(dupCheckTime)},
326 null)) {
327 if (cursor != null) {
328 while (cursor.moveToNext()) {
329 cbMessages.add(SmsCbMessage.createFromCursor(cursor));
330 }
331 }
332 }
333
334 boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body);
335
336 log("Found " + cbMessages.size() + " messages since "
337 + DateFormat.getDateTimeInstance().format(dupCheckTime));
338 for (SmsCbMessage messageToCheck : cbMessages) {
339 // If messages are from different slots, then we only compare the message body.
340 if (message.getSlotIndex() != messageToCheck.getSlotIndex()) {
341 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
342 log("Duplicate message detected from different slot. " + message);
343 return true;
344 }
345 } else {
346 // Check serial number if message is from the same carrier.
347 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
348 // Not a dup. Check next one.
349 log("Serial number check. Not a dup. " + messageToCheck);
350 continue;
351 }
352
Jack Yu2003c522019-10-31 09:47:20 -0700353 // ETWS primary / secondary should be treated differently.
354 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
355 && message.getEtwsWarningInfo().isPrimary()
356 != messageToCheck.getEtwsWarningInfo().isPrimary()) {
Jack Yua1309272019-10-24 00:19:49 -0700357 // Not a dup. Check next one.
Jack Yu6f9a26e2019-11-07 23:10:02 -0800358 log("ETWS primary check. Not a dup. " + messageToCheck);
Jack Yua1309272019-10-24 00:19:49 -0700359 continue;
360 }
361
Jack Yu2003c522019-10-31 09:47:20 -0700362 // Check if the message category is different. Some carriers send cell broadcast
363 // messages on different techs (i.e. GSM / CDMA), so we need to compare service
364 // category cross techs.
365 if (message.getServiceCategory() != messageToCheck.getServiceCategory()
Jack Yu6f9a26e2019-11-07 23:10:02 -0800366 && !Objects.equals(mServiceCategoryCrossRATMap.get(
367 message.getServiceCategory()), messageToCheck.getServiceCategory())
368 && !Objects.equals(mServiceCategoryCrossRATMap.get(
369 messageToCheck.getServiceCategory()),
370 message.getServiceCategory())) {
Jack Yua1309272019-10-24 00:19:49 -0700371 // Not a dup. Check next one.
Jack Yu6f9a26e2019-11-07 23:10:02 -0800372 log("Service category check. Not a dup. " + messageToCheck);
Jack Yua1309272019-10-24 00:19:49 -0700373 continue;
374 }
375
376 // Compare message body if needed.
377 if (!compareMessageBody || TextUtils.equals(
378 message.getMessageBody(), messageToCheck.getMessageBody())) {
379 log("Duplicate message detected. " + message);
380 return true;
381 }
382 }
383 }
384
385 log("Not a duplicate message. " + message);
386 return false;
387 }
388
389 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700390 * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
391 * {@code location} is inside the {@code broadcastArea}.
392 * @param message the message need to geo-fencing check
393 * @param uri the message's uri
394 * @param broadcastArea the broadcast area of the message
395 * @param location current location
396 */
397 protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
398 LatLng location, int slotIndex) {
399
400 if (DBG) {
401 logd("Perform geo-fencing check for message identifier = "
402 + message.getServiceCategory()
403 + " serialNumber = " + message.getSerialNumber());
404 }
405
406 for (Geometry geo : broadcastArea) {
407 if (geo.contains(location)) {
408 broadcastMessage(message, uri, slotIndex);
409 return;
410 }
411 }
412
413 if (DBG) {
414 logd("Device location is outside the broadcast area "
415 + CbGeoUtils.encodeGeometriesToString(broadcastArea));
416 }
Jack Yu2f37c432019-11-04 21:28:24 -0800417
418 sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
Jordan Liuc872fad2019-10-11 11:42:03 -0700419 }
420
421 /**
422 * Request a single location update.
423 * @param callback a callback will be called when the location is available.
424 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
425 * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
426 */
427 protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
428 mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
429 }
430
431 /**
Jordan Liu4a2ff262019-10-21 12:30:56 -0700432 * Get the subscription ID for a phone ID, or INVALID_SUBSCRIPTION_ID if the phone does not
433 * have an active sub
434 * @param phoneId the phoneId to use
435 * @return the associated sub id
436 */
Jordan Liucdfc9f32019-11-21 11:59:07 -0800437 protected static int getSubIdForPhone(Context context, int phoneId) {
Jordan Liu4a2ff262019-10-21 12:30:56 -0700438 SubscriptionManager subMan =
Jordan Liucdfc9f32019-11-21 11:59:07 -0800439 (SubscriptionManager) context.getSystemService(
Jordan Liu4a2ff262019-10-21 12:30:56 -0700440 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
441 int[] subIds = subMan.getSubscriptionIds(phoneId);
442 if (subIds != null) {
443 return subIds[0];
444 } else {
445 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
446 }
447 }
448
449 /**
Jordan Liucdfc9f32019-11-21 11:59:07 -0800450 * Put the phone ID and sub ID into an intent as extras.
451 */
452 public static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) {
453 int subId = getSubIdForPhone(context, phoneId);
454 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
455 intent.putExtra("subscription", subId);
456 intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
457 }
458 intent.putExtra("phone", phoneId);
459 intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
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;
Jordan Liuc4c3b282019-11-22 11:40:05 -0800470 String appOp;
Jordan Liuc872fad2019-10-11 11:42:03 -0700471 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;
Jordan Liuc4c3b282019-11-22 11:40:05 -0800481 appOp = AppOpsManager.OPSTR_RECEIVE_EMERGENCY_BROADCAST;
Jordan Liuc872fad2019-10-11 11:42:03 -0700482
483 intent.putExtra(EXTRA_MESSAGE, message);
Jordan Liucdfc9f32019-11-21 11:59:07 -0800484 putPhoneIdAndSubIdExtra(mContext, intent, slotIndex);
Jordan Liuc872fad2019-10-11 11:42:03 -0700485
Jordan Liu4a2ff262019-10-21 12:30:56 -0700486 if (IS_DEBUGGABLE) {
Jordan Liuc872fad2019-10-11 11:42:03 -0700487 // Send additional broadcast intent to the specified package. This is only for sl4a
488 // automation tests.
Jordan Liuf24590f2019-11-14 14:23:49 -0800489 String[] testPkgs = mContext.getResources().getStringArray(
490 R.array.config_testCellBroadcastReceiverPkgs);
491 if (testPkgs != null) {
492 mReceiverCount.addAndGet(testPkgs.length);
Jordan Liuc872fad2019-10-11 11:42:03 -0700493 Intent additionalIntent = new Intent(intent);
Jordan Liuf24590f2019-11-14 14:23:49 -0800494 for (String pkg : testPkgs) {
495 additionalIntent.setPackage(pkg);
Jordan Liuc4c3b282019-11-22 11:40:05 -0800496 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
497 additionalIntent, receiverPermission, appOp, mReceiver,
498 getHandler(), Activity.RESULT_OK, null, null);
Jordan Liuf24590f2019-11-14 14:23:49 -0800499 }
Jordan Liuc872fad2019-10-11 11:42:03 -0700500 }
501 }
502
503 String[] pkgs = mContext.getResources().getStringArray(
Jordan Liuf24590f2019-11-14 14:23:49 -0800504 R.array.config_defaultCellBroadcastReceiverPkgs);
505 if (pkgs != null) {
506 mReceiverCount.addAndGet(pkgs.length);
507 for (String pkg : pkgs) {
508 // Explicitly send the intent to all the configured cell broadcast receivers.
509 intent.setPackage(pkg);
Jordan Liuc4c3b282019-11-22 11:40:05 -0800510 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
511 intent, receiverPermission, appOp, mReceiver, getHandler(),
512 Activity.RESULT_OK, null, null);
Jordan Liuf24590f2019-11-14 14:23:49 -0800513 }
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;
Jordan Liuc4c3b282019-11-22 11:40:05 -0800524 appOp = AppOpsManager.OPSTR_RECEIVE_SMS;
Jordan Liuc872fad2019-10-11 11:42:03 -0700525
526 intent.putExtra(EXTRA_MESSAGE, message);
Jordan Liucdfc9f32019-11-21 11:59:07 -0800527 putPhoneIdAndSubIdExtra(mContext, intent, slotIndex);
Jordan Liuc872fad2019-10-11 11:42:03 -0700528
529 mReceiverCount.incrementAndGet();
Jordan Liuc4c3b282019-11-22 11:40:05 -0800530 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
531 intent, receiverPermission, appOp, mReceiver, getHandler(),
532 Activity.RESULT_OK, null, null);
Jordan Liuc872fad2019-10-11 11:42:03 -0700533 }
534
535 if (messageUri != null) {
536 ContentValues cv = new ContentValues();
537 cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
Chen Xu93fdfeb2019-10-21 22:56:37 -0700538 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
Jordan Liuc872fad2019-10-11 11:42:03 -0700539 CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
540 }
541 }
542
Jack Yua1309272019-10-24 00:19:49 -0700543 /**
544 * Get the device resource based on SIM
545 *
546 * @param subId Subscription index
547 *
548 * @return The resource
549 */
550 public @NonNull Resources getResources(int subId) {
551 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
552 || !SubscriptionManager.isValidSubscriptionId(subId)) {
553 return mContext.getResources();
554 }
555
556 if (mResourcesCache.containsKey(subId)) {
557 return mResourcesCache.get(subId);
558 }
559
560 Resources res = SubscriptionManager.getResourcesForSubId(mContext, subId);
561 mResourcesCache.put(subId, res);
562
563 return res;
564 }
565
Jordan Liuc872fad2019-10-11 11:42:03 -0700566 @Override
567 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
568 pw.println("CellBroadcastHandler:");
569 mLocalLog.dump(fd, pw, args);
570 pw.flush();
571 }
572
573 /** The callback interface of a location request. */
574 public interface LocationUpdateCallback {
575 /**
576 * Call when the location update is available.
577 * @param location a location in (latitude, longitude) format, or {@code null} if the
578 * location service is not available.
579 */
580 void onLocationUpdate(@Nullable LatLng location);
581 }
582
583 private static final class LocationRequester {
584 private static final String TAG = LocationRequester.class.getSimpleName();
585
586 /**
587 * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
588 * Most of the location request should be responded within 20 seconds.
589 */
590 private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 20;
591
592 /**
593 * Trigger this event when the {@link LocationManager} is not responded within the given
594 * time.
595 */
596 private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
597
598 /** Request a single location update. */
599 private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
600
601 /**
602 * Request location update from network or gps location provider. Network provider will be
603 * used if available, otherwise use the gps provider.
604 */
605 private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
606 LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
607
608 private final LocationManager mLocationManager;
609 private final Looper mLooper;
610 private final List<LocationUpdateCallback> mCallbacks;
611 private final Context mContext;
612 private Handler mLocationHandler;
613
614 LocationRequester(Context context, LocationManager locationManager, Looper looper) {
615 mLocationManager = locationManager;
616 mLooper = looper;
617 mCallbacks = new ArrayList<>();
618 mContext = context;
619 mLocationHandler = new LocationHandler(looper);
620 }
621
622 /**
623 * Request a single location update. If the location is not available, a callback with
624 * {@code null} location will be called immediately.
625 *
626 * @param callback a callback to the response when the location is available
627 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
628 * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
629 * called.
630 */
631 void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
632 int maximumWaitTimeSec) {
633 mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
634 0 /* arg2 */, callback).sendToTarget();
635 }
636
637 private void onLocationUpdate(@Nullable LatLng location) {
638 for (LocationUpdateCallback callback : mCallbacks) {
639 callback.onLocationUpdate(location);
640 }
641 mCallbacks.clear();
642 }
643
644 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
645 int maximumWaitTimeSec) {
646 if (DBG) Log.d(TAG, "requestLocationUpdate");
647 if (!isLocationServiceAvailable()) {
648 if (DBG) {
649 Log.d(TAG, "Can't request location update because of no location permission");
650 }
651 callback.onLocationUpdate(null);
652 return;
653 }
654
655 if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
656 if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
657 maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
658 }
659 mLocationHandler.sendMessageDelayed(
660 mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
661 maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
662 }
663
664 mCallbacks.add(callback);
665
666 for (String provider : LOCATION_PROVIDERS) {
667 if (mLocationManager.isProviderEnabled(provider)) {
668 mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper);
669 break;
670 }
671 }
672 }
673
674 private boolean isLocationServiceAvailable() {
675 if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
676 && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
677 for (String provider : LOCATION_PROVIDERS) {
678 if (mLocationManager.isProviderEnabled(provider)) return true;
679 }
680 return false;
681 }
682
683 private boolean hasPermission(String permission) {
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -0700684 return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
685 == PackageManager.PERMISSION_GRANTED;
Jordan Liuc872fad2019-10-11 11:42:03 -0700686 }
687
688 private final LocationListener mLocationListener = new LocationListener() {
689 @Override
690 public void onLocationChanged(Location location) {
691 mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
692 onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude()));
693 }
694
695 @Override
696 public void onStatusChanged(String provider, int status, Bundle extras) {}
697
698 @Override
699 public void onProviderEnabled(String provider) {}
700
701 @Override
702 public void onProviderDisabled(String provider) {}
703 };
704
705 private final class LocationHandler extends Handler {
706 LocationHandler(Looper looper) {
707 super(looper);
708 }
709
710 @Override
711 public void handleMessage(Message msg) {
712 switch (msg.what) {
713 case EVENT_LOCATION_REQUEST_TIMEOUT:
714 if (DBG) Log.d(TAG, "location request timeout");
715 onLocationUpdate(null);
716 break;
717 case EVENT_REQUEST_LOCATION_UPDATE:
718 requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
719 break;
720 default:
721 Log.e(TAG, "Unsupported message type " + msg.what);
722 }
723 }
724 }
725 }
726}