Home | History | Annotate | Download | only in gsm
      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