Loading src/java/com/android/internal/telephony/CellBroadcastHandler.java +263 −4 Original line number Diff line number Diff line Loading @@ -16,36 +16,59 @@ package com.android.internal.telephony; import static android.content.PermissionChecker.PERMISSION_GRANTED; import static android.provider.Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.AppOpsManager; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.PermissionChecker; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.provider.Settings; import android.provider.Telephony; import android.provider.Telephony.CellBroadcasts; import android.telephony.SmsCbMessage; import android.telephony.SubscriptionManager; import android.util.LocalLog; import android.util.Log; import com.android.internal.telephony.CbGeoUtils.Geometry; import com.android.internal.telephony.CbGeoUtils.LatLng; import com.android.internal.telephony.metrics.TelephonyMetrics; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast * completes and our result receiver is called. */ public class CellBroadcastHandler extends WakeLockStateMachine { private static final String EXTRA_MESSAGE = "message"; private final LocalLog mLocalLog = new LocalLog(100); private static final String EXTRA_MESSAGE = "message"; protected static final Uri CELL_BROADCAST_URI = Uri.parse("content://cellbroadcasts_fwk"); /** Uses to request the location update. */ public final LocationRequester mLocationRequester; private CellBroadcastHandler(Context context, Phone phone) { this("CellBroadcastHandler", context, phone); Loading @@ -53,6 +76,10 @@ public class CellBroadcastHandler extends WakeLockStateMachine { protected CellBroadcastHandler(String debugTag, Context context, Phone phone) { super(debugTag, context, phone); mLocationRequester = new LocationRequester( context, (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE), getHandler().getLooper()); } /** Loading Loading @@ -89,9 +116,6 @@ public class CellBroadcastHandler extends WakeLockStateMachine { * @param message the Cell Broadcast to broadcast */ protected void handleBroadcastSms(SmsCbMessage message) { String receiverPermission; int appOp; // Log Cellbroadcast msg received event TelephonyMetrics metrics = TelephonyMetrics.getInstance(); metrics.writeNewCBSms(mPhone.getPhoneId(), message.getMessageFormat(), Loading @@ -99,6 +123,92 @@ public class CellBroadcastHandler extends WakeLockStateMachine { message.getServiceCategory(), message.getSerialNumber(), System.currentTimeMillis()); // TODO: Database inserting can be time consuming, therefore this should be changed to // asynchronous. ContentValues cv = message.getContentValues(); Uri uri = mContext.getContentResolver().insert(CELL_BROADCAST_URI, cv); if (message.needGeoFencingCheck()) { if (DBG) { log("Request location update for geo-fencing. serialNumber = " + message.getSerialNumber()); } requestLocationUpdate(location -> { if (location == null) { // Broadcast the message directly if the location is not available. broadcastMessage(message, uri); } else { performGeoFencing(message, uri, message.getGeometries(), location); } }); } else { if (DBG) { log("Broadcast the message directly because no geo-fencing required, " + "serialNumber = " + message.getSerialNumber() + " needGeoFencing = " + message.needGeoFencingCheck()); } broadcastMessage(message, uri); } } /** * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the * {@code location} is inside the {@code broadcastArea}. * @param message the message need to geo-fencing check * @param uri the message's uri * @param broadcastArea the broadcast area of the message * @param location current location */ protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea, LatLng location) { if (DBG) { logd("Perform geo-fencing check for message identifier = " + message.getServiceCategory() + " serialNumber = " + message.getSerialNumber()); } for (Geometry geo : broadcastArea) { if (geo.contains(location)) { broadcastMessage(message, uri); return; } } if (DBG) { logd("Device location is outside the broadcast area " + CbGeoUtils.encodeGeometriesToString(broadcastArea)); } } /** * Request a single location update. * @param callback a callback will be called when the location is available. */ protected void requestLocationUpdate(LocationUpdateCallback callback) { mLocationRequester.requestLocationUpdate(callback); } /** * Broadcast a list of cell broadcast messages. * @param cbMessages a list of cell broadcast message. * @param cbMessageUris the corresponding {@link Uri} of the cell broadcast messages. */ protected void broadcastMessage(List<SmsCbMessage> cbMessages, List<Uri> cbMessageUris) { for (int i = 0; i < cbMessages.size(); i++) { broadcastMessage(cbMessages.get(i), cbMessageUris.get(i)); } } /** * Broadcast the {@code message} to the applications. * @param message a message need to broadcast * @param messageUri message's uri */ protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri) { String receiverPermission; int appOp; String msg; Intent intent; if (message.isEmergencyMessage()) { Loading Loading @@ -155,6 +265,13 @@ public class CellBroadcastHandler extends WakeLockStateMachine { mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission, appOp, mReceiver, getHandler(), Activity.RESULT_OK, null, null); } if (messageUri != null) { ContentValues cv = new ContentValues(); cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1); mContext.getContentResolver().update(CELL_BROADCAST_URI, cv, CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()}); } } @Override Loading @@ -163,4 +280,146 @@ public class CellBroadcastHandler extends WakeLockStateMachine { mLocalLog.dump(fd, pw, args); pw.flush(); } /** The callback interface of a location request. */ public interface LocationUpdateCallback { /** * Call when the location update is available. * @param location a location in (latitude, longitude) format, or {@code null} if the * location service is not available. */ void onLocationUpdate(@Nullable LatLng location); } private static final class LocationRequester { private static final String TAG = LocationRequester.class.getSimpleName(); /** * Trigger this event when the {@link LocationManager} is not responded within the given * time. */ private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1; /** Request a single location update. */ private static final int EVENT_REQUEST_LOCATION_UPDATE = 2; /** Default expired time of the location request. */ private static final int LOCATION_REQUEST_TIMEOUT_MILLIS = 30 * 1000; /** * Request location update from network or gps location provider. Network provider will be * used if available, otherwise use the gps provider. */ private static final List<String> LOCATION_PROVIDERS = Arrays.asList( LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER); private final LocationManager mLocationManager; private final Looper mLooper; private final List<LocationUpdateCallback> mCallbacks; private final Context mContext; private Handler mLocationHandler; LocationRequester(Context context, LocationManager locationManager, Looper looper) { mLocationManager = locationManager; mLooper = looper; mCallbacks = new ArrayList<>(); mContext = context; mLocationHandler = new LocationHandler(looper); } /** * Request a single location update. If the location is not available, a callback with * {@code null} location will be called immediately. * @param callback a callback to the the response when the location is available */ void requestLocationUpdate(@NonNull LocationUpdateCallback callback) { mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, callback).sendToTarget(); } private void onLocationUpdate(@Nullable LatLng location) { for (LocationUpdateCallback callback : mCallbacks) { callback.onLocationUpdate(location); } mCallbacks.clear(); } private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback) { if (DBG) Log.d(TAG, "requestLocationUpdate"); if (!isLocationServiceAvailable()) { if (DBG) { Log.d(TAG, "Can't request location update because of no location permission"); } callback.onLocationUpdate(null); return; } // TODO: handle the "Geo-fencing Maximum Wait" defined in ATIS-0700041 Section 5.2.3 if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) { mLocationHandler.sendMessageDelayed( mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT), LOCATION_REQUEST_TIMEOUT_MILLIS); } mCallbacks.add(callback); for (String provider : LOCATION_PROVIDERS) { if (mLocationManager.isProviderEnabled(provider)) { mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper); break; } } } private boolean isLocationServiceAvailable() { if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION) && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false; for (String provider : LOCATION_PROVIDERS) { if (mLocationManager.isProviderEnabled(provider)) return true; } return false; } private boolean hasPermission(String permission) { return PermissionChecker.checkCallingOrSelfPermission(mContext, permission) == PERMISSION_GRANTED; } private final LocationListener mLocationListener = new LocationListener() { @Override public void onLocationChanged(Location location) { mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT); onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude())); } @Override public void onStatusChanged(String provider, int status, Bundle extras) {} @Override public void onProviderEnabled(String provider) {} @Override public void onProviderDisabled(String provider) {} }; private final class LocationHandler extends Handler { LocationHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case EVENT_LOCATION_REQUEST_TIMEOUT: if (DBG) Log.d(TAG, "location request timeout"); onLocationUpdate(null); break; case EVENT_REQUEST_LOCATION_UPDATE: requestLocationUpdateInternal((LocationUpdateCallback) msg.obj); break; default: Log.e(TAG, "Unsupported message type " + msg.what); } } } } } src/java/com/android/internal/telephony/WakeLockStateMachine.java +15 −16 Original line number Diff line number Diff line Loading @@ -38,7 +38,7 @@ import java.util.concurrent.atomic.AtomicInteger; * {@link #quit}. */ public abstract class WakeLockStateMachine extends StateMachine { protected static final boolean DBG = true; // TODO: change to false protected static final boolean DBG = Build.IS_DEBUGGABLE; private final PowerManager.WakeLock mWakeLock; Loading Loading @@ -75,7 +75,8 @@ public abstract class WakeLockStateMachine extends StateMachine { PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, debugTag); mWakeLock.acquire(); // wake lock released after we enter idle state // wake lock released after we enter idle state mWakeLock.acquire(); addState(mDefaultState); addState(mIdleState, mDefaultState); Loading @@ -83,6 +84,16 @@ public abstract class WakeLockStateMachine extends StateMachine { setInitialState(mIdleState); } private void releaseWakeLock() { if (mWakeLock.isHeld()) { mWakeLock.release(); } if (mWakeLock.isHeld()) { loge("Wait lock is held after release."); } } /** * Tell the state machine to quit after processing all messages. */ Loading Loading @@ -155,15 +166,7 @@ public abstract class WakeLockStateMachine extends StateMachine { return HANDLED; case EVENT_RELEASE_WAKE_LOCK: mWakeLock.release(); if (DBG) { if (mWakeLock.isHeld()) { // this is okay as long as we call release() for every acquire() log("mWakeLock is still held after release"); } else { log("mWakeLock released"); } } releaseWakeLock(); return HANDLED; default: Loading Loading @@ -191,11 +194,7 @@ public abstract class WakeLockStateMachine extends StateMachine { return HANDLED; case EVENT_RELEASE_WAKE_LOCK: mWakeLock.release(); // decrement wakelock from previous entry to Idle if (!mWakeLock.isHeld()) { // wakelock should still be held until 3 seconds after we enter Idle loge("mWakeLock released while still in WaitingState!"); } releaseWakeLock(); return HANDLED; default: Loading src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java +121 −7 Original line number Diff line number Diff line Loading @@ -16,20 +16,32 @@ package com.android.internal.telephony.gsm; import static com.android.internal.telephony.gsm.SmsCbConstants.MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.AsyncResult; import android.os.Message; import android.provider.Telephony.CellBroadcasts; import android.telephony.CellLocation; import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; import android.telephony.TelephonyManager; import android.telephony.gsm.GsmCellLocation; import com.android.internal.telephony.CbGeoUtils.Geometry; import com.android.internal.telephony.CellBroadcastHandler; import com.android.internal.telephony.Phone; import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage; import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; /** * Handler for 3GPP format Cell Broadcasts. Parent class can also handle CDMA Cell Broadcasts. Loading @@ -37,6 +49,9 @@ import java.util.Iterator; public class GsmCellBroadcastHandler extends CellBroadcastHandler { private static final boolean VDBG = false; // log CB PDU data /** Indicates that a message is not being broadcasted. */ private static final String MESSAGE_NOT_BROADCASTED = "0"; /** This map holds incomplete concatenated messages waiting for assembly. */ private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap = new HashMap<SmsCbConcatInfo, byte[][]>(4); Loading Loading @@ -64,6 +79,82 @@ public class GsmCellBroadcastHandler extends CellBroadcastHandler { return handler; } /** * Find the cell broadcast messages specify by the geo-fencing trigger message and perform a * geo-fencing check for these messages. * @param geoFencingTriggerMessage the trigger message */ private void handleGeoFencingTriggerMessage(GeoFencingTriggerMessage geoFencingTriggerMessage) { final List<SmsCbMessage> cbMessages = new ArrayList<>(); final List<Uri> cbMessageUris = new ArrayList<>(); // Find the cell broadcast message identify by the message identifier and serial number // and is not broadcasted. String where = CellBroadcasts.SERVICE_CATEGORY + "=? AND " + CellBroadcasts.SERIAL_NUMBER + "=? AND " + CellBroadcasts.MESSAGE_BROADCASTED + "=?"; ContentResolver resolver = mContext.getContentResolver(); for (CellBroadcastIdentity identity : geoFencingTriggerMessage.cbIdentifiers) { try (Cursor cursor = resolver.query(CELL_BROADCAST_URI, CellBroadcasts.QUERY_COLUMNS_FWK, where, new String[] { Integer.toString(identity.messageIdentifier), Integer.toString(identity.serialNumber), MESSAGE_NOT_BROADCASTED}, null /* sortOrder */)) { if (cursor != null) { while (cursor.moveToNext()) { cbMessages.add(SmsCbMessage.createFromCursor(cursor)); cbMessageUris.add(ContentUris.withAppendedId(CELL_BROADCAST_URI, cursor.getInt(cursor.getColumnIndex(CellBroadcasts._ID)))); } } } } List<Geometry> commonBroadcastArea = new ArrayList<>(); if (geoFencingTriggerMessage.shouldShareBroadcastArea()) { for (SmsCbMessage msg : cbMessages) { if (msg.getGeometries() != null) { commonBroadcastArea.addAll(msg.getGeometries()); } } } if (DBG) { logd("Geo-fencing trigger message = " + geoFencingTriggerMessage); for (SmsCbMessage msg : cbMessages) { logd(msg.toString()); } } if (cbMessages.isEmpty()) { if (DBG) logd("No CellBroadcast message need to be broadcasted"); // Need to send this event to make the state machine back to Idle. if (mReceiverCount.get() == 0) sendMessage(EVENT_BROADCAST_COMPLETE); return; } requestLocationUpdate(location -> { if (location == null) { // If the location is not available, broadcast the messages directly. broadcastMessage(cbMessages, cbMessageUris); } else { for (int i = 0; i < cbMessages.size(); i++) { List<Geometry> broadcastArea = commonBroadcastArea.isEmpty() ? commonBroadcastArea : cbMessages.get(i).getGeometries(); if (broadcastArea == null || broadcastArea.isEmpty()) { broadcastMessage(cbMessages.get(i), cbMessageUris.get(i)); } else { performGeoFencing(cbMessages.get(i), cbMessageUris.get(i), broadcastArea, location); } } } }); } /** * Handle 3GPP-format Cell Broadcast messages sent from radio. * Loading @@ -73,21 +164,36 @@ public class GsmCellBroadcastHandler extends CellBroadcastHandler { @Override protected boolean handleSmsMessage(Message message) { if (message.obj instanceof AsyncResult) { SmsCbMessage cbMessage = handleGsmBroadcastSms((AsyncResult) message.obj); SmsCbHeader header = createSmsCbHeader((AsyncResult) message.obj); if (header == null) return false; AsyncResult ar = (AsyncResult) message.obj; byte[] pdu = (byte[]) ar.result; if (header.getServiceCategory() == MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER) { GeoFencingTriggerMessage triggerMessage = GsmSmsCbMessage.createGeoFencingTriggerMessage(pdu); if (triggerMessage != null) { handleGeoFencingTriggerMessage(triggerMessage); return true; } } else { SmsCbMessage cbMessage = handleGsmBroadcastSms(header, ar); if (cbMessage != null) { handleBroadcastSms(cbMessage); return true; } if (VDBG) log("Not handled GSM broadcasts."); } } return super.handleSmsMessage(message); } /** * Handle 3GPP format SMS-CB message. * @param header the cellbroadcast header. * @param ar the AsyncResult containing the received PDUs */ private SmsCbMessage handleGsmBroadcastSms(AsyncResult ar) { private SmsCbMessage handleGsmBroadcastSms(SmsCbHeader header, AsyncResult ar) { try { byte[] receivedPdu = (byte[]) ar.result; Loading @@ -106,7 +212,6 @@ public class GsmCellBroadcastHandler extends CellBroadcastHandler { } } SmsCbHeader header = new SmsCbHeader(receivedPdu); if (VDBG) log("header=" + header); String plmn = TelephonyManager.from(mContext).getNetworkOperatorForPhone( mPhone.getPhoneId()); Loading Loading @@ -197,6 +302,15 @@ public class GsmCellBroadcastHandler extends CellBroadcastHandler { } } private SmsCbHeader createSmsCbHeader(AsyncResult ar) { try { return new SmsCbHeader((byte[]) ar.result); } catch (Exception ex) { loge("Can't create SmsCbHeader, ex = " + ex.toString()); return null; } } /** * Holds all info about a message page needed to assemble a complete concatenated message. */ Loading Loading
src/java/com/android/internal/telephony/CellBroadcastHandler.java +263 −4 Original line number Diff line number Diff line Loading @@ -16,36 +16,59 @@ package com.android.internal.telephony; import static android.content.PermissionChecker.PERMISSION_GRANTED; import static android.provider.Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.AppOpsManager; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.PermissionChecker; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.provider.Settings; import android.provider.Telephony; import android.provider.Telephony.CellBroadcasts; import android.telephony.SmsCbMessage; import android.telephony.SubscriptionManager; import android.util.LocalLog; import android.util.Log; import com.android.internal.telephony.CbGeoUtils.Geometry; import com.android.internal.telephony.CbGeoUtils.LatLng; import com.android.internal.telephony.metrics.TelephonyMetrics; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast * completes and our result receiver is called. */ public class CellBroadcastHandler extends WakeLockStateMachine { private static final String EXTRA_MESSAGE = "message"; private final LocalLog mLocalLog = new LocalLog(100); private static final String EXTRA_MESSAGE = "message"; protected static final Uri CELL_BROADCAST_URI = Uri.parse("content://cellbroadcasts_fwk"); /** Uses to request the location update. */ public final LocationRequester mLocationRequester; private CellBroadcastHandler(Context context, Phone phone) { this("CellBroadcastHandler", context, phone); Loading @@ -53,6 +76,10 @@ public class CellBroadcastHandler extends WakeLockStateMachine { protected CellBroadcastHandler(String debugTag, Context context, Phone phone) { super(debugTag, context, phone); mLocationRequester = new LocationRequester( context, (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE), getHandler().getLooper()); } /** Loading Loading @@ -89,9 +116,6 @@ public class CellBroadcastHandler extends WakeLockStateMachine { * @param message the Cell Broadcast to broadcast */ protected void handleBroadcastSms(SmsCbMessage message) { String receiverPermission; int appOp; // Log Cellbroadcast msg received event TelephonyMetrics metrics = TelephonyMetrics.getInstance(); metrics.writeNewCBSms(mPhone.getPhoneId(), message.getMessageFormat(), Loading @@ -99,6 +123,92 @@ public class CellBroadcastHandler extends WakeLockStateMachine { message.getServiceCategory(), message.getSerialNumber(), System.currentTimeMillis()); // TODO: Database inserting can be time consuming, therefore this should be changed to // asynchronous. ContentValues cv = message.getContentValues(); Uri uri = mContext.getContentResolver().insert(CELL_BROADCAST_URI, cv); if (message.needGeoFencingCheck()) { if (DBG) { log("Request location update for geo-fencing. serialNumber = " + message.getSerialNumber()); } requestLocationUpdate(location -> { if (location == null) { // Broadcast the message directly if the location is not available. broadcastMessage(message, uri); } else { performGeoFencing(message, uri, message.getGeometries(), location); } }); } else { if (DBG) { log("Broadcast the message directly because no geo-fencing required, " + "serialNumber = " + message.getSerialNumber() + " needGeoFencing = " + message.needGeoFencingCheck()); } broadcastMessage(message, uri); } } /** * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the * {@code location} is inside the {@code broadcastArea}. * @param message the message need to geo-fencing check * @param uri the message's uri * @param broadcastArea the broadcast area of the message * @param location current location */ protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea, LatLng location) { if (DBG) { logd("Perform geo-fencing check for message identifier = " + message.getServiceCategory() + " serialNumber = " + message.getSerialNumber()); } for (Geometry geo : broadcastArea) { if (geo.contains(location)) { broadcastMessage(message, uri); return; } } if (DBG) { logd("Device location is outside the broadcast area " + CbGeoUtils.encodeGeometriesToString(broadcastArea)); } } /** * Request a single location update. * @param callback a callback will be called when the location is available. */ protected void requestLocationUpdate(LocationUpdateCallback callback) { mLocationRequester.requestLocationUpdate(callback); } /** * Broadcast a list of cell broadcast messages. * @param cbMessages a list of cell broadcast message. * @param cbMessageUris the corresponding {@link Uri} of the cell broadcast messages. */ protected void broadcastMessage(List<SmsCbMessage> cbMessages, List<Uri> cbMessageUris) { for (int i = 0; i < cbMessages.size(); i++) { broadcastMessage(cbMessages.get(i), cbMessageUris.get(i)); } } /** * Broadcast the {@code message} to the applications. * @param message a message need to broadcast * @param messageUri message's uri */ protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri) { String receiverPermission; int appOp; String msg; Intent intent; if (message.isEmergencyMessage()) { Loading Loading @@ -155,6 +265,13 @@ public class CellBroadcastHandler extends WakeLockStateMachine { mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission, appOp, mReceiver, getHandler(), Activity.RESULT_OK, null, null); } if (messageUri != null) { ContentValues cv = new ContentValues(); cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1); mContext.getContentResolver().update(CELL_BROADCAST_URI, cv, CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()}); } } @Override Loading @@ -163,4 +280,146 @@ public class CellBroadcastHandler extends WakeLockStateMachine { mLocalLog.dump(fd, pw, args); pw.flush(); } /** The callback interface of a location request. */ public interface LocationUpdateCallback { /** * Call when the location update is available. * @param location a location in (latitude, longitude) format, or {@code null} if the * location service is not available. */ void onLocationUpdate(@Nullable LatLng location); } private static final class LocationRequester { private static final String TAG = LocationRequester.class.getSimpleName(); /** * Trigger this event when the {@link LocationManager} is not responded within the given * time. */ private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1; /** Request a single location update. */ private static final int EVENT_REQUEST_LOCATION_UPDATE = 2; /** Default expired time of the location request. */ private static final int LOCATION_REQUEST_TIMEOUT_MILLIS = 30 * 1000; /** * Request location update from network or gps location provider. Network provider will be * used if available, otherwise use the gps provider. */ private static final List<String> LOCATION_PROVIDERS = Arrays.asList( LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER); private final LocationManager mLocationManager; private final Looper mLooper; private final List<LocationUpdateCallback> mCallbacks; private final Context mContext; private Handler mLocationHandler; LocationRequester(Context context, LocationManager locationManager, Looper looper) { mLocationManager = locationManager; mLooper = looper; mCallbacks = new ArrayList<>(); mContext = context; mLocationHandler = new LocationHandler(looper); } /** * Request a single location update. If the location is not available, a callback with * {@code null} location will be called immediately. * @param callback a callback to the the response when the location is available */ void requestLocationUpdate(@NonNull LocationUpdateCallback callback) { mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, callback).sendToTarget(); } private void onLocationUpdate(@Nullable LatLng location) { for (LocationUpdateCallback callback : mCallbacks) { callback.onLocationUpdate(location); } mCallbacks.clear(); } private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback) { if (DBG) Log.d(TAG, "requestLocationUpdate"); if (!isLocationServiceAvailable()) { if (DBG) { Log.d(TAG, "Can't request location update because of no location permission"); } callback.onLocationUpdate(null); return; } // TODO: handle the "Geo-fencing Maximum Wait" defined in ATIS-0700041 Section 5.2.3 if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) { mLocationHandler.sendMessageDelayed( mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT), LOCATION_REQUEST_TIMEOUT_MILLIS); } mCallbacks.add(callback); for (String provider : LOCATION_PROVIDERS) { if (mLocationManager.isProviderEnabled(provider)) { mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper); break; } } } private boolean isLocationServiceAvailable() { if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION) && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false; for (String provider : LOCATION_PROVIDERS) { if (mLocationManager.isProviderEnabled(provider)) return true; } return false; } private boolean hasPermission(String permission) { return PermissionChecker.checkCallingOrSelfPermission(mContext, permission) == PERMISSION_GRANTED; } private final LocationListener mLocationListener = new LocationListener() { @Override public void onLocationChanged(Location location) { mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT); onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude())); } @Override public void onStatusChanged(String provider, int status, Bundle extras) {} @Override public void onProviderEnabled(String provider) {} @Override public void onProviderDisabled(String provider) {} }; private final class LocationHandler extends Handler { LocationHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case EVENT_LOCATION_REQUEST_TIMEOUT: if (DBG) Log.d(TAG, "location request timeout"); onLocationUpdate(null); break; case EVENT_REQUEST_LOCATION_UPDATE: requestLocationUpdateInternal((LocationUpdateCallback) msg.obj); break; default: Log.e(TAG, "Unsupported message type " + msg.what); } } } } }
src/java/com/android/internal/telephony/WakeLockStateMachine.java +15 −16 Original line number Diff line number Diff line Loading @@ -38,7 +38,7 @@ import java.util.concurrent.atomic.AtomicInteger; * {@link #quit}. */ public abstract class WakeLockStateMachine extends StateMachine { protected static final boolean DBG = true; // TODO: change to false protected static final boolean DBG = Build.IS_DEBUGGABLE; private final PowerManager.WakeLock mWakeLock; Loading Loading @@ -75,7 +75,8 @@ public abstract class WakeLockStateMachine extends StateMachine { PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, debugTag); mWakeLock.acquire(); // wake lock released after we enter idle state // wake lock released after we enter idle state mWakeLock.acquire(); addState(mDefaultState); addState(mIdleState, mDefaultState); Loading @@ -83,6 +84,16 @@ public abstract class WakeLockStateMachine extends StateMachine { setInitialState(mIdleState); } private void releaseWakeLock() { if (mWakeLock.isHeld()) { mWakeLock.release(); } if (mWakeLock.isHeld()) { loge("Wait lock is held after release."); } } /** * Tell the state machine to quit after processing all messages. */ Loading Loading @@ -155,15 +166,7 @@ public abstract class WakeLockStateMachine extends StateMachine { return HANDLED; case EVENT_RELEASE_WAKE_LOCK: mWakeLock.release(); if (DBG) { if (mWakeLock.isHeld()) { // this is okay as long as we call release() for every acquire() log("mWakeLock is still held after release"); } else { log("mWakeLock released"); } } releaseWakeLock(); return HANDLED; default: Loading Loading @@ -191,11 +194,7 @@ public abstract class WakeLockStateMachine extends StateMachine { return HANDLED; case EVENT_RELEASE_WAKE_LOCK: mWakeLock.release(); // decrement wakelock from previous entry to Idle if (!mWakeLock.isHeld()) { // wakelock should still be held until 3 seconds after we enter Idle loge("mWakeLock released while still in WaitingState!"); } releaseWakeLock(); return HANDLED; default: Loading
src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java +121 −7 Original line number Diff line number Diff line Loading @@ -16,20 +16,32 @@ package com.android.internal.telephony.gsm; import static com.android.internal.telephony.gsm.SmsCbConstants.MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.AsyncResult; import android.os.Message; import android.provider.Telephony.CellBroadcasts; import android.telephony.CellLocation; import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; import android.telephony.TelephonyManager; import android.telephony.gsm.GsmCellLocation; import com.android.internal.telephony.CbGeoUtils.Geometry; import com.android.internal.telephony.CellBroadcastHandler; import com.android.internal.telephony.Phone; import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage; import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; /** * Handler for 3GPP format Cell Broadcasts. Parent class can also handle CDMA Cell Broadcasts. Loading @@ -37,6 +49,9 @@ import java.util.Iterator; public class GsmCellBroadcastHandler extends CellBroadcastHandler { private static final boolean VDBG = false; // log CB PDU data /** Indicates that a message is not being broadcasted. */ private static final String MESSAGE_NOT_BROADCASTED = "0"; /** This map holds incomplete concatenated messages waiting for assembly. */ private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap = new HashMap<SmsCbConcatInfo, byte[][]>(4); Loading Loading @@ -64,6 +79,82 @@ public class GsmCellBroadcastHandler extends CellBroadcastHandler { return handler; } /** * Find the cell broadcast messages specify by the geo-fencing trigger message and perform a * geo-fencing check for these messages. * @param geoFencingTriggerMessage the trigger message */ private void handleGeoFencingTriggerMessage(GeoFencingTriggerMessage geoFencingTriggerMessage) { final List<SmsCbMessage> cbMessages = new ArrayList<>(); final List<Uri> cbMessageUris = new ArrayList<>(); // Find the cell broadcast message identify by the message identifier and serial number // and is not broadcasted. String where = CellBroadcasts.SERVICE_CATEGORY + "=? AND " + CellBroadcasts.SERIAL_NUMBER + "=? AND " + CellBroadcasts.MESSAGE_BROADCASTED + "=?"; ContentResolver resolver = mContext.getContentResolver(); for (CellBroadcastIdentity identity : geoFencingTriggerMessage.cbIdentifiers) { try (Cursor cursor = resolver.query(CELL_BROADCAST_URI, CellBroadcasts.QUERY_COLUMNS_FWK, where, new String[] { Integer.toString(identity.messageIdentifier), Integer.toString(identity.serialNumber), MESSAGE_NOT_BROADCASTED}, null /* sortOrder */)) { if (cursor != null) { while (cursor.moveToNext()) { cbMessages.add(SmsCbMessage.createFromCursor(cursor)); cbMessageUris.add(ContentUris.withAppendedId(CELL_BROADCAST_URI, cursor.getInt(cursor.getColumnIndex(CellBroadcasts._ID)))); } } } } List<Geometry> commonBroadcastArea = new ArrayList<>(); if (geoFencingTriggerMessage.shouldShareBroadcastArea()) { for (SmsCbMessage msg : cbMessages) { if (msg.getGeometries() != null) { commonBroadcastArea.addAll(msg.getGeometries()); } } } if (DBG) { logd("Geo-fencing trigger message = " + geoFencingTriggerMessage); for (SmsCbMessage msg : cbMessages) { logd(msg.toString()); } } if (cbMessages.isEmpty()) { if (DBG) logd("No CellBroadcast message need to be broadcasted"); // Need to send this event to make the state machine back to Idle. if (mReceiverCount.get() == 0) sendMessage(EVENT_BROADCAST_COMPLETE); return; } requestLocationUpdate(location -> { if (location == null) { // If the location is not available, broadcast the messages directly. broadcastMessage(cbMessages, cbMessageUris); } else { for (int i = 0; i < cbMessages.size(); i++) { List<Geometry> broadcastArea = commonBroadcastArea.isEmpty() ? commonBroadcastArea : cbMessages.get(i).getGeometries(); if (broadcastArea == null || broadcastArea.isEmpty()) { broadcastMessage(cbMessages.get(i), cbMessageUris.get(i)); } else { performGeoFencing(cbMessages.get(i), cbMessageUris.get(i), broadcastArea, location); } } } }); } /** * Handle 3GPP-format Cell Broadcast messages sent from radio. * Loading @@ -73,21 +164,36 @@ public class GsmCellBroadcastHandler extends CellBroadcastHandler { @Override protected boolean handleSmsMessage(Message message) { if (message.obj instanceof AsyncResult) { SmsCbMessage cbMessage = handleGsmBroadcastSms((AsyncResult) message.obj); SmsCbHeader header = createSmsCbHeader((AsyncResult) message.obj); if (header == null) return false; AsyncResult ar = (AsyncResult) message.obj; byte[] pdu = (byte[]) ar.result; if (header.getServiceCategory() == MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER) { GeoFencingTriggerMessage triggerMessage = GsmSmsCbMessage.createGeoFencingTriggerMessage(pdu); if (triggerMessage != null) { handleGeoFencingTriggerMessage(triggerMessage); return true; } } else { SmsCbMessage cbMessage = handleGsmBroadcastSms(header, ar); if (cbMessage != null) { handleBroadcastSms(cbMessage); return true; } if (VDBG) log("Not handled GSM broadcasts."); } } return super.handleSmsMessage(message); } /** * Handle 3GPP format SMS-CB message. * @param header the cellbroadcast header. * @param ar the AsyncResult containing the received PDUs */ private SmsCbMessage handleGsmBroadcastSms(AsyncResult ar) { private SmsCbMessage handleGsmBroadcastSms(SmsCbHeader header, AsyncResult ar) { try { byte[] receivedPdu = (byte[]) ar.result; Loading @@ -106,7 +212,6 @@ public class GsmCellBroadcastHandler extends CellBroadcastHandler { } } SmsCbHeader header = new SmsCbHeader(receivedPdu); if (VDBG) log("header=" + header); String plmn = TelephonyManager.from(mContext).getNetworkOperatorForPhone( mPhone.getPhoneId()); Loading Loading @@ -197,6 +302,15 @@ public class GsmCellBroadcastHandler extends CellBroadcastHandler { } } private SmsCbHeader createSmsCbHeader(AsyncResult ar) { try { return new SmsCbHeader((byte[]) ar.result); } catch (Exception ex) { loge("Can't create SmsCbHeader, ex = " + ex.toString()); return null; } } /** * Holds all info about a message page needed to assemble a complete concatenated message. */ Loading