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 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