blob: 8781c804fac06a7cd02d73cb8725cfc439b39134 [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 Liu5b80be22019-11-22 11:30:20 -080048import android.telephony.CellBroadcastIntents;
Jordan Liuc872fad2019-10-11 11:42:03 -070049import android.telephony.SmsCbMessage;
50import android.telephony.SubscriptionManager;
Jack Yu2003c522019-10-31 09:47:20 -070051import android.telephony.cdma.CdmaSmsCbProgramData;
Jack Yua1309272019-10-24 00:19:49 -070052import android.text.TextUtils;
Jordan Liuc872fad2019-10-11 11:42:03 -070053import android.text.format.DateUtils;
54import android.util.LocalLog;
Jordan Liu75ebf632019-12-16 15:35:27 -080055import android.util.Log;
Jordan Liuc872fad2019-10-11 11:42:03 -070056
Jack Yua1309272019-10-24 00:19:49 -070057import com.android.internal.annotations.VisibleForTesting;
Jordan Liuc872fad2019-10-11 11:42:03 -070058
59import java.io.FileDescriptor;
60import java.io.PrintWriter;
Jack Yua1309272019-10-24 00:19:49 -070061import java.text.DateFormat;
Jordan Liuc872fad2019-10-11 11:42:03 -070062import java.util.ArrayList;
63import java.util.Arrays;
Jack Yua1309272019-10-24 00:19:49 -070064import java.util.HashMap;
Jordan Liuc872fad2019-10-11 11:42:03 -070065import java.util.List;
Jack Yua1309272019-10-24 00:19:49 -070066import java.util.Map;
Jack Yu6f9a26e2019-11-07 23:10:02 -080067import java.util.Objects;
Jack Yu2003c522019-10-31 09:47:20 -070068import java.util.stream.Collectors;
69import java.util.stream.Stream;
Jordan Liuc872fad2019-10-11 11:42:03 -070070
71/**
72 * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
73 * completes and our result receiver is called.
74 */
75public class CellBroadcastHandler extends WakeLockStateMachine {
76 private static final String EXTRA_MESSAGE = "message";
77
Jack Yudc607602019-10-31 13:24:43 -070078 /**
79 * To disable cell broadcast duplicate detection for debugging purposes
80 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
81 * --ez enable false</code>
82 *
83 * To enable cell broadcast duplicate detection for debugging purposes
84 * <code>adb shell am broadcast -a com.android.cellbroadcastservice.action.DUPLICATE_DETECTION
85 * --ez enable true</code>
86 */
87 private static final String ACTION_DUPLICATE_DETECTION =
88 "com.android.cellbroadcastservice.action.DUPLICATE_DETECTION";
89
90 /**
91 * The extra for cell broadcast duplicate detection enable/disable
92 */
93 private static final String EXTRA_ENABLE = "enable";
94
Jordan Liuc872fad2019-10-11 11:42:03 -070095 private final LocalLog mLocalLog = new LocalLog(100);
96
Jordan Liu26e820c2019-10-22 14:42:07 -070097 private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
98
Jordan Liuc872fad2019-10-11 11:42:03 -070099 /** Uses to request the location update. */
100 public final LocationRequester mLocationRequester;
101
Jack Yua1309272019-10-24 00:19:49 -0700102 /** Timestamp of last airplane mode on */
Jack Yue7591d32019-11-07 22:59:46 -0800103 protected long mLastAirplaneModeTime = 0;
Jack Yua1309272019-10-24 00:19:49 -0700104
Jack Yu2003c522019-10-31 09:47:20 -0700105 /** Resource cache */
Jack Yua1309272019-10-24 00:19:49 -0700106 private final Map<Integer, Resources> mResourcesCache = new HashMap<>();
107
Jack Yudc607602019-10-31 13:24:43 -0700108 /** Whether performing duplicate detection or not. Note this is for debugging purposes only. */
109 private boolean mEnableDuplicateDetection = true;
110
Jack Yu2003c522019-10-31 09:47:20 -0700111 /**
112 * Service category equivalent map. The key is the GSM service category, the value is the CDMA
113 * service category.
114 */
115 private final Map<Integer, Integer> mServiceCategoryCrossRATMap;
116
Chen Xu47bb2062020-01-02 18:13:20 -0800117 private BroadcastReceiver mReceiver = new BroadcastReceiver() {
118 @Override
119 public void onReceive(Context context, Intent intent) {
120 switch (intent.getAction()) {
121 case Intent.ACTION_AIRPLANE_MODE_CHANGED:
122 boolean airplaneModeOn = intent.getBooleanExtra("state", false);
123 if (airplaneModeOn) {
124 mLastAirplaneModeTime = System.currentTimeMillis();
125 log("Airplane mode on.");
126 }
127 break;
128 case ACTION_DUPLICATE_DETECTION:
129 mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE,
130 true);
131 break;
132 }
133 }
134 };
135
Jordan Liuc872fad2019-10-11 11:42:03 -0700136 private CellBroadcastHandler(Context context) {
Jack Yue7591d32019-11-07 22:59:46 -0800137 this("CellBroadcastHandler", context, Looper.myLooper());
Jordan Liuc872fad2019-10-11 11:42:03 -0700138 }
139
Jordan Liuf24590f2019-11-14 14:23:49 -0800140 @VisibleForTesting
141 public CellBroadcastHandler(String debugTag, Context context, Looper looper) {
Jack Yue7591d32019-11-07 22:59:46 -0800142 super(debugTag, context, looper);
Jordan Liuc872fad2019-10-11 11:42:03 -0700143 mLocationRequester = new LocationRequester(
144 context,
145 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
146 getHandler().getLooper());
Jack Yua1309272019-10-24 00:19:49 -0700147
Jack Yu2003c522019-10-31 09:47:20 -0700148 // Adding GSM / CDMA service category mapping.
149 mServiceCategoryCrossRATMap = Stream.of(new Integer[][] {
150 // Presidential alert
151 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
152 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
153 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE,
154 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
155
156 // Extreme alert
157 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
158 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
159 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE,
160 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
161 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
162 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
163 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE,
164 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
165
166 // Severe alert
167 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
168 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
169 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE,
170 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
171 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
172 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
173 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE,
174 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
175 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED,
176 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
177 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE,
178 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
179 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
180 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
181 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE,
182 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
183 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
184 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
185 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE,
186 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
187 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
188 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
189 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE,
190 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
191
192 // Amber alert
193 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
194 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
195 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE,
196 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
197
198 // Monthly test alert
199 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
200 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
201 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE,
202 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
203 }).collect(Collectors.toMap(data -> data[0], data -> data[1]));
204
Jack Yudc607602019-10-31 13:24:43 -0700205 IntentFilter intentFilter = new IntentFilter();
206 intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
Jordan Liu4a2ff262019-10-21 12:30:56 -0700207 if (IS_DEBUGGABLE) {
Jack Yudc607602019-10-31 13:24:43 -0700208 intentFilter.addAction(ACTION_DUPLICATE_DETECTION);
209 }
Jack Yudc607602019-10-31 13:24:43 -0700210
Chen Xu47bb2062020-01-02 18:13:20 -0800211 mContext.registerReceiver(mReceiver, intentFilter);
212 }
213
214 public void cleanup() {
215 if (DBG) log("CellBroadcastHandler cleanup");
216 mContext.unregisterReceiver(mReceiver);
Jordan Liuc872fad2019-10-11 11:42:03 -0700217 }
218
219 /**
220 * Create a new CellBroadcastHandler.
221 * @param context the context to use for dispatching Intents
222 * @return the new handler
223 */
224 public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
225 CellBroadcastHandler handler = new CellBroadcastHandler(context);
226 handler.start();
227 return handler;
228 }
229
230 /**
231 * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
232 * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
233 *
234 * @param message the message to process
235 * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
236 */
237 @Override
238 protected boolean handleSmsMessage(Message message) {
239 if (message.obj instanceof SmsCbMessage) {
Jack Yua1309272019-10-24 00:19:49 -0700240 if (!isDuplicate((SmsCbMessage) message.obj)) {
241 handleBroadcastSms((SmsCbMessage) message.obj);
242 return true;
243 }
244 return false;
Jordan Liuc872fad2019-10-11 11:42:03 -0700245 } else {
246 loge("handleMessage got object of type: " + message.obj.getClass().getName());
247 return false;
248 }
249 }
250
251 /**
252 * Dispatch a Cell Broadcast message to listeners.
253 * @param message the Cell Broadcast to broadcast
254 */
255 protected void handleBroadcastSms(SmsCbMessage message) {
256 int slotIndex = message.getSlotIndex();
Jordan Liuc872fad2019-10-11 11:42:03 -0700257
258 // TODO: Database inserting can be time consuming, therefore this should be changed to
259 // asynchronous.
260 ContentValues cv = message.getContentValues();
Chen Xu93fdfeb2019-10-21 22:56:37 -0700261 Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv);
Jordan Liuc872fad2019-10-11 11:42:03 -0700262
263 if (message.needGeoFencingCheck()) {
264 if (DBG) {
265 log("Request location update for geo-fencing. serialNumber = "
266 + message.getSerialNumber());
267 }
268
269 requestLocationUpdate(location -> {
270 if (location == null) {
271 // Broadcast the message directly if the location is not available.
272 broadcastMessage(message, uri, slotIndex);
273 } else {
274 performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
275 }
Jordan Liu4a2ff262019-10-21 12:30:56 -0700276 }, message.getMaximumWaitingDuration());
Jordan Liuc872fad2019-10-11 11:42:03 -0700277 } else {
278 if (DBG) {
279 log("Broadcast the message directly because no geo-fencing required, "
280 + "serialNumber = " + message.getSerialNumber()
281 + " needGeoFencing = " + message.needGeoFencingCheck());
282 }
283 broadcastMessage(message, uri, slotIndex);
284 }
285 }
286
287 /**
Jack Yua1309272019-10-24 00:19:49 -0700288 * Check if the message is a duplicate
289 *
290 * @param message Cell broadcast message
291 * @return {@code true} if this message is a duplicate
292 */
293 @VisibleForTesting
294 public boolean isDuplicate(SmsCbMessage message) {
Jack Yudc607602019-10-31 13:24:43 -0700295 if (!mEnableDuplicateDetection) {
296 log("Duplicate detection was disabled for debugging purposes.");
297 return false;
298 }
299
Jack Yua1309272019-10-24 00:19:49 -0700300 // Find the cell broadcast message identify by the message identifier and serial number
301 // and is not broadcasted.
302 String where = CellBroadcasts.RECEIVED_TIME + ">?";
303
304 int slotIndex = message.getSlotIndex();
305 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
306 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
307 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
308 Resources res;
309 if (subIds != null) {
310 res = getResources(subIds[0]);
311 } else {
312 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
313 }
314
315 // Only consider cell broadcast messages received within certain period.
316 // By default it's 24 hours.
317 long expirationDuration = res.getInteger(R.integer.message_expiration_time);
318 long dupCheckTime = System.currentTimeMillis() - expirationDuration;
319
Jack Yue7591d32019-11-07 22:59:46 -0800320 // Some carriers require reset duplication detection after airplane mode or reboot.
321 if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) {
Jack Yua1309272019-10-24 00:19:49 -0700322 dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime);
Jack Yue7591d32019-11-07 22:59:46 -0800323 dupCheckTime = Long.max(dupCheckTime,
324 System.currentTimeMillis() - SystemClock.elapsedRealtime());
Jack Yua1309272019-10-24 00:19:49 -0700325 }
326
327 List<SmsCbMessage> cbMessages = new ArrayList<>();
328
329 try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI,
Jordan Liud3eeb402019-11-15 11:13:59 -0800330 CellBroadcastProvider.QUERY_COLUMNS,
Jack Yua1309272019-10-24 00:19:49 -0700331 where,
332 new String[] {Long.toString(dupCheckTime)},
333 null)) {
334 if (cursor != null) {
335 while (cursor.moveToNext()) {
336 cbMessages.add(SmsCbMessage.createFromCursor(cursor));
337 }
338 }
339 }
340
341 boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body);
342
343 log("Found " + cbMessages.size() + " messages since "
344 + DateFormat.getDateTimeInstance().format(dupCheckTime));
345 for (SmsCbMessage messageToCheck : cbMessages) {
346 // If messages are from different slots, then we only compare the message body.
347 if (message.getSlotIndex() != messageToCheck.getSlotIndex()) {
348 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
349 log("Duplicate message detected from different slot. " + message);
350 return true;
351 }
352 } else {
353 // Check serial number if message is from the same carrier.
354 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
355 // Not a dup. Check next one.
Jack Yua1309272019-10-24 00:19:49 -0700356 continue;
357 }
358
Jack Yu2003c522019-10-31 09:47:20 -0700359 // ETWS primary / secondary should be treated differently.
360 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
361 && message.getEtwsWarningInfo().isPrimary()
362 != messageToCheck.getEtwsWarningInfo().isPrimary()) {
Jack Yua1309272019-10-24 00:19:49 -0700363 // Not a dup. Check next one.
Jack Yua1309272019-10-24 00:19:49 -0700364 continue;
365 }
366
Jack Yu2003c522019-10-31 09:47:20 -0700367 // Check if the message category is different. Some carriers send cell broadcast
368 // messages on different techs (i.e. GSM / CDMA), so we need to compare service
369 // category cross techs.
370 if (message.getServiceCategory() != messageToCheck.getServiceCategory()
Jack Yu6f9a26e2019-11-07 23:10:02 -0800371 && !Objects.equals(mServiceCategoryCrossRATMap.get(
372 message.getServiceCategory()), messageToCheck.getServiceCategory())
373 && !Objects.equals(mServiceCategoryCrossRATMap.get(
374 messageToCheck.getServiceCategory()),
375 message.getServiceCategory())) {
Jack Yua1309272019-10-24 00:19:49 -0700376 // Not a dup. Check next one.
Jack Yua1309272019-10-24 00:19:49 -0700377 continue;
378 }
379
380 // Compare message body if needed.
381 if (!compareMessageBody || TextUtils.equals(
382 message.getMessageBody(), messageToCheck.getMessageBody())) {
383 log("Duplicate message detected. " + message);
384 return true;
385 }
386 }
387 }
388
389 log("Not a duplicate message. " + message);
390 return false;
391 }
392
393 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700394 * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
395 * {@code location} is inside the {@code broadcastArea}.
396 * @param message the message need to geo-fencing check
397 * @param uri the message's uri
398 * @param broadcastArea the broadcast area of the message
399 * @param location current location
400 */
401 protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
402 LatLng location, int slotIndex) {
403
404 if (DBG) {
405 logd("Perform geo-fencing check for message identifier = "
406 + message.getServiceCategory()
407 + " serialNumber = " + message.getSerialNumber());
408 }
409
410 for (Geometry geo : broadcastArea) {
411 if (geo.contains(location)) {
412 broadcastMessage(message, uri, slotIndex);
413 return;
414 }
415 }
416
417 if (DBG) {
418 logd("Device location is outside the broadcast area "
419 + CbGeoUtils.encodeGeometriesToString(broadcastArea));
420 }
Jack Yu2f37c432019-11-04 21:28:24 -0800421
422 sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
Jordan Liuc872fad2019-10-11 11:42:03 -0700423 }
424
425 /**
426 * Request a single location update.
427 * @param callback a callback will be called when the location is available.
428 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
429 * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
430 */
431 protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
432 mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
433 }
434
435 /**
Jordan Liu4a2ff262019-10-21 12:30:56 -0700436 * Get the subscription ID for a phone ID, or INVALID_SUBSCRIPTION_ID if the phone does not
437 * have an active sub
438 * @param phoneId the phoneId to use
439 * @return the associated sub id
440 */
Jordan Liucdfc9f32019-11-21 11:59:07 -0800441 protected static int getSubIdForPhone(Context context, int phoneId) {
Jordan Liu4a2ff262019-10-21 12:30:56 -0700442 SubscriptionManager subMan =
Jordan Liucdfc9f32019-11-21 11:59:07 -0800443 (SubscriptionManager) context.getSystemService(
Jordan Liu4a2ff262019-10-21 12:30:56 -0700444 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
445 int[] subIds = subMan.getSubscriptionIds(phoneId);
446 if (subIds != null) {
447 return subIds[0];
448 } else {
449 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
450 }
451 }
452
453 /**
Jordan Liucdfc9f32019-11-21 11:59:07 -0800454 * Put the phone ID and sub ID into an intent as extras.
455 */
456 public static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) {
457 int subId = getSubIdForPhone(context, phoneId);
458 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
459 intent.putExtra("subscription", subId);
460 intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
461 }
462 intent.putExtra("phone", phoneId);
463 intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
464 }
465
466 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700467 * Broadcast the {@code message} to the applications.
468 * @param message a message need to broadcast
469 * @param messageUri message's uri
470 */
471 protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
472 int slotIndex) {
473 String receiverPermission;
Jordan Liuc4c3b282019-11-22 11:40:05 -0800474 String appOp;
Jordan Liuc872fad2019-10-11 11:42:03 -0700475 String msg;
476 Intent intent;
477 if (message.isEmergencyMessage()) {
478 msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
479 log(msg);
480 mLocalLog.log(msg);
Chen Xueca1f0a2019-11-06 20:53:24 -0800481 intent = new Intent(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED);
Jordan Liuc872fad2019-10-11 11:42:03 -0700482 //Emergency alerts need to be delivered with high priority
483 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
484 receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
Jordan Liuc4c3b282019-11-22 11:40:05 -0800485 appOp = AppOpsManager.OPSTR_RECEIVE_EMERGENCY_BROADCAST;
Jordan Liuc872fad2019-10-11 11:42:03 -0700486
487 intent.putExtra(EXTRA_MESSAGE, message);
Jordan Liucdfc9f32019-11-21 11:59:07 -0800488 putPhoneIdAndSubIdExtra(mContext, intent, slotIndex);
Jordan Liuc872fad2019-10-11 11:42:03 -0700489
Jordan Liu4a2ff262019-10-21 12:30:56 -0700490 if (IS_DEBUGGABLE) {
Jordan Liuc872fad2019-10-11 11:42:03 -0700491 // Send additional broadcast intent to the specified package. This is only for sl4a
492 // automation tests.
Jordan Liuf24590f2019-11-14 14:23:49 -0800493 String[] testPkgs = mContext.getResources().getStringArray(
494 R.array.config_testCellBroadcastReceiverPkgs);
495 if (testPkgs != null) {
Jordan Liuc872fad2019-10-11 11:42:03 -0700496 Intent additionalIntent = new Intent(intent);
Jordan Liuf24590f2019-11-14 14:23:49 -0800497 for (String pkg : testPkgs) {
498 additionalIntent.setPackage(pkg);
Jordan Liuc4c3b282019-11-22 11:40:05 -0800499 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
Jordan Liude4a6ff2019-12-06 11:12:50 -0800500 additionalIntent, receiverPermission, appOp, null,
Jordan Liuc4c3b282019-11-22 11:40:05 -0800501 getHandler(), Activity.RESULT_OK, null, null);
Jordan Liuf24590f2019-11-14 14:23:49 -0800502 }
Jordan Liuc872fad2019-10-11 11:42:03 -0700503 }
504 }
505
506 String[] pkgs = mContext.getResources().getStringArray(
Jordan Liuf24590f2019-11-14 14:23:49 -0800507 R.array.config_defaultCellBroadcastReceiverPkgs);
508 if (pkgs != null) {
509 mReceiverCount.addAndGet(pkgs.length);
510 for (String pkg : pkgs) {
511 // Explicitly send the intent to all the configured cell broadcast receivers.
512 intent.setPackage(pkg);
Jordan Liuc4c3b282019-11-22 11:40:05 -0800513 mContext.createContextAsUser(UserHandle.ALL, 0).sendOrderedBroadcast(
514 intent, receiverPermission, appOp, mReceiver, getHandler(),
515 Activity.RESULT_OK, null, null);
Jordan Liuf24590f2019-11-14 14:23:49 -0800516 }
Jordan Liuc872fad2019-10-11 11:42:03 -0700517 }
518 } else {
519 msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
520 log(msg);
521 mLocalLog.log(msg);
Jordan Liuc872fad2019-10-11 11:42:03 -0700522 // Send implicit intent since there are various 3rd party carrier apps listen to
523 // this intent.
Jordan Liuc872fad2019-10-11 11:42:03 -0700524 receiverPermission = Manifest.permission.RECEIVE_SMS;
Jordan Liuc4c3b282019-11-22 11:40:05 -0800525 appOp = AppOpsManager.OPSTR_RECEIVE_SMS;
Jordan Liuc872fad2019-10-11 11:42:03 -0700526
Jordan Liuc872fad2019-10-11 11:42:03 -0700527 mReceiverCount.incrementAndGet();
Jordan Liu034be312020-01-16 11:04:00 -0800528 CellBroadcastIntents.sendSmsCbReceivedBroadcast(
529 mContext, UserHandle.ALL, message, mReceiver, getHandler(), Activity.RESULT_OK,
530 slotIndex);
Jordan Liuc872fad2019-10-11 11:42:03 -0700531 }
532
533 if (messageUri != null) {
534 ContentValues cv = new ContentValues();
535 cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
Chen Xu93fdfeb2019-10-21 22:56:37 -0700536 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
Jordan Liuc872fad2019-10-11 11:42:03 -0700537 CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
538 }
539 }
540
Jack Yua1309272019-10-24 00:19:49 -0700541 /**
542 * Get the device resource based on SIM
543 *
544 * @param subId Subscription index
545 *
546 * @return The resource
547 */
548 public @NonNull Resources getResources(int subId) {
549 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
550 || !SubscriptionManager.isValidSubscriptionId(subId)) {
551 return mContext.getResources();
552 }
553
554 if (mResourcesCache.containsKey(subId)) {
555 return mResourcesCache.get(subId);
556 }
557
558 Resources res = SubscriptionManager.getResourcesForSubId(mContext, subId);
559 mResourcesCache.put(subId, res);
560
561 return res;
562 }
563
Jordan Liuc872fad2019-10-11 11:42:03 -0700564 @Override
565 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
566 pw.println("CellBroadcastHandler:");
567 mLocalLog.dump(fd, pw, args);
568 pw.flush();
569 }
570
571 /** The callback interface of a location request. */
572 public interface LocationUpdateCallback {
573 /**
574 * Call when the location update is available.
575 * @param location a location in (latitude, longitude) format, or {@code null} if the
576 * location service is not available.
577 */
578 void onLocationUpdate(@Nullable LatLng location);
579 }
580
581 private static final class LocationRequester {
582 private static final String TAG = LocationRequester.class.getSimpleName();
583
584 /**
585 * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
586 * Most of the location request should be responded within 20 seconds.
587 */
588 private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 20;
589
590 /**
591 * Trigger this event when the {@link LocationManager} is not responded within the given
592 * time.
593 */
594 private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
595
596 /** Request a single location update. */
597 private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
598
599 /**
600 * Request location update from network or gps location provider. Network provider will be
601 * used if available, otherwise use the gps provider.
602 */
603 private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
604 LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
605
606 private final LocationManager mLocationManager;
607 private final Looper mLooper;
608 private final List<LocationUpdateCallback> mCallbacks;
609 private final Context mContext;
610 private Handler mLocationHandler;
611
612 LocationRequester(Context context, LocationManager locationManager, Looper looper) {
613 mLocationManager = locationManager;
614 mLooper = looper;
615 mCallbacks = new ArrayList<>();
616 mContext = context;
617 mLocationHandler = new LocationHandler(looper);
618 }
619
620 /**
621 * Request a single location update. If the location is not available, a callback with
622 * {@code null} location will be called immediately.
623 *
624 * @param callback a callback to the response when the location is available
625 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
626 * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
627 * called.
628 */
629 void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
630 int maximumWaitTimeSec) {
631 mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
632 0 /* arg2 */, callback).sendToTarget();
633 }
634
635 private void onLocationUpdate(@Nullable LatLng location) {
636 for (LocationUpdateCallback callback : mCallbacks) {
637 callback.onLocationUpdate(location);
638 }
639 mCallbacks.clear();
640 }
641
642 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
643 int maximumWaitTimeSec) {
Jordan Liu75ebf632019-12-16 15:35:27 -0800644 if (DBG) Log.d(TAG, "requestLocationUpdate");
Jordan Liuc872fad2019-10-11 11:42:03 -0700645 if (!isLocationServiceAvailable()) {
646 if (DBG) {
Jordan Liu75ebf632019-12-16 15:35:27 -0800647 Log.d(TAG, "Can't request location update because of no location permission");
Jordan Liuc872fad2019-10-11 11:42:03 -0700648 }
649 callback.onLocationUpdate(null);
650 return;
651 }
652
653 if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
654 if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
655 maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
656 }
657 mLocationHandler.sendMessageDelayed(
658 mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
659 maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
660 }
661
662 mCallbacks.add(callback);
663
664 for (String provider : LOCATION_PROVIDERS) {
665 if (mLocationManager.isProviderEnabled(provider)) {
666 mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper);
667 break;
668 }
669 }
670 }
671
672 private boolean isLocationServiceAvailable() {
673 if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
674 && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
675 for (String provider : LOCATION_PROVIDERS) {
676 if (mLocationManager.isProviderEnabled(provider)) return true;
677 }
678 return false;
679 }
680
681 private boolean hasPermission(String permission) {
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -0700682 return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
683 == PackageManager.PERMISSION_GRANTED;
Jordan Liuc872fad2019-10-11 11:42:03 -0700684 }
685
686 private final LocationListener mLocationListener = new LocationListener() {
687 @Override
688 public void onLocationChanged(Location location) {
689 mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
690 onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude()));
691 }
692
693 @Override
694 public void onStatusChanged(String provider, int status, Bundle extras) {}
695
696 @Override
697 public void onProviderEnabled(String provider) {}
698
699 @Override
700 public void onProviderDisabled(String provider) {}
701 };
702
703 private final class LocationHandler extends Handler {
704 LocationHandler(Looper looper) {
705 super(looper);
706 }
707
708 @Override
709 public void handleMessage(Message msg) {
710 switch (msg.what) {
711 case EVENT_LOCATION_REQUEST_TIMEOUT:
Jordan Liu75ebf632019-12-16 15:35:27 -0800712 if (DBG) Log.d(TAG, "location request timeout");
Jordan Liuc872fad2019-10-11 11:42:03 -0700713 onLocationUpdate(null);
714 break;
715 case EVENT_REQUEST_LOCATION_UPDATE:
716 requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
717 break;
718 default:
Jordan Liu75ebf632019-12-16 15:35:27 -0800719 Log.e(TAG, "Unsupported message type " + msg.what);
Jordan Liuc872fad2019-10-11 11:42:03 -0700720 }
721 }
722 }
723 }
724}