blob: 5a8d616f0095489c6ad0d4861695d630573b7d91 [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 Yua1309272019-10-24 00:19:49 -070051import android.text.TextUtils;
Jordan Liuc872fad2019-10-11 11:42:03 -070052import android.text.format.DateUtils;
53import android.util.LocalLog;
54import android.util.Log;
55
Jack Yua1309272019-10-24 00:19:49 -070056import com.android.internal.annotations.VisibleForTesting;
Jordan Liuc872fad2019-10-11 11:42:03 -070057import com.android.internal.telephony.CbGeoUtils.Geometry;
58import com.android.internal.telephony.CbGeoUtils.LatLng;
59import com.android.internal.telephony.metrics.TelephonyMetrics;
60
61import java.io.FileDescriptor;
62import java.io.PrintWriter;
Jack Yua1309272019-10-24 00:19:49 -070063import java.text.DateFormat;
Jordan Liuc872fad2019-10-11 11:42:03 -070064import java.util.ArrayList;
65import java.util.Arrays;
Jack Yua1309272019-10-24 00:19:49 -070066import java.util.HashMap;
Jordan Liuc872fad2019-10-11 11:42:03 -070067import java.util.List;
Jack Yua1309272019-10-24 00:19:49 -070068import java.util.Map;
Jordan Liuc872fad2019-10-11 11:42:03 -070069
70/**
71 * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
72 * completes and our result receiver is called.
73 */
74public class CellBroadcastHandler extends WakeLockStateMachine {
75 private static final String EXTRA_MESSAGE = "message";
76
77 private final LocalLog mLocalLog = new LocalLog(100);
78
Jordan Liu26e820c2019-10-22 14:42:07 -070079 private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
80
Jordan Liuc872fad2019-10-11 11:42:03 -070081 /** Uses to request the location update. */
82 public final LocationRequester mLocationRequester;
83
Jack Yua1309272019-10-24 00:19:49 -070084 /** Timestamp of last airplane mode on */
85 private long mLastAirplaneModeTime = 0;
86
87 // Resource cache
88 private final Map<Integer, Resources> mResourcesCache = new HashMap<>();
89
Jordan Liuc872fad2019-10-11 11:42:03 -070090 private CellBroadcastHandler(Context context) {
91 this("CellBroadcastHandler", context);
92 }
93
94 protected CellBroadcastHandler(String debugTag, Context context) {
95 super(debugTag, context);
96 mLocationRequester = new LocationRequester(
97 context,
98 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
99 getHandler().getLooper());
Jack Yua1309272019-10-24 00:19:49 -0700100
101 mContext.registerReceiver(
102 new BroadcastReceiver() {
103 @Override
104 public void onReceive(Context context, Intent intent) {
105 boolean airplaneModeOn = intent.getBooleanExtra("state", false);
106 if (airplaneModeOn) {
107 mLastAirplaneModeTime = System.currentTimeMillis();
108 log("Airplane mode on. Reset duplicate detection.");
109 }
110 }
111 },
112 new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
Jordan Liuc872fad2019-10-11 11:42:03 -0700113 }
114
115 /**
116 * Create a new CellBroadcastHandler.
117 * @param context the context to use for dispatching Intents
118 * @return the new handler
119 */
120 public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
121 CellBroadcastHandler handler = new CellBroadcastHandler(context);
122 handler.start();
123 return handler;
124 }
125
126 /**
127 * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
128 * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
129 *
130 * @param message the message to process
131 * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
132 */
133 @Override
134 protected boolean handleSmsMessage(Message message) {
135 if (message.obj instanceof SmsCbMessage) {
Jack Yua1309272019-10-24 00:19:49 -0700136 if (!isDuplicate((SmsCbMessage) message.obj)) {
137 handleBroadcastSms((SmsCbMessage) message.obj);
138 return true;
139 }
140 return false;
Jordan Liuc872fad2019-10-11 11:42:03 -0700141 } else {
142 loge("handleMessage got object of type: " + message.obj.getClass().getName());
143 return false;
144 }
145 }
146
147 /**
148 * Dispatch a Cell Broadcast message to listeners.
149 * @param message the Cell Broadcast to broadcast
150 */
151 protected void handleBroadcastSms(SmsCbMessage message) {
152 int slotIndex = message.getSlotIndex();
153 // Log Cellbroadcast msg received event
154 TelephonyMetrics metrics = TelephonyMetrics.getInstance();
155 metrics.writeNewCBSms(slotIndex, message.getMessageFormat(),
156 message.getMessagePriority(), message.isCmasMessage(), message.isEtwsMessage(),
157 message.getServiceCategory(), message.getSerialNumber(),
158 System.currentTimeMillis());
159
160 // TODO: Database inserting can be time consuming, therefore this should be changed to
161 // asynchronous.
162 ContentValues cv = message.getContentValues();
Chen Xu93fdfeb2019-10-21 22:56:37 -0700163 Uri uri = mContext.getContentResolver().insert(CellBroadcasts.CONTENT_URI, cv);
Jordan Liuc872fad2019-10-11 11:42:03 -0700164
165 if (message.needGeoFencingCheck()) {
166 if (DBG) {
167 log("Request location update for geo-fencing. serialNumber = "
168 + message.getSerialNumber());
169 }
170
171 requestLocationUpdate(location -> {
172 if (location == null) {
173 // Broadcast the message directly if the location is not available.
174 broadcastMessage(message, uri, slotIndex);
175 } else {
176 performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
177 }
178 }, message.getMaximumWaitingTime());
179 } else {
180 if (DBG) {
181 log("Broadcast the message directly because no geo-fencing required, "
182 + "serialNumber = " + message.getSerialNumber()
183 + " needGeoFencing = " + message.needGeoFencingCheck());
184 }
185 broadcastMessage(message, uri, slotIndex);
186 }
187 }
188
189 /**
Jack Yua1309272019-10-24 00:19:49 -0700190 * Check if the message is a duplicate
191 *
192 * @param message Cell broadcast message
193 * @return {@code true} if this message is a duplicate
194 */
195 @VisibleForTesting
196 public boolean isDuplicate(SmsCbMessage message) {
197 // Find the cell broadcast message identify by the message identifier and serial number
198 // and is not broadcasted.
199 String where = CellBroadcasts.RECEIVED_TIME + ">?";
200
201 int slotIndex = message.getSlotIndex();
202 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
203 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
204 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
205 Resources res;
206 if (subIds != null) {
207 res = getResources(subIds[0]);
208 } else {
209 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
210 }
211
212 // Only consider cell broadcast messages received within certain period.
213 // By default it's 24 hours.
214 long expirationDuration = res.getInteger(R.integer.message_expiration_time);
215 long dupCheckTime = System.currentTimeMillis() - expirationDuration;
216
217 // Some carriers require reset duplication detection after airplane mode.
218 if (res.getBoolean(R.bool.reset_duplicate_detection_on_airplane_mode)) {
219 dupCheckTime = Long.max(dupCheckTime, mLastAirplaneModeTime);
220 }
221
222 List<SmsCbMessage> cbMessages = new ArrayList<>();
223
224 try (Cursor cursor = mContext.getContentResolver().query(CellBroadcasts.CONTENT_URI,
225 // TODO: QUERY_COLUMNS_FWK is a hidden API, since we are going to move
226 // CellBroadcastProvider to this module we can define those COLUMNS in side
227 // CellBroadcastProvider and reference from there.
228 CellBroadcasts.QUERY_COLUMNS_FWK,
229 where,
230 new String[] {Long.toString(dupCheckTime)},
231 null)) {
232 if (cursor != null) {
233 while (cursor.moveToNext()) {
234 cbMessages.add(SmsCbMessage.createFromCursor(cursor));
235 }
236 }
237 }
238
239 boolean compareMessageBody = res.getBoolean(R.bool.duplicate_compare_body);
240
241 log("Found " + cbMessages.size() + " messages since "
242 + DateFormat.getDateTimeInstance().format(dupCheckTime));
243 for (SmsCbMessage messageToCheck : cbMessages) {
244 // If messages are from different slots, then we only compare the message body.
245 if (message.getSlotIndex() != messageToCheck.getSlotIndex()) {
246 if (TextUtils.equals(message.getMessageBody(), messageToCheck.getMessageBody())) {
247 log("Duplicate message detected from different slot. " + message);
248 return true;
249 }
250 } else {
251 // Check serial number if message is from the same carrier.
252 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
253 // Not a dup. Check next one.
254 log("Serial number check. Not a dup. " + messageToCheck);
255 continue;
256 }
257
258 if (message.getServiceCategory() != messageToCheck.getServiceCategory()) {
259 // Not a dup. Check next one.
260 log("Service category check. Not a dup. " + messageToCheck);
261 continue;
262 }
263
264 // ETWS primary / secondary should be treated differently.
265 if (message.isEtwsMessage() && messageToCheck.isEtwsMessage()
266 && message.getEtwsWarningInfo().isPrimary()
267 != messageToCheck.getEtwsWarningInfo().isPrimary()) {
268 // Not a dup. Check next one.
269 log("ETWS primary check. Not a dup. " + messageToCheck);
270 continue;
271 }
272
273 // Compare message body if needed.
274 if (!compareMessageBody || TextUtils.equals(
275 message.getMessageBody(), messageToCheck.getMessageBody())) {
276 log("Duplicate message detected. " + message);
277 return true;
278 }
279 }
280 }
281
282 log("Not a duplicate message. " + message);
283 return false;
284 }
285
286 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700287 * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
288 * {@code location} is inside the {@code broadcastArea}.
289 * @param message the message need to geo-fencing check
290 * @param uri the message's uri
291 * @param broadcastArea the broadcast area of the message
292 * @param location current location
293 */
294 protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
295 LatLng location, int slotIndex) {
296
297 if (DBG) {
298 logd("Perform geo-fencing check for message identifier = "
299 + message.getServiceCategory()
300 + " serialNumber = " + message.getSerialNumber());
301 }
302
303 for (Geometry geo : broadcastArea) {
304 if (geo.contains(location)) {
305 broadcastMessage(message, uri, slotIndex);
306 return;
307 }
308 }
309
310 if (DBG) {
311 logd("Device location is outside the broadcast area "
312 + CbGeoUtils.encodeGeometriesToString(broadcastArea));
313 }
314 }
315
316 /**
317 * Request a single location update.
318 * @param callback a callback will be called when the location is available.
319 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
320 * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
321 */
322 protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
323 mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
324 }
325
326 /**
Jordan Liuc872fad2019-10-11 11:42:03 -0700327 * Broadcast the {@code message} to the applications.
328 * @param message a message need to broadcast
329 * @param messageUri message's uri
330 */
331 protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
332 int slotIndex) {
333 String receiverPermission;
334 int appOp;
335 String msg;
336 Intent intent;
337 if (message.isEmergencyMessage()) {
338 msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
339 log(msg);
340 mLocalLog.log(msg);
341 intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
342 //Emergency alerts need to be delivered with high priority
343 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
344 receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
345 appOp = AppOpsManager.OP_RECEIVE_EMERGECY_SMS;
346
347 intent.putExtra(EXTRA_MESSAGE, message);
348 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
349
350 if (Build.IS_DEBUGGABLE) {
351 // Send additional broadcast intent to the specified package. This is only for sl4a
352 // automation tests.
353 final String additionalPackage = Settings.Secure.getString(
354 mContext.getContentResolver(), CMAS_ADDITIONAL_BROADCAST_PKG);
355 if (additionalPackage != null) {
356 Intent additionalIntent = new Intent(intent);
357 additionalIntent.setPackage(additionalPackage);
358 mContext.sendOrderedBroadcastAsUser(additionalIntent, UserHandle.ALL,
359 receiverPermission, appOp, null, getHandler(), Activity.RESULT_OK,
360 null, null);
361 }
362 }
363
364 String[] pkgs = mContext.getResources().getStringArray(
365 com.android.internal.R.array.config_defaultCellBroadcastReceiverPkgs);
366 mReceiverCount.addAndGet(pkgs.length);
367 for (String pkg : pkgs) {
368 // Explicitly send the intent to all the configured cell broadcast receivers.
369 intent.setPackage(pkg);
370 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission,
371 appOp, mReceiver, getHandler(), Activity.RESULT_OK, null, null);
372 }
373 } else {
374 msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
375 log(msg);
376 mLocalLog.log(msg);
377 intent = new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION);
378 // Send implicit intent since there are various 3rd party carrier apps listen to
379 // this intent.
380 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
381 receiverPermission = Manifest.permission.RECEIVE_SMS;
382 appOp = AppOpsManager.OP_RECEIVE_SMS;
383
384 intent.putExtra(EXTRA_MESSAGE, message);
385 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
386
387 mReceiverCount.incrementAndGet();
388 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission, appOp,
389 mReceiver, getHandler(), Activity.RESULT_OK, null, null);
390 }
391
392 if (messageUri != null) {
393 ContentValues cv = new ContentValues();
394 cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
Chen Xu93fdfeb2019-10-21 22:56:37 -0700395 mContext.getContentResolver().update(CellBroadcasts.CONTENT_URI, cv,
Jordan Liuc872fad2019-10-11 11:42:03 -0700396 CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
397 }
398 }
399
Jack Yua1309272019-10-24 00:19:49 -0700400 /**
401 * Get the device resource based on SIM
402 *
403 * @param subId Subscription index
404 *
405 * @return The resource
406 */
407 public @NonNull Resources getResources(int subId) {
408 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
409 || !SubscriptionManager.isValidSubscriptionId(subId)) {
410 return mContext.getResources();
411 }
412
413 if (mResourcesCache.containsKey(subId)) {
414 return mResourcesCache.get(subId);
415 }
416
417 Resources res = SubscriptionManager.getResourcesForSubId(mContext, subId);
418 mResourcesCache.put(subId, res);
419
420 return res;
421 }
422
Jordan Liuc872fad2019-10-11 11:42:03 -0700423 @Override
424 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
425 pw.println("CellBroadcastHandler:");
426 mLocalLog.dump(fd, pw, args);
427 pw.flush();
428 }
429
430 /** The callback interface of a location request. */
431 public interface LocationUpdateCallback {
432 /**
433 * Call when the location update is available.
434 * @param location a location in (latitude, longitude) format, or {@code null} if the
435 * location service is not available.
436 */
437 void onLocationUpdate(@Nullable LatLng location);
438 }
439
440 private static final class LocationRequester {
441 private static final String TAG = LocationRequester.class.getSimpleName();
442
443 /**
444 * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
445 * Most of the location request should be responded within 20 seconds.
446 */
447 private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 20;
448
449 /**
450 * Trigger this event when the {@link LocationManager} is not responded within the given
451 * time.
452 */
453 private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
454
455 /** Request a single location update. */
456 private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
457
458 /**
459 * Request location update from network or gps location provider. Network provider will be
460 * used if available, otherwise use the gps provider.
461 */
462 private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
463 LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
464
465 private final LocationManager mLocationManager;
466 private final Looper mLooper;
467 private final List<LocationUpdateCallback> mCallbacks;
468 private final Context mContext;
469 private Handler mLocationHandler;
470
471 LocationRequester(Context context, LocationManager locationManager, Looper looper) {
472 mLocationManager = locationManager;
473 mLooper = looper;
474 mCallbacks = new ArrayList<>();
475 mContext = context;
476 mLocationHandler = new LocationHandler(looper);
477 }
478
479 /**
480 * Request a single location update. If the location is not available, a callback with
481 * {@code null} location will be called immediately.
482 *
483 * @param callback a callback to the response when the location is available
484 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
485 * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
486 * called.
487 */
488 void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
489 int maximumWaitTimeSec) {
490 mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
491 0 /* arg2 */, callback).sendToTarget();
492 }
493
494 private void onLocationUpdate(@Nullable LatLng location) {
495 for (LocationUpdateCallback callback : mCallbacks) {
496 callback.onLocationUpdate(location);
497 }
498 mCallbacks.clear();
499 }
500
501 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
502 int maximumWaitTimeSec) {
503 if (DBG) Log.d(TAG, "requestLocationUpdate");
504 if (!isLocationServiceAvailable()) {
505 if (DBG) {
506 Log.d(TAG, "Can't request location update because of no location permission");
507 }
508 callback.onLocationUpdate(null);
509 return;
510 }
511
512 if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
513 if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
514 maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
515 }
516 mLocationHandler.sendMessageDelayed(
517 mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
518 maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
519 }
520
521 mCallbacks.add(callback);
522
523 for (String provider : LOCATION_PROVIDERS) {
524 if (mLocationManager.isProviderEnabled(provider)) {
525 mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper);
526 break;
527 }
528 }
529 }
530
531 private boolean isLocationServiceAvailable() {
532 if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
533 && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
534 for (String provider : LOCATION_PROVIDERS) {
535 if (mLocationManager.isProviderEnabled(provider)) return true;
536 }
537 return false;
538 }
539
540 private boolean hasPermission(String permission) {
Philip P. Moltmann376f9fa2019-10-30 15:25:24 -0700541 return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
542 == PackageManager.PERMISSION_GRANTED;
Jordan Liuc872fad2019-10-11 11:42:03 -0700543 }
544
545 private final LocationListener mLocationListener = new LocationListener() {
546 @Override
547 public void onLocationChanged(Location location) {
548 mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
549 onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude()));
550 }
551
552 @Override
553 public void onStatusChanged(String provider, int status, Bundle extras) {}
554
555 @Override
556 public void onProviderEnabled(String provider) {}
557
558 @Override
559 public void onProviderDisabled(String provider) {}
560 };
561
562 private final class LocationHandler extends Handler {
563 LocationHandler(Looper looper) {
564 super(looper);
565 }
566
567 @Override
568 public void handleMessage(Message msg) {
569 switch (msg.what) {
570 case EVENT_LOCATION_REQUEST_TIMEOUT:
571 if (DBG) Log.d(TAG, "location request timeout");
572 onLocationUpdate(null);
573 break;
574 case EVENT_REQUEST_LOCATION_UPDATE:
575 requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
576 break;
577 default:
578 Log.e(TAG, "Unsupported message type " + msg.what);
579 }
580 }
581 }
582 }
583}