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