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