GsmCellBroadcastHandler.java revision d2feaf918ab0c1173d4ada182532e48d0c0d3f77
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    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