blob: 30047a96b8f0e54856ccd46ea63d569b4e832dff [file] [log] [blame]
Jordan Liuc872fad2019-10-11 11:42:03 -07001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.cellbroadcastservice;
18
Jordan Liuc872fad2019-10-11 11:42:03 -070019import static android.provider.Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG;
20
21import android.Manifest;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.app.Activity;
25import android.app.AppOpsManager;
Jack Yua1309272019-10-24 00:19:49 -070026import android.content.BroadcastReceiver;
Jordan Liuc872fad2019-10-11 11:42:03 -070027import android.content.ContentValues;
28import android.content.Context;
29import android.content.Intent;
Jack Yua1309272019-10-24 00:19:49 -070030import android.content.IntentFilter;
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -070031import android.content.pm.PackageManager;
Jack Yua1309272019-10-24 00:19:49 -070032import android.content.res.Resources;
33import android.database.Cursor;
Jordan Liuc872fad2019-10-11 11:42:03 -070034import android.location.Location;
35import android.location.LocationListener;
36import android.location.LocationManager;
37import android.net.Uri;
38import android.os.Build;
39import android.os.Bundle;
40import android.os.Handler;
41import android.os.Looper;
42import android.os.Message;
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -070043import android.os.Process;
Jordan Liu26e820c2019-10-22 14:42:07 -070044import android.os.SystemProperties;
Jordan Liuc872fad2019-10-11 11:42:03 -070045import android.os.UserHandle;
46import android.provider.Settings;
47import android.provider.Telephony;
48import android.provider.Telephony.CellBroadcasts;
49import 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;
55import android.util.Log;
56
Jack Yua1309272019-10-24 00:19:49 -070057import com.android.internal.annotations.VisibleForTesting;
Jordan Liuc872fad2019-10-11 11:42:03 -070058import com.android.internal.telephony.CbGeoUtils.Geometry;
59import com.android.internal.telephony.CbGeoUtils.LatLng;
60import com.android.internal.telephony.metrics.TelephonyMetrics;
61
62import java.io.FileDescriptor;
63import java.io.PrintWriter;
Jack Yua1309272019-10-24 00:19:49 -070064import java.text.DateFormat;
Jordan Liuc872fad2019-10-11 11:42:03 -070065import java.util.ArrayList;
66import java.util.Arrays;
Jack Yua1309272019-10-24 00:19:49 -070067import java.util.HashMap;
Jordan Liuc872fad2019-10-11 11:42:03 -070068import java.util.List;
Jack Yua1309272019-10-24 00:19:49 -070069import java.util.Map;
Jack Yu2003c522019-10-31 09:47:20 -070070import java.util.stream.Collectors;
71import java.util.stream.Stream;
Jordan Liuc872fad2019-10-11 11:42:03 -070072
73/**
74 * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
75 * completes and our result receiver is called.
76 */
77public class CellBroadcastHandler extends WakeLockStateMachine {
78 private static final String EXTRA_MESSAGE = "message";
79
80 private final LocalLog mLocalLog = new LocalLog(100);
81
Jordan Liu26e820c2019-10-22 14:42:07 -070082 private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
83
Jordan Liuc872fad2019-10-11 11:42:03 -070084 /** Uses to request the location update. */
85 public final LocationRequester mLocationRequester;
86
Jack Yua1309272019-10-24 00:19:49 -070087 /** Timestamp of last airplane mode on */
88 private long mLastAirplaneModeTime = 0;
89
Jack Yu2003c522019-10-31 09:47:20 -070090 /** Resource cache */
Jack Yua1309272019-10-24 00:19:49 -070091 private final Map<Integer, Resources> mResourcesCache = new HashMap<>();
92
Jack Yu2003c522019-10-31 09:47:20 -070093 /**
94 * Service category equivalent map. The key is the GSM service category, the value is the CDMA
95 * service category.
96 */
97 private final Map<Integer, Integer> mServiceCategoryCrossRATMap;
98
Jordan Liuc872fad2019-10-11 11:42:03 -070099 private CellBroadcastHandler(Context context) {
100 this("CellBroadcastHandler", context);
101 }
102
103 protected CellBroadcastHandler(String debugTag, Context context) {
104 super(debugTag, context);
105 mLocationRequester = new LocationRequester(
106 context,
107 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
108 getHandler().getLooper());
Jack Yua1309272019-10-24 00:19:49 -0700109
Jack Yu2003c522019-10-31 09:47:20 -0700110 // Adding GSM / CDMA service category mapping.
111 mServiceCategoryCrossRATMap = Stream.of(new Integer[][] {
112 // Presidential alert
113 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
114 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
115 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE,
116 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT},
117
118 // Extreme alert
119 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
120 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
121 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE,
122 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
123 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
124 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
125 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE,
126 CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT},
127
128 // Severe alert
129 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
130 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
131 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE,
132 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
133 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
134 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
135 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE,
136 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
137 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED,
138 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
139 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE,
140 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
141 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
142 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
143 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE,
144 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
145 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
146 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
147 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE,
148 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
149 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
150 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
151 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE,
152 CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT},
153
154 // Amber alert
155 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
156 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
157 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE,
158 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY},
159
160 // Monthly test alert
161 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
162 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
163 { SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE,
164 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE},
165 }).collect(Collectors.toMap(data -> data[0], data -> data[1]));
166
Jack Yua1309272019-10-24 00:19:49 -0700167 mContext.registerReceiver(
168 new BroadcastReceiver() {
169 @Override
170 public void onReceive(Context context, Intent intent) {
171 boolean airplaneModeOn = intent.getBooleanExtra("state", false);
172 if (airplaneModeOn) {
173 mLastAirplaneModeTime = System.currentTimeMillis();
174 log("Airplane mode on. Reset duplicate detection.");
175 }
176 }
177 },
178 new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
Jordan Liuc872fad2019-10-11 11:42:03 -0700179 }
180
181 /**
182 * Create a new CellBroadcastHandler.
183 * @param context the context to use for dispatching Intents
184 * @return the new handler
185 */
186 public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
187 CellBroadcastHandler handler = new CellBroadcastHandler(context);
188 handler.start();
189 return handler;
190 }
191
192 /**
193 * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
194 * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
195 *
196 * @param message the message to process
197 * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
198 */
199 @Override
200 protected boolean handleSmsMessage(Message message) {
201 if (message.obj instanceof SmsCbMessage) {
Jack Yua1309272019-10-24 00:19:49 -0700202 if (!isDuplicate((SmsCbMessage) message.obj)) {
203 handleBroadcastSms((SmsCbMessage) message.obj);
204 return true;
205 }
206 return false;
Jordan Liuc872fad2019-10-11 11:42:03 -0700207 } else {
208 loge("handleMessage got object of type: " + message.obj.getClass().getName());
209 return false;
210 }
211 }
212
213 /**
214 * Dispatch a Cell Broadcast message to listeners.
215 * @param message the Cell Broadcast to broadcast
216 */
217 protected void handleBroadcastSms(SmsCbMessage message) {
218 int slotIndex = message.getSlotIndex();
219 // Log Cellbroadcast msg received event
220 TelephonyMetrics metrics = TelephonyMetrics.getInstance();
221 metrics.writeNewCBSms(slotIndex, message.getMessageFormat(),
222 message.getMessagePriority(), message.isCmasMessage(), message.isEtwsMessage(),
223 message.getServiceCategory(), message.getSerialNumber(),
224 System.currentTimeMillis());
225
226 // TODO: Database inserting can be time consuming, therefore this should be changed to
227 // asynchronous.
228 ContentValues cv = message.getContentValues();
Chen Xu93fdfeb2019-10-21 22:56:37 -0700229 Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv);
Jordan Liuc872fad2019-10-11 11:42:03 -0700230
231 if (message.needGeoFencingCheck()) {
232 if (DBG) {
233 log("Request location update for geo-fencing. serialNumber = "
234 + message.getSerialNumber());
235 }
236
237 requestLocationUpdate(location -> {
238 if (location == null) {
239 // Broadcast the message directly if the location is not available.
240 broadcastMessage(message, uri, slotIndex);
241 } else {
242 performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
243 }
244 }, message.getMaximumWaitingTime());
245 } else {
246 if (DBG) {
247 log("Broadcast the message directly because no geo-fencing required, "
248 + "serialNumber = " + message.getSerialNumber()
249 + " needGeoFencing = " + message.needGeoFencingCheck());
250 }
251 broadcastMessage(message, uri, slotIndex);
252 }
253 }
254
255 /**
Jack Yua1309272019-10-24 00:19:49 -0700256 * Check if the message is a duplicate
257 *
258 * @param message Cell broadcast message
259 * @return {@code true} if this message is a duplicate
260 */
261 @VisibleForTesting
262 public boolean isDuplicate(SmsCbMessage message) {
263 // Find the cell broadcast message identify by the message identifier and serial number
264 // and is not broadcasted.
265 String where = CellBroadcasts.RECEIVED_TIME + ">?";
266
267 int slotIndex = message.getSlotIndex();
268 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
269 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
270 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
271 Resources res;
272 if (subIds != null) {
273 res = getResources(subIds[0]);
274 } else {
275 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
276 }
277
278 // Only consider cell broadcast messages received within certain period.
279 // By default it's 24 hours.
280 long expirationDuration = res.getInteger(R.integer.message_expiration_time);
281 long dupCheckTime = System.currentTimeMillis() - expirationDuration;
282
283 // Some carriers require reset duplication detection after airplane mode.
284 if (res.getBoolean(R.bool.reset_duplicate_detection_on_airplane_mode)) {
285 dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime);
286 }
287
288 List<SmsCbMessage> cbMessages = new ArrayList<>();
289
290 try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI,
291 // TODO: QUERY_COLUMNS_FWK is a hidden API, since we are going to move
292 // CellBroadcastProvider to this module we can define those COLUMNS in side
293 // CellBroadcastProvider and reference from there.
294 CellBroadcasts.QUERY_COLUMNS_FWK,
295 where,
296 new String[] {Long.toString(dupCheckTime)},
297 null)) {
298 if (cursor != null) {
299 while (cursor.moveToNext()) {
300 cbMessages.add(SmsCbMessage.createFromCursor(cursor));
301 }
302 }
303 }
304
305 boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body);
306
307 log("Found " + cbMessages.size() + " messages since "
308 + DateFormat.getDateTimeInstance().format(dupCheckTime));
309 for (SmsCbMessage messageToCheck : cbMessages) {
310 // If messages are from different slots, then we only compare the message body.
311 if (message.getSlotIndex() != messageToCheck.getSlotIndex()) {
312 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
313 log("Duplicate message detected from different slot. " + message);
314 return true;
315 }
316 } else {
317 // Check serial number if message is from the same carrier.
318 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
319 // Not a dup. Check next one.
320 log("Serial number check. Not a dup. " + messageToCheck);
321 continue;
322 }
323
Jack Yu2003c522019-10-31 09:47:20 -0700324 // ETWS primary / secondary should be treated differently.
325 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
326 && message.getEtwsWarningInfo().isPrimary()
327 != messageToCheck.getEtwsWarningInfo().isPrimary()) {
Jack Yua1309272019-10-24 00:19:49 -0700328 // Not a dup. Check next one.
329 log("Service category check. Not a dup. " + messageToCheck);
330 continue;
331 }
332
Jack Yu2003c522019-10-31 09:47:20 -0700333 // Check if the message category is different. Some carriers send cell broadcast
334 // messages on different techs (i.e. GSM / CDMA), so we need to compare service
335 // category cross techs.
336 if (message.getServiceCategory() != messageToCheck.getServiceCategory()
337 && mServiceCategoryCrossRATMap.get(message.getServiceCategory())
338 != messageToCheck.getServiceCategory()
339 && mServiceCategoryCrossRATMap.get(messageToCheck.getServiceCategory())
340 != message.getServiceCategory()) {
Jack Yua1309272019-10-24 00:19:49 -0700341 // Not a dup. Check next one.
342 log("ETWS primary check. Not a dup. " + messageToCheck);
343 continue;
344 }
345
346 // Compare message body if needed.
347 if (!compareMessageBody || TextUtils.equals(
348 message.getMessageBody(), messageToCheck.getMessageBody())) {
349 log("Duplicate message detected. " + message);
350 return true;
351 }
352 }
353 }
354
355 log("Not a duplicate message. " + message);
356 return false;
357 }
358
359 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700360 * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
361 * {@code location} is inside the {@code broadcastArea}.
362 * @param message the message need to geo-fencing check
363 * @param uri the message's uri
364 * @param broadcastArea the broadcast area of the message
365 * @param location current location
366 */
367 protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
368 LatLng location, int slotIndex) {
369
370 if (DBG) {
371 logd("Perform geo-fencing check for message identifier = "
372 + message.getServiceCategory()
373 + " serialNumber = " + message.getSerialNumber());
374 }
375
376 for (Geometry geo : broadcastArea) {
377 if (geo.contains(location)) {
378 broadcastMessage(message, uri, slotIndex);
379 return;
380 }
381 }
382
383 if (DBG) {
384 logd("Device location is outside the broadcast area "
385 + CbGeoUtils.encodeGeometriesToString(broadcastArea));
386 }
387 }
388
389 /**
390 * Request a single location update.
391 * @param callback a callback will be called when the location is available.
392 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
393 * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
394 */
395 protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
396 mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
397 }
398
399 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700400 * Broadcast the {@code message} to the applications.
401 * @param message a message need to broadcast
402 * @param messageUri message's uri
403 */
404 protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
405 int slotIndex) {
406 String receiverPermission;
407 int appOp;
408 String msg;
409 Intent intent;
410 if (message.isEmergencyMessage()) {
411 msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
412 log(msg);
413 mLocalLog.log(msg);
414 intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
415 //Emergency alerts need to be delivered with high priority
416 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
417 receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
418 appOp = AppOpsManager.OP_RECEIVE_EMERGECY_SMS;
419
420 intent.putExtra(EXTRA_MESSAGE, message);
421 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
422
423 if (Build.IS_DEBUGGABLE) {
424 // Send additional broadcast intent to the specified package. This is only for sl4a
425 // automation tests.
426 final String additionalPackage = Settings.Secure.getString(
427 mContext.getContentResolver(), CMAS_ADDITIONAL_BROADCAST_PKG);
428 if (additionalPackage != null) {
429 Intent additionalIntent = new Intent(intent);
430 additionalIntent.setPackage(additionalPackage);
431 mContext.sendOrderedBroadcastAsUser(additionalIntent, UserHandle.ALL,
432 receiverPermission, appOp, null, getHandler(), Activity.RESULT_OK,
433 null, null);
434 }
435 }
436
437 String[] pkgs = mContext.getResources().getStringArray(
438 com.android.internal.R.array.config_defaultCellBroadcastReceiverPkgs);
439 mReceiverCount.addAndGet(pkgs.length);
440 for (String pkg : pkgs) {
441 // Explicitly send the intent to all the configured cell broadcast receivers.
442 intent.setPackage(pkg);
443 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission,
444 appOp, mReceiver, getHandler(), Activity.RESULT_OK, null, null);
445 }
446 } else {
447 msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
448 log(msg);
449 mLocalLog.log(msg);
450 intent = new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION);
451 // Send implicit intent since there are various 3rd party carrier apps listen to
452 // this intent.
453 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
454 receiverPermission = Manifest.permission.RECEIVE_SMS;
455 appOp = AppOpsManager.OP_RECEIVE_SMS;
456
457 intent.putExtra(EXTRA_MESSAGE, message);
458 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
459
460 mReceiverCount.incrementAndGet();
461 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission, appOp,
462 mReceiver, getHandler(), Activity.RESULT_OK, null, null);
463 }
464
465 if (messageUri != null) {
466 ContentValues cv = new ContentValues();
467 cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
Chen Xu93fdfeb2019-10-21 22:56:37 -0700468 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
Jordan Liuc872fad2019-10-11 11:42:03 -0700469 CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
470 }
471 }
472
Jack Yua1309272019-10-24 00:19:49 -0700473 /**
474 * Get the device resource based on SIM
475 *
476 * @param subId Subscription index
477 *
478 * @return The resource
479 */
480 public @NonNull Resources getResources(int subId) {
481 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
482 || !SubscriptionManager.isValidSubscriptionId(subId)) {
483 return mContext.getResources();
484 }
485
486 if (mResourcesCache.containsKey(subId)) {
487 return mResourcesCache.get(subId);
488 }
489
490 Resources res = SubscriptionManager.getResourcesForSubId(mContext, subId);
491 mResourcesCache.put(subId, res);
492
493 return res;
494 }
495
Jordan Liuc872fad2019-10-11 11:42:03 -0700496 @Override
497 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
498 pw.println("CellBroadcastHandler:");
499 mLocalLog.dump(fd, pw, args);
500 pw.flush();
501 }
502
503 /** The callback interface of a location request. */
504 public interface LocationUpdateCallback {
505 /**
506 * Call when the location update is available.
507 * @param location a location in (latitude, longitude) format, or {@code null} if the
508 * location service is not available.
509 */
510 void onLocationUpdate(@Nullable LatLng location);
511 }
512
513 private static final class LocationRequester {
514 private static final String TAG = LocationRequester.class.getSimpleName();
515
516 /**
517 * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
518 * Most of the location request should be responded within 20 seconds.
519 */
520 private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 20;
521
522 /**
523 * Trigger this event when the {@link LocationManager} is not responded within the given
524 * time.
525 */
526 private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
527
528 /** Request a single location update. */
529 private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
530
531 /**
532 * Request location update from network or gps location provider. Network provider will be
533 * used if available, otherwise use the gps provider.
534 */
535 private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
536 LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
537
538 private final LocationManager mLocationManager;
539 private final Looper mLooper;
540 private final List<LocationUpdateCallback> mCallbacks;
541 private final Context mContext;
542 private Handler mLocationHandler;
543
544 LocationRequester(Context context, LocationManager locationManager, Looper looper) {
545 mLocationManager = locationManager;
546 mLooper = looper;
547 mCallbacks = new ArrayList<>();
548 mContext = context;
549 mLocationHandler = new LocationHandler(looper);
550 }
551
552 /**
553 * Request a single location update. If the location is not available, a callback with
554 * {@code null} location will be called immediately.
555 *
556 * @param callback a callback to the response when the location is available
557 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
558 * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
559 * called.
560 */
561 void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
562 int maximumWaitTimeSec) {
563 mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
564 0 /* arg2 */, callback).sendToTarget();
565 }
566
567 private void onLocationUpdate(@Nullable LatLng location) {
568 for (LocationUpdateCallback callback : mCallbacks) {
569 callback.onLocationUpdate(location);
570 }
571 mCallbacks.clear();
572 }
573
574 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
575 int maximumWaitTimeSec) {
576 if (DBG) Log.d(TAG, "requestLocationUpdate");
577 if (!isLocationServiceAvailable()) {
578 if (DBG) {
579 Log.d(TAG, "Can't request location update because of no location permission");
580 }
581 callback.onLocationUpdate(null);
582 return;
583 }
584
585 if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
586 if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
587 maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
588 }
589 mLocationHandler.sendMessageDelayed(
590 mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
591 maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
592 }
593
594 mCallbacks.add(callback);
595
596 for (String provider : LOCATION_PROVIDERS) {
597 if (mLocationManager.isProviderEnabled(provider)) {
598 mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper);
599 break;
600 }
601 }
602 }
603
604 private boolean isLocationServiceAvailable() {
605 if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
606 && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
607 for (String provider : LOCATION_PROVIDERS) {
608 if (mLocationManager.isProviderEnabled(provider)) return true;
609 }
610 return false;
611 }
612
613 private boolean hasPermission(String permission) {
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -0700614 return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
615 == PackageManager.PERMISSION_GRANTED;
Jordan Liuc872fad2019-10-11 11:42:03 -0700616 }
617
618 private final LocationListener mLocationListener = new LocationListener() {
619 @Override
620 public void onLocationChanged(Location location) {
621 mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
622 onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude()));
623 }
624
625 @Override
626 public void onStatusChanged(String provider, int status, Bundle extras) {}
627
628 @Override
629 public void onProviderEnabled(String provider) {}
630
631 @Override
632 public void onProviderDisabled(String provider) {}
633 };
634
635 private final class LocationHandler extends Handler {
636 LocationHandler(Looper looper) {
637 super(looper);
638 }
639
640 @Override
641 public void handleMessage(Message msg) {
642 switch (msg.what) {
643 case EVENT_LOCATION_REQUEST_TIMEOUT:
644 if (DBG) Log.d(TAG, "location request timeout");
645 onLocationUpdate(null);
646 break;
647 case EVENT_REQUEST_LOCATION_UPDATE:
648 requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
649 break;
650 default:
651 Log.e(TAG, "Unsupported message type " + msg.what);
652 }
653 }
654 }
655 }
656}