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