GsmCellBroadcastHandler.java revision 0d4bcdf379842af4b6304809156971e926f374f0
1/* 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.internal.telephony.gsm; 18 19import android.content.Context; 20import android.os.AsyncResult; 21import android.os.Message; 22import android.os.SystemProperties; 23import android.telephony.CellLocation; 24import android.telephony.SmsCbLocation; 25import android.telephony.SmsCbMessage; 26import android.telephony.gsm.GsmCellLocation; 27 28import com.android.internal.telephony.CellBroadcastHandler; 29import com.android.internal.telephony.PhoneBase; 30import com.android.internal.telephony.TelephonyProperties; 31 32import java.util.HashMap; 33import java.util.Iterator; 34 35/** 36 * Handler for 3GPP format Cell Broadcasts. Parent class can also handle CDMA Cell Broadcasts. 37 */ 38public class GsmCellBroadcastHandler extends CellBroadcastHandler { 39 private static final boolean VDBG = false; // log CB PDU data 40 41 /** This map holds incomplete concatenated messages waiting for assembly. */ 42 private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap = 43 new HashMap<SmsCbConcatInfo, byte[][]>(4); 44 45 private final PhoneBase mPhone; 46 47 protected GsmCellBroadcastHandler(Context context, PhoneBase phone) { 48 super("GsmCellBroadcastHandler", context); 49 mPhone = phone; 50 phone.mCi.setOnNewGsmBroadcastSms(getHandler(), EVENT_NEW_SMS_MESSAGE, null); 51 } 52 53 @Override 54 protected void onQuitting() { 55 mPhone.mCi.unSetOnNewGsmBroadcastSms(getHandler()); 56 super.onQuitting(); // release wakelock 57 } 58 59 /** 60 * Create a new CellBroadcastHandler. 61 * @param context the context to use for dispatching Intents 62 * @return the new handler 63 */ 64 public static GsmCellBroadcastHandler makeGsmCellBroadcastHandler(Context context, 65 PhoneBase phone) { 66 GsmCellBroadcastHandler handler = new GsmCellBroadcastHandler(context, phone); 67 handler.start(); 68 return handler; 69 } 70 71 /** 72 * Handle 3GPP-format Cell Broadcast messages sent from radio. 73 * 74 * @param message the message to process 75 * @return true if an ordered broadcast was sent; false on failure 76 */ 77 @Override 78 protected boolean handleSmsMessage(Message message) { 79 if (message.obj instanceof AsyncResult) { 80 SmsCbMessage cbMessage = handleGsmBroadcastSms((AsyncResult) message.obj); 81 if (cbMessage != null) { 82 handleBroadcastSms(cbMessage); 83 return true; 84 } 85 } 86 return super.handleSmsMessage(message); 87 } 88 89 /** 90 * Handle 3GPP format SMS-CB message. 91 * @param ar the AsyncResult containing the received PDUs 92 */ 93 private SmsCbMessage handleGsmBroadcastSms(AsyncResult ar) { 94 try { 95 byte[] receivedPdu = (byte[]) ar.result; 96 97 if (VDBG) { 98 int pduLength = receivedPdu.length; 99 for (int i = 0; i < pduLength; i += 8) { 100 StringBuilder sb = new StringBuilder("SMS CB pdu data: "); 101 for (int j = i; j < i + 8 && j < pduLength; j++) { 102 int b = receivedPdu[j] & 0xff; 103 if (b < 0x10) { 104 sb.append('0'); 105 } 106 sb.append(Integer.toHexString(b)).append(' '); 107 } 108 log(sb.toString()); 109 } 110 } 111 112 SmsCbHeader header = new SmsCbHeader(receivedPdu); 113 String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC); 114 int lac = -1; 115 int cid = -1; 116 CellLocation cl = mPhone.getCellLocation(); 117 // Check if cell location is GsmCellLocation. This is required to support 118 // dual-mode devices such as CDMA/LTE devices that require support for 119 // both 3GPP and 3GPP2 format messages 120 if (cl instanceof GsmCellLocation) { 121 GsmCellLocation cellLocation = (GsmCellLocation)cl; 122 lac = cellLocation.getLac(); 123 cid = cellLocation.getCid(); 124 } 125 126 SmsCbLocation location; 127 switch (header.getGeographicalScope()) { 128 case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE: 129 location = new SmsCbLocation(plmn, lac, -1); 130 break; 131 132 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: 133 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: 134 location = new SmsCbLocation(plmn, lac, cid); 135 break; 136 137 case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: 138 default: 139 location = new SmsCbLocation(plmn); 140 break; 141 } 142 143 byte[][] pdus; 144 int pageCount = header.getNumberOfPages(); 145 if (pageCount > 1) { 146 // Multi-page message 147 SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location); 148 149 // Try to find other pages of the same message 150 pdus = mSmsCbPageMap.get(concatInfo); 151 152 if (pdus == null) { 153 // This is the first page of this message, make room for all 154 // pages and keep until complete 155 pdus = new byte[pageCount][]; 156 157 mSmsCbPageMap.put(concatInfo, pdus); 158 } 159 160 // Page parameter is one-based 161 pdus[header.getPageIndex() - 1] = receivedPdu; 162 163 for (byte[] pdu : pdus) { 164 if (pdu == null) { 165 // Still missing pages, exit 166 return null; 167 } 168 } 169 170 // Message complete, remove and dispatch 171 mSmsCbPageMap.remove(concatInfo); 172 } else { 173 // Single page message 174 pdus = new byte[1][]; 175 pdus[0] = receivedPdu; 176 } 177 178 // Remove messages that are out of scope to prevent the map from 179 // growing indefinitely, containing incomplete messages that were 180 // never assembled 181 Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator(); 182 183 while (iter.hasNext()) { 184 SmsCbConcatInfo info = iter.next(); 185 186 if (!info.matchesLocation(plmn, lac, cid)) { 187 iter.remove(); 188 } 189 } 190 191 return GsmSmsCbMessage.createSmsCbMessage(header, location, pdus); 192 193 } catch (RuntimeException e) { 194 loge("Error in decoding SMS CB pdu", e); 195 return null; 196 } 197 } 198 199 /** 200 * Holds all info about a message page needed to assemble a complete concatenated message. 201 */ 202 private static final class SmsCbConcatInfo { 203 204 private final SmsCbHeader mHeader; 205 private final SmsCbLocation mLocation; 206 207 SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) { 208 mHeader = header; 209 mLocation = location; 210 } 211 212 @Override 213 public int hashCode() { 214 return (mHeader.getSerialNumber() * 31) + mLocation.hashCode(); 215 } 216 217 @Override 218 public boolean equals(Object obj) { 219 if (obj instanceof SmsCbConcatInfo) { 220 SmsCbConcatInfo other = (SmsCbConcatInfo)obj; 221 222 // Two pages match if they have the same serial number (which includes the 223 // geographical scope and update number), and both pages belong to the same 224 // location (PLMN, plus LAC and CID if these are part of the geographical scope). 225 return mHeader.getSerialNumber() == other.mHeader.getSerialNumber() 226 && mLocation.equals(other.mLocation); 227 } 228 229 return false; 230 } 231 232 /** 233 * Compare the location code for this message to the current location code. The match is 234 * relative to the geographical scope of the message, which determines whether the LAC 235 * and Cell ID are saved in mLocation or set to -1 to match all values. 236 * 237 * @param plmn the current PLMN 238 * @param lac the current Location Area (GSM) or Service Area (UMTS) 239 * @param cid the current Cell ID 240 * @return true if this message is valid for the current location; false otherwise 241 */ 242 public boolean matchesLocation(String plmn, int lac, int cid) { 243 return mLocation.isInLocationArea(plmn, lac, cid); 244 } 245 } 246} 247