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