blob: 90779b63d224b6991f088ee65b5b8ac07e4358f4 [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
19import static android.content.PermissionChecker.PERMISSION_GRANTED;
20import static android.provider.Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG;
21
22import android.Manifest;
23import android.annotation.NonNull;
24import android.annotation.Nullable;
25import android.app.Activity;
26import android.app.AppOpsManager;
27import android.content.ContentValues;
28import android.content.Context;
29import android.content.Intent;
30import android.content.PermissionChecker;
31import android.location.Location;
32import android.location.LocationListener;
33import android.location.LocationManager;
34import android.net.Uri;
35import android.os.Build;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.Looper;
39import android.os.Message;
40import android.os.UserHandle;
41import android.provider.Settings;
42import android.provider.Telephony;
43import android.provider.Telephony.CellBroadcasts;
44import android.telephony.SmsCbMessage;
45import android.telephony.SubscriptionManager;
46import android.text.format.DateUtils;
47import android.util.LocalLog;
48import android.util.Log;
49
50import com.android.internal.telephony.CbGeoUtils.Geometry;
51import com.android.internal.telephony.CbGeoUtils.LatLng;
52import com.android.internal.telephony.metrics.TelephonyMetrics;
53
54import java.io.FileDescriptor;
55import java.io.PrintWriter;
56import java.util.ArrayList;
57import java.util.Arrays;
58import java.util.List;
59
60/**
61 * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
62 * completes and our result receiver is called.
63 */
64public class CellBroadcastHandler extends WakeLockStateMachine {
65 private static final String EXTRA_MESSAGE = "message";
66
67 private final LocalLog mLocalLog = new LocalLog(100);
68
69 protected static final Uri CELL_BROADCAST_URI = Uri.parse("content://cellbroadcasts_fwk");
70
71 /** Uses to request the location update. */
72 public final LocationRequester mLocationRequester;
73
74 private CellBroadcastHandler(Context context) {
75 this("CellBroadcastHandler", context);
76 }
77
78 protected CellBroadcastHandler(String debugTag, Context context) {
79 super(debugTag, context);
80 mLocationRequester = new LocationRequester(
81 context,
82 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
83 getHandler().getLooper());
84 }
85
86 /**
87 * Create a new CellBroadcastHandler.
88 * @param context the context to use for dispatching Intents
89 * @return the new handler
90 */
91 public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
92 CellBroadcastHandler handler = new CellBroadcastHandler(context);
93 handler.start();
94 return handler;
95 }
96
97 /**
98 * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
99 * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
100 *
101 * @param message the message to process
102 * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
103 */
104 @Override
105 protected boolean handleSmsMessage(Message message) {
106 if (message.obj instanceof SmsCbMessage) {
107 handleBroadcastSms((SmsCbMessage) message.obj);
108 return true;
109 } else {
110 loge("handleMessage got object of type: " + message.obj.getClass().getName());
111 return false;
112 }
113 }
114
115 /**
116 * Dispatch a Cell Broadcast message to listeners.
117 * @param message the Cell Broadcast to broadcast
118 */
119 protected void handleBroadcastSms(SmsCbMessage message) {
120 int slotIndex = message.getSlotIndex();
121 // Log Cellbroadcast msg received event
122 TelephonyMetrics metrics = TelephonyMetrics.getInstance();
123 metrics.writeNewCBSms(slotIndex, message.getMessageFormat(),
124 message.getMessagePriority(), message.isCmasMessage(), message.isEtwsMessage(),
125 message.getServiceCategory(), message.getSerialNumber(),
126 System.currentTimeMillis());
127
128 // TODO: Database inserting can be time consuming, therefore this should be changed to
129 // asynchronous.
130 ContentValues cv = message.getContentValues();
131 Uri uri = mContext.getContentResolver().insert(CELL_BROADCAST_URI, cv);
132
133 if (message.needGeoFencingCheck()) {
134 if (DBG) {
135 log("Request location update for geo-fencing. serialNumber = "
136 + message.getSerialNumber());
137 }
138
139 requestLocationUpdate(location -> {
140 if (location == null) {
141 // Broadcast the message directly if the location is not available.
142 broadcastMessage(message, uri, slotIndex);
143 } else {
144 performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
145 }
146 }, message.getMaximumWaitingTime());
147 } else {
148 if (DBG) {
149 log("Broadcast the message directly because no geo-fencing required, "
150 + "serialNumber = " + message.getSerialNumber()
151 + " needGeoFencing = " + message.needGeoFencingCheck());
152 }
153 broadcastMessage(message, uri, slotIndex);
154 }
155 }
156
157 /**
158 * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
159 * {@code location} is inside the {@code broadcastArea}.
160 * @param message the message need to geo-fencing check
161 * @param uri the message's uri
162 * @param broadcastArea the broadcast area of the message
163 * @param location current location
164 */
165 protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
166 LatLng location, int slotIndex) {
167
168 if (DBG) {
169 logd("Perform geo-fencing check for message identifier = "
170 + message.getServiceCategory()
171 + " serialNumber = " + message.getSerialNumber());
172 }
173
174 for (Geometry geo : broadcastArea) {
175 if (geo.contains(location)) {
176 broadcastMessage(message, uri, slotIndex);
177 return;
178 }
179 }
180
181 if (DBG) {
182 logd("Device location is outside the broadcast area "
183 + CbGeoUtils.encodeGeometriesToString(broadcastArea));
184 }
185 }
186
187 /**
188 * Request a single location update.
189 * @param callback a callback will be called when the location is available.
190 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
191 * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
192 */
193 protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
194 mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
195 }
196
197 /**
198 * Broadcast a list of cell broadcast messages.
199 * @param cbMessages a list of cell broadcast message.
200 * @param cbMessageUris the corresponding {@link Uri} of the cell broadcast messages.
201 */
202 protected void broadcastMessage(List<SmsCbMessage> cbMessages, List<Uri> cbMessageUris,
203 int slotIndex) {
204 for (int i = 0; i < cbMessages.size(); i++) {
205 broadcastMessage(cbMessages.get(i), cbMessageUris.get(i), slotIndex);
206 }
207 }
208
209 /**
210 * Broadcast the {@code message} to the applications.
211 * @param message a message need to broadcast
212 * @param messageUri message's uri
213 */
214 protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
215 int slotIndex) {
216 String receiverPermission;
217 int appOp;
218 String msg;
219 Intent intent;
220 if (message.isEmergencyMessage()) {
221 msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
222 log(msg);
223 mLocalLog.log(msg);
224 intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
225 //Emergency alerts need to be delivered with high priority
226 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
227 receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
228 appOp = AppOpsManager.OP_RECEIVE_EMERGECY_SMS;
229
230 intent.putExtra(EXTRA_MESSAGE, message);
231 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
232
233 if (Build.IS_DEBUGGABLE) {
234 // Send additional broadcast intent to the specified package. This is only for sl4a
235 // automation tests.
236 final String additionalPackage = Settings.Secure.getString(
237 mContext.getContentResolver(), CMAS_ADDITIONAL_BROADCAST_PKG);
238 if (additionalPackage != null) {
239 Intent additionalIntent = new Intent(intent);
240 additionalIntent.setPackage(additionalPackage);
241 mContext.sendOrderedBroadcastAsUser(additionalIntent, UserHandle.ALL,
242 receiverPermission, appOp, null, getHandler(), Activity.RESULT_OK,
243 null, null);
244 }
245 }
246
247 String[] pkgs = mContext.getResources().getStringArray(
248 com.android.internal.R.array.config_defaultCellBroadcastReceiverPkgs);
249 mReceiverCount.addAndGet(pkgs.length);
250 for (String pkg : pkgs) {
251 // Explicitly send the intent to all the configured cell broadcast receivers.
252 intent.setPackage(pkg);
253 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission,
254 appOp, mReceiver, getHandler(), Activity.RESULT_OK, null, null);
255 }
256 } else {
257 msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
258 log(msg);
259 mLocalLog.log(msg);
260 intent = new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION);
261 // Send implicit intent since there are various 3rd party carrier apps listen to
262 // this intent.
263 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
264 receiverPermission = Manifest.permission.RECEIVE_SMS;
265 appOp = AppOpsManager.OP_RECEIVE_SMS;
266
267 intent.putExtra(EXTRA_MESSAGE, message);
268 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
269
270 mReceiverCount.incrementAndGet();
271 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission, appOp,
272 mReceiver, getHandler(), Activity.RESULT_OK, null, null);
273 }
274
275 if (messageUri != null) {
276 ContentValues cv = new ContentValues();
277 cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
278 mContext.getContentResolver().update(CELL_BROADCAST_URI, cv,
279 CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
280 }
281 }
282
283 @Override
284 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
285 pw.println("CellBroadcastHandler:");
286 mLocalLog.dump(fd, pw, args);
287 pw.flush();
288 }
289
290 /** The callback interface of a location request. */
291 public interface LocationUpdateCallback {
292 /**
293 * Call when the location update is available.
294 * @param location a location in (latitude, longitude) format, or {@code null} if the
295 * location service is not available.
296 */
297 void onLocationUpdate(@Nullable LatLng location);
298 }
299
300 private static final class LocationRequester {
301 private static final String TAG = LocationRequester.class.getSimpleName();
302
303 /**
304 * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
305 * Most of the location request should be responded within 20 seconds.
306 */
307 private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 20;
308
309 /**
310 * Trigger this event when the {@link LocationManager} is not responded within the given
311 * time.
312 */
313 private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
314
315 /** Request a single location update. */
316 private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
317
318 /**
319 * Request location update from network or gps location provider. Network provider will be
320 * used if available, otherwise use the gps provider.
321 */
322 private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
323 LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
324
325 private final LocationManager mLocationManager;
326 private final Looper mLooper;
327 private final List<LocationUpdateCallback> mCallbacks;
328 private final Context mContext;
329 private Handler mLocationHandler;
330
331 LocationRequester(Context context, LocationManager locationManager, Looper looper) {
332 mLocationManager = locationManager;
333 mLooper = looper;
334 mCallbacks = new ArrayList<>();
335 mContext = context;
336 mLocationHandler = new LocationHandler(looper);
337 }
338
339 /**
340 * Request a single location update. If the location is not available, a callback with
341 * {@code null} location will be called immediately.
342 *
343 * @param callback a callback to the response when the location is available
344 * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
345 * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
346 * called.
347 */
348 void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
349 int maximumWaitTimeSec) {
350 mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
351 0 /* arg2 */, callback).sendToTarget();
352 }
353
354 private void onLocationUpdate(@Nullable LatLng location) {
355 for (LocationUpdateCallback callback : mCallbacks) {
356 callback.onLocationUpdate(location);
357 }
358 mCallbacks.clear();
359 }
360
361 private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
362 int maximumWaitTimeSec) {
363 if (DBG) Log.d(TAG, "requestLocationUpdate");
364 if (!isLocationServiceAvailable()) {
365 if (DBG) {
366 Log.d(TAG, "Can't request location update because of no location permission");
367 }
368 callback.onLocationUpdate(null);
369 return;
370 }
371
372 if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
373 if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
374 maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
375 }
376 mLocationHandler.sendMessageDelayed(
377 mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
378 maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
379 }
380
381 mCallbacks.add(callback);
382
383 for (String provider : LOCATION_PROVIDERS) {
384 if (mLocationManager.isProviderEnabled(provider)) {
385 mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper);
386 break;
387 }
388 }
389 }
390
391 private boolean isLocationServiceAvailable() {
392 if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
393 && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
394 for (String provider : LOCATION_PROVIDERS) {
395 if (mLocationManager.isProviderEnabled(provider)) return true;
396 }
397 return false;
398 }
399
400 private boolean hasPermission(String permission) {
401 return PermissionChecker.checkCallingOrSelfPermissionForDataDelivery(mContext,
402 permission) == PERMISSION_GRANTED;
403 }
404
405 private final LocationListener mLocationListener = new LocationListener() {
406 @Override
407 public void onLocationChanged(Location location) {
408 mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
409 onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude()));
410 }
411
412 @Override
413 public void onStatusChanged(String provider, int status, Bundle extras) {}
414
415 @Override
416 public void onProviderEnabled(String provider) {}
417
418 @Override
419 public void onProviderDisabled(String provider) {}
420 };
421
422 private final class LocationHandler extends Handler {
423 LocationHandler(Looper looper) {
424 super(looper);
425 }
426
427 @Override
428 public void handleMessage(Message msg) {
429 switch (msg.what) {
430 case EVENT_LOCATION_REQUEST_TIMEOUT:
431 if (DBG) Log.d(TAG, "location request timeout");
432 onLocationUpdate(null);
433 break;
434 case EVENT_REQUEST_LOCATION_UPDATE:
435 requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
436 break;
437 default:
438 Log.e(TAG, "Unsupported message type " + msg.what);
439 }
440 }
441 }
442 }
443}