blob: e8e19c18cf8d35c2428d561efa3b268a7ccdb1ee [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
Jordan Liuc872fad2019-10-11 11:42:03 -0700117 private CellBroadcastHandler(Context context) {
Jack Yue7591d32019-11-07 22:59:46 -0800118 this("CellBroadcastHandler", context, Looper.myLooper());
Jordan Liuc872fad2019-10-11 11:42:03 -0700119 }
120
Jordan Liuf24590f2019-11-14 14:23:49 -0800121 @VisibleForTesting
122 public CellBroadcastHandler(String debugTag, Context context, Looper looper) {
Jack Yue7591d32019-11-07 22:59:46 -0800123 super(debugTag, context, looper);
Jordan Liuc872fad2019-10-11 11:42:03 -0700124 mLocationRequester = new LocationRequester(
125 context,
126 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
127 getHandler().getLooper());
Jack Yua1309272019-10-24 00:19:49 -0700128
Jack Yu2003c522019-10-31 09:47:20 -0700129 // Adding GSM / CDMA service category mapping.
130 mServiceCategoryCrossRATMap = Stream.of(new Integer[][] {
131 // Presidential alert
132 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
133 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
134 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE,
135 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
136
137 // Extreme alert
138 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
139 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
140 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE,
141 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
142 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
143 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
144 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE,
145 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
146
147 // Severe alert
148 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
149 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
150 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE,
151 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
152 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
153 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
154 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE,
155 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
156 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED,
157 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
158 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE,
159 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
160 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
161 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
162 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE,
163 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
164 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
165 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
166 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE,
167 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
168 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
169 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
170 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE,
171 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
172
173 // Amber alert
174 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
175 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
176 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE,
177 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
178
179 // Monthly test alert
180 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
181 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
182 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE,
183 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
184 }).collect(Collectors.toMap(data -> data[0], data -> data[1]));
185
Jack Yudc607602019-10-31 13:24:43 -0700186 IntentFilter intentFilter = new IntentFilter();
187 intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
Jordan Liu4a2ff262019-10-21 12:30:56 -0700188 if (IS_DEBUGGABLE) {
Jack Yudc607602019-10-31 13:24:43 -0700189 intentFilter.addAction(ACTION_DUPLICATE_DETECTION);
190 }
Jack Yua1309272019-10-24 00:19:49 -0700191 mContext.registerReceiver(
192 new BroadcastReceiver() {
193 @Override
194 public void onReceive(Context context, Intent intent) {
Jack Yudc607602019-10-31 13:24:43 -0700195 switch (intent.getAction()) {
196 case Intent.ACTION_AIRPLANE_MODE_CHANGED:
197 boolean airplaneModeOn = intent.getBooleanExtra("state", false);
198 if (airplaneModeOn) {
199 mLastAirplaneModeTime = System.currentTimeMillis();
Jack Yu446d1742019-11-12 17:30:27 -0800200 log("Airplane mode on.");
Jack Yudc607602019-10-31 13:24:43 -0700201 }
202 break;
203 case ACTION_DUPLICATE_DETECTION:
204 mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE,
205 true);
206 break;
Jack Yua1309272019-10-24 00:19:49 -0700207 }
Jack Yudc607602019-10-31 13:24:43 -0700208
Jack Yua1309272019-10-24 00:19:49 -0700209 }
Jack Yudc607602019-10-31 13:24:43 -0700210 }, intentFilter);
Jordan Liuc872fad2019-10-11 11:42:03 -0700211 }
212
213 /**
214 * Create a new CellBroadcastHandler.
215 * @param context the context to use for dispatching Intents
216 * @return the new handler
217 */
218 public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
219 CellBroadcastHandler handler = new CellBroadcastHandler(context);
220 handler.start();
221 return handler;
222 }
223
224 /**
225 * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
226 * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
227 *
228 * @param message the message to process
229 * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
230 */
231 @Override
232 protected boolean handleSmsMessage(Message message) {
233 if (message.obj instanceof SmsCbMessage) {
Jack Yua1309272019-10-24 00:19:49 -0700234 if (!isDuplicate((SmsCbMessage) message.obj)) {
235 handleBroadcastSms((SmsCbMessage) message.obj);
236 return true;
237 }
238 return false;
Jordan Liuc872fad2019-10-11 11:42:03 -0700239 } else {
240 loge("handleMessage got object of type: " + message.obj.getClass().getName());
241 return false;
242 }
243 }
244
245 /**
246 * Dispatch a Cell Broadcast message to listeners.
247 * @param message the Cell Broadcast to broadcast
248 */
249 protected void handleBroadcastSms(SmsCbMessage message) {
250 int slotIndex = message.getSlotIndex();
Jordan Liuc872fad2019-10-11 11:42:03 -0700251
252 // TODO: Database inserting can be time consuming, therefore this should be changed to
253 // asynchronous.
254 ContentValues cv = message.getContentValues();
Chen Xu93fdfeb2019-10-21 22:56:37 -0700255 Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv);
Jordan Liuc872fad2019-10-11 11:42:03 -0700256
257 if (message.needGeoFencingCheck()) {
258 if (DBG) {
259 log("Request location update for geo-fencing. serialNumber = "
260 + message.getSerialNumber());
261 }
262
263 requestLocationUpdate(location -> {
264 if (location == null) {
265 // Broadcast the message directly if the location is not available.
266 broadcastMessage(message, uri, slotIndex);
267 } else {
268 performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
269 }
Jordan Liu4a2ff262019-10-21 12:30:56 -0700270 }, message.getMaximumWaitingDuration());
Jordan Liuc872fad2019-10-11 11:42:03 -0700271 } else {
272 if (DBG) {
273 log("Broadcast the message directly because no geo-fencing required, "
274 + "serialNumber = " + message.getSerialNumber()
275 + " needGeoFencing = " + message.needGeoFencingCheck());
276 }
277 broadcastMessage(message, uri, slotIndex);
278 }
279 }
280
281 /**
Jack Yua1309272019-10-24 00:19:49 -0700282 * Check if the message is a duplicate
283 *
284 * @param message Cell broadcast message
285 * @return {@code true} if this message is a duplicate
286 */
287 @VisibleForTesting
288 public boolean isDuplicate(SmsCbMessage message) {
Jack Yudc607602019-10-31 13:24:43 -0700289 if (!mEnableDuplicateDetection) {
290 log("Duplicate detection was disabled for debugging purposes.");
291 return false;
292 }
293
Jack Yua1309272019-10-24 00:19:49 -0700294 // Find the cell broadcast message identify by the message identifier and serial number
295 // and is not broadcasted.
296 String where = CellBroadcasts.RECEIVED_TIME + ">?";
297
298 int slotIndex = message.getSlotIndex();
299 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
300 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
301 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
302 Resources res;
303 if (subIds != null) {
304 res = getResources(subIds[0]);
305 } else {
306 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
307 }
308
309 // Only consider cell broadcast messages received within certain period.
310 // By default it's 24 hours.
311 long expirationDuration = res.getInteger(R.integer.message_expiration_time);
312 long dupCheckTime = System.currentTimeMillis() - expirationDuration;
313
Jack Yue7591d32019-11-07 22:59:46 -0800314 // Some carriers require reset duplication detection after airplane mode or reboot.
315 if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) {
Jack Yua1309272019-10-24 00:19:49 -0700316 dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime);
Jack Yue7591d32019-11-07 22:59:46 -0800317 dupCheckTime = Long.max(dupCheckTime,
318 System.currentTimeMillis() - SystemClock.elapsedRealtime());
Jack Yua1309272019-10-24 00:19:49 -0700319 }
320
321 List<SmsCbMessage> cbMessages = new ArrayList<>();
322
323 try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI,
Jordan Liud3eeb402019-11-15 11:13:59 -0800324 CellBroadcastProvider.QUERY_COLUMNS,
Jack Yua1309272019-10-24 00:19:49 -0700325 where,
326 new String[] {Long.toString(dupCheckTime)},
327 null)) {
328 if (cursor != null) {
329 while (cursor.moveToNext()) {
330 cbMessages.add(SmsCbMessage.createFromCursor(cursor));
331 }
332 }
333 }
334
335 boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body);
336
337 log("Found " + cbMessages.size() + " messages since "
338 + DateFormat.getDateTimeInstance().format(dupCheckTime));
339 for (SmsCbMessage messageToCheck : cbMessages) {
340 // If messages are from different slots, then we only compare the message body.
341 if (message.getSlotIndex() != messageToCheck.getSlotIndex()) {
342 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
343 log("Duplicate message detected from different slot. " + message);
344 return true;
345 }
346 } else {
347 // Check serial number if message is from the same carrier.
348 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
349 // Not a dup. Check next one.
350 log("Serial number check. Not a dup. " + messageToCheck);
351 continue;
352 }
353
Jack Yu2003c522019-10-31 09:47:20 -0700354 // ETWS primary / secondary should be treated differently.
355 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
356 && message.getEtwsWarningInfo().isPrimary()
357 != messageToCheck.getEtwsWarningInfo().isPrimary()) {
Jack Yua1309272019-10-24 00:19:49 -0700358 // Not a dup. Check next one.
Jack Yu6f9a26e2019-11-07 23:10:02 -0800359 log("ETWS primary check. Not a dup. " + messageToCheck);
Jack Yua1309272019-10-24 00:19:49 -0700360 continue;
361 }
362
Jack Yu2003c522019-10-31 09:47:20 -0700363 // Check if the message category is different. Some carriers send cell broadcast
364 // messages on different techs (i.e. GSM / CDMA), so we need to compare service
365 // category cross techs.
366 if (message.getServiceCategory() != messageToCheck.getServiceCategory()
Jack Yu6f9a26e2019-11-07 23:10:02 -0800367 && !Objects.equals(mServiceCategoryCrossRATMap.get(
368 message.getServiceCategory()), messageToCheck.getServiceCategory())
369 && !Objects.equals(mServiceCategoryCrossRATMap.get(
370 messageToCheck.getServiceCategory()),
371 message.getServiceCategory())) {
Jack Yua1309272019-10-24 00:19:49 -0700372 // Not a dup. Check next one.
Jack Yu6f9a26e2019-11-07 23:10:02 -0800373 log("Service category check. Not a dup. " + messageToCheck);
Jack Yua1309272019-10-24 00:19:49 -0700374 continue;
375 }
376
377 // Compare message body if needed.
378 if (!compareMessageBody || TextUtils.equals(
379 message.getMessageBody(), messageToCheck.getMessageBody())) {
380 log("Duplicate message detected. " + message);
381 return true;
382 }
383 }
384 }
385
386 log("Not a duplicate message. " + message);
387 return false;
388 }
389
390 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700391 * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
392 * {@code location} is inside the {@code broadcastArea}.
393 * @param message the message need to geo-fencing check
394 * @param uri the message's uri
395 * @param broadcastArea the broadcast area of the message
396 * @param location current location
397 */
398 protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
399 LatLng location, int slotIndex) {
400
401 if (DBG) {
402 logd("Perform geo-fencing check for message identifier = "
403 + message.getServiceCategory()
404 + " serialNumber = " + message.getSerialNumber());
405 }
406
407 for (Geometry geo : broadcastArea) {
408 if (geo.contains(location)) {
409 broadcastMessage(message, uri, slotIndex);
410 return;
411 }
412 }
413
414 if (DBG) {
415 logd("Device location is outside the broadcast area "
416 + CbGeoUtils.encodeGeometriesToString(broadcastArea));
417 }
Jack Yu2f37c432019-11-04 21:28:24 -0800418
419 sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
Jordan Liuc872fad2019-10-11 11:42:03 -0700420 }
421
422 /**
423 * Request a single location update.
424 * @param callback a callback will be called when the location is available.
425 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
426 * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
427 */
428 protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
429 mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
430 }
431
432 /**
Jordan Liu4a2ff262019-10-21 12:30:56 -0700433 * Get the subscription ID for a phone ID, or INVALID_SUBSCRIPTION_ID if the phone does not
434 * have an active sub
435 * @param phoneId the phoneId to use
436 * @return the associated sub id
437 */
Jordan Liucdfc9f32019-11-21 11:59:07 -0800438 protected static int getSubIdForPhone(Context context, int phoneId) {
Jordan Liu4a2ff262019-10-21 12:30:56 -0700439 SubscriptionManager subMan =
Jordan Liucdfc9f32019-11-21 11:59:07 -0800440 (SubscriptionManager) context.getSystemService(
Jordan Liu4a2ff262019-10-21 12:30:56 -0700441 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
442 int[] subIds = subMan.getSubscriptionIds(phoneId);
443 if (subIds != null) {
444 return subIds[0];
445 } else {
446 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
447 }
448 }
449
450 /**
Jordan Liucdfc9f32019-11-21 11:59:07 -0800451 * Put the phone ID and sub ID into an intent as extras.
452 */
453 public static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) {
454 int subId = getSubIdForPhone(context, phoneId);
455 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
456 intent.putExtra("subscription", subId);
457 intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
458 }
459 intent.putExtra("phone", phoneId);
460 intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
461 }
462
463 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700464 * Broadcast the {@code message} to the applications.
465 * @param message a message need to broadcast
466 * @param messageUri message's uri
467 */
468 protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
469 int slotIndex) {
470 String receiverPermission;
Jordan Liuc4c3b282019-11-22 11:40:05 -0800471 String appOp;
Jordan Liuc872fad2019-10-11 11:42:03 -0700472 String msg;
473 Intent intent;
474 if (message.isEmergencyMessage()) {
475 msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
476 log(msg);
477 mLocalLog.log(msg);
Chen Xueca1f0a2019-11-06 20:53:24 -0800478 intent = new Intent(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED);
Jordan Liuc872fad2019-10-11 11:42:03 -0700479 //Emergency alerts need to be delivered with high priority
480 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
481 receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
Jordan Liuc4c3b282019-11-22 11:40:05 -0800482 appOp = AppOpsManager.OPSTR_RECEIVE_EMERGENCY_BROADCAST;
Jordan Liuc872fad2019-10-11 11:42:03 -0700483
484 intent.putExtra(EXTRA_MESSAGE, message);
Jordan Liucdfc9f32019-11-21 11:59:07 -0800485 putPhoneIdAndSubIdExtra(mContext, intent, slotIndex);
Jordan Liuc872fad2019-10-11 11:42:03 -0700486
Jordan Liu4a2ff262019-10-21 12:30:56 -0700487 if (IS_DEBUGGABLE) {
Jordan Liuc872fad2019-10-11 11:42:03 -0700488 // Send additional broadcast intent to the specified package. This is only for sl4a
489 // automation tests.
Jordan Liuf24590f2019-11-14 14:23:49 -0800490 String[] testPkgs = mContext.getResources().getStringArray(
491 R.array.config_testCellBroadcastReceiverPkgs);
492 if (testPkgs != null) {
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(
Jordan Liude4a6ff2019-12-06 11:12:50 -0800497 additionalIntent, receiverPermission, appOp, null,
Jordan Liuc4c3b282019-11-22 11:40:05 -0800498 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);
Jordan Liuc872fad2019-10-11 11:42:03 -0700519 // Send implicit intent since there are various 3rd party carrier apps listen to
520 // this intent.
Jordan Liuc872fad2019-10-11 11:42:03 -0700521 receiverPermission = Manifest.permission.RECEIVE_SMS;
Jordan Liuc4c3b282019-11-22 11:40:05 -0800522 appOp = AppOpsManager.OPSTR_RECEIVE_SMS;
Jordan Liuc872fad2019-10-11 11:42:03 -0700523
Jordan Liuc872fad2019-10-11 11:42:03 -0700524 mReceiverCount.incrementAndGet();
Jordan Liu034be312020-01-16 11:04:00 -0800525 CellBroadcastIntents.sendSmsCbReceivedBroadcast(
526 mContext, UserHandle.ALL, message, mReceiver, getHandler(), Activity.RESULT_OK,
527 slotIndex);
Jordan Liuc872fad2019-10-11 11:42:03 -0700528 }
529
530 if (messageUri != null) {
531 ContentValues cv = new ContentValues();
532 cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
Chen Xu93fdfeb2019-10-21 22:56:37 -0700533 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
Jordan Liuc872fad2019-10-11 11:42:03 -0700534 CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
535 }
536 }
537
Jack Yua1309272019-10-24 00:19:49 -0700538 /**
539 * Get the device resource based on SIM
540 *
541 * @param subId Subscription index
542 *
543 * @return The resource
544 */
545 public @NonNull Resources getResources(int subId) {
546 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
547 || !SubscriptionManager.isValidSubscriptionId(subId)) {
548 return mContext.getResources();
549 }
550
551 if (mResourcesCache.containsKey(subId)) {
552 return mResourcesCache.get(subId);
553 }
554
555 Resources res = SubscriptionManager.getResourcesForSubId(mContext, subId);
556 mResourcesCache.put(subId, res);
557
558 return res;
559 }
560
Jordan Liuc872fad2019-10-11 11:42:03 -0700561 @Override
562 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
563 pw.println("CellBroadcastHandler:");
564 mLocalLog.dump(fd, pw, args);
565 pw.flush();
566 }
567
568 /** The callback interface of a location request. */
569 public interface LocationUpdateCallback {
570 /**
571 * Call when the location update is available.
572 * @param location a location in (latitude, longitude) format, or {@code null} if the
573 * location service is not available.
574 */
575 void onLocationUpdate(@Nullable LatLng location);
576 }
577
578 private static final class LocationRequester {
579 private static final String TAG = LocationRequester.class.getSimpleName();
580
581 /**
582 * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
583 * Most of the location request should be responded within 20 seconds.
584 */
585 private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 20;
586
587 /**
588 * Trigger this event when the {@link LocationManager} is not responded within the given
589 * time.
590 */
591 private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
592
593 /** Request a single location update. */
594 private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
595
596 /**
597 * Request location update from network or gps location provider. Network provider will be
598 * used if available, otherwise use the gps provider.
599 */
600 private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
601 LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
602
603 private final LocationManager mLocationManager;
604 private final Looper mLooper;
605 private final List<LocationUpdateCallback> mCallbacks;
606 private final Context mContext;
607 private Handler mLocationHandler;
608
609 LocationRequester(Context context, LocationManager locationManager, Looper looper) {
610 mLocationManager = locationManager;
611 mLooper = looper;
612 mCallbacks = new ArrayList<>();
613 mContext = context;
614 mLocationHandler = new LocationHandler(looper);
615 }
616
617 /**
618 * Request a single location update. If the location is not available, a callback with
619 * {@code null} location will be called immediately.
620 *
621 * @param callback a callback to the response when the location is available
622 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
623 * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
624 * called.
625 */
626 void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
627 int maximumWaitTimeSec) {
628 mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
629 0 /* arg2 */, callback).sendToTarget();
630 }
631
632 private void onLocationUpdate(@Nullable LatLng location) {
633 for (LocationUpdateCallback callback : mCallbacks) {
634 callback.onLocationUpdate(location);
635 }
636 mCallbacks.clear();
637 }
638
639 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
640 int maximumWaitTimeSec) {
Jordan Liu75ebf632019-12-16 15:35:27 -0800641 if (DBG) Log.d(TAG, "requestLocationUpdate");
Jordan Liuc872fad2019-10-11 11:42:03 -0700642 if (!isLocationServiceAvailable()) {
643 if (DBG) {
Jordan Liu75ebf632019-12-16 15:35:27 -0800644 Log.d(TAG, "Can't request location update because of no location permission");
Jordan Liuc872fad2019-10-11 11:42:03 -0700645 }
646 callback.onLocationUpdate(null);
647 return;
648 }
649
650 if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
651 if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
652 maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
653 }
654 mLocationHandler.sendMessageDelayed(
655 mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
656 maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
657 }
658
659 mCallbacks.add(callback);
660
661 for (String provider : LOCATION_PROVIDERS) {
662 if (mLocationManager.isProviderEnabled(provider)) {
663 mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper);
664 break;
665 }
666 }
667 }
668
669 private boolean isLocationServiceAvailable() {
670 if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
671 && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
672 for (String provider : LOCATION_PROVIDERS) {
673 if (mLocationManager.isProviderEnabled(provider)) return true;
674 }
675 return false;
676 }
677
678 private boolean hasPermission(String permission) {
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -0700679 return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
680 == PackageManager.PERMISSION_GRANTED;
Jordan Liuc872fad2019-10-11 11:42:03 -0700681 }
682
683 private final LocationListener mLocationListener = new LocationListener() {
684 @Override
685 public void onLocationChanged(Location location) {
686 mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
687 onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude()));
688 }
689
690 @Override
691 public void onStatusChanged(String provider, int status, Bundle extras) {}
692
693 @Override
694 public void onProviderEnabled(String provider) {}
695
696 @Override
697 public void onProviderDisabled(String provider) {}
698 };
699
700 private final class LocationHandler extends Handler {
701 LocationHandler(Looper looper) {
702 super(looper);
703 }
704
705 @Override
706 public void handleMessage(Message msg) {
707 switch (msg.what) {
708 case EVENT_LOCATION_REQUEST_TIMEOUT:
Jordan Liu75ebf632019-12-16 15:35:27 -0800709 if (DBG) Log.d(TAG, "location request timeout");
Jordan Liuc872fad2019-10-11 11:42:03 -0700710 onLocationUpdate(null);
711 break;
712 case EVENT_REQUEST_LOCATION_UPDATE:
713 requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
714 break;
715 default:
Jordan Liu75ebf632019-12-16 15:35:27 -0800716 Log.e(TAG, "Unsupported message type " + msg.what);
Jordan Liuc872fad2019-10-11 11:42:03 -0700717 }
718 }
719 }
720 }
721}