1/*
2** Copyright 2007, 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.content.pm.PackageManager;
21import android.os.AsyncResult;
22import android.os.Binder;
23import android.os.Handler;
24import android.os.Message;
25import android.util.Log;
26
27import com.android.internal.telephony.IccConstants;
28import com.android.internal.telephony.IccSmsInterfaceManager;
29import com.android.internal.telephony.IccUtils;
30import com.android.internal.telephony.IntRangeManager;
31import com.android.internal.telephony.SMSDispatcher;
32import com.android.internal.telephony.SmsRawData;
33
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.HashMap;
37import java.util.HashSet;
38import java.util.List;
39import java.util.Set;
40
41import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
42
43/**
44 * SimSmsInterfaceManager to provide an inter-process communication to
45 * access Sms in Sim.
46 */
47public class SimSmsInterfaceManager extends IccSmsInterfaceManager {
48    static final String LOG_TAG = "GSM";
49    static final boolean DBG = true;
50
51    private final Object mLock = new Object();
52    private boolean mSuccess;
53    private List<SmsRawData> mSms;
54    private HashMap<Integer, HashSet<String>> mCellBroadcastSubscriptions =
55            new HashMap<Integer, HashSet<String>>();
56
57    private CellBroadcastRangeManager mCellBroadcastRangeManager =
58            new CellBroadcastRangeManager();
59
60    private static final int EVENT_LOAD_DONE = 1;
61    private static final int EVENT_UPDATE_DONE = 2;
62    private static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3;
63    private static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4;
64    private static final int SMS_CB_CODE_SCHEME_MIN = 0;
65    private static final int SMS_CB_CODE_SCHEME_MAX = 255;
66
67    Handler mHandler = new Handler() {
68        @Override
69        public void handleMessage(Message msg) {
70            AsyncResult ar;
71
72            switch (msg.what) {
73                case EVENT_UPDATE_DONE:
74                    ar = (AsyncResult) msg.obj;
75                    synchronized (mLock) {
76                        mSuccess = (ar.exception == null);
77                        mLock.notifyAll();
78                    }
79                    break;
80                case EVENT_LOAD_DONE:
81                    ar = (AsyncResult)msg.obj;
82                    synchronized (mLock) {
83                        if (ar.exception == null) {
84                            mSms  = buildValidRawData((ArrayList<byte[]>) ar.result);
85                        } else {
86                            if(DBG) log("Cannot load Sms records");
87                            if (mSms != null)
88                                mSms.clear();
89                        }
90                        mLock.notifyAll();
91                    }
92                    break;
93                case EVENT_SET_BROADCAST_ACTIVATION_DONE:
94                case EVENT_SET_BROADCAST_CONFIG_DONE:
95                    ar = (AsyncResult) msg.obj;
96                    synchronized (mLock) {
97                        mSuccess = (ar.exception == null);
98                        mLock.notifyAll();
99                    }
100                    break;
101            }
102        }
103    };
104
105    public SimSmsInterfaceManager(GSMPhone phone, SMSDispatcher dispatcher) {
106        super(phone);
107        mDispatcher = dispatcher;
108    }
109
110    public void dispose() {
111    }
112
113    @Override
114    protected void finalize() {
115        try {
116            super.finalize();
117        } catch (Throwable throwable) {
118            Log.e(LOG_TAG, "Error while finalizing:", throwable);
119        }
120        if(DBG) Log.d(LOG_TAG, "SimSmsInterfaceManager finalized");
121    }
122
123    /**
124     * Update the specified message on the SIM.
125     *
126     * @param index record index of message to update
127     * @param status new message status (STATUS_ON_ICC_READ,
128     *                  STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
129     *                  STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
130     * @param pdu the raw PDU to store
131     * @return success or not
132     *
133     */
134    public boolean
135    updateMessageOnIccEf(int index, int status, byte[] pdu) {
136        if (DBG) log("updateMessageOnIccEf: index=" + index +
137                " status=" + status + " ==> " +
138                "("+ Arrays.toString(pdu) + ")");
139        enforceReceiveAndSend("Updating message on SIM");
140        synchronized(mLock) {
141            mSuccess = false;
142            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
143
144            if (status == STATUS_ON_ICC_FREE) {
145                // Special case FREE: call deleteSmsOnSim instead of
146                // manipulating the SIM record
147                mPhone.mCM.deleteSmsOnSim(index, response);
148            } else {
149                byte[] record = makeSmsRecordData(status, pdu);
150                mPhone.getIccFileHandler().updateEFLinearFixed(
151                        IccConstants.EF_SMS,
152                        index, record, null, response);
153            }
154            try {
155                mLock.wait();
156            } catch (InterruptedException e) {
157                log("interrupted while trying to update by index");
158            }
159        }
160        return mSuccess;
161    }
162
163    /**
164     * Copy a raw SMS PDU to the SIM.
165     *
166     * @param pdu the raw PDU to store
167     * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
168     *               STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
169     * @return success or not
170     *
171     */
172    public boolean copyMessageToIccEf(int status, byte[] pdu, byte[] smsc) {
173        if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
174                "pdu=("+ Arrays.toString(pdu) +
175                "), smsm=(" + Arrays.toString(smsc) +")");
176        enforceReceiveAndSend("Copying message to SIM");
177        synchronized(mLock) {
178            mSuccess = false;
179            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
180
181            mPhone.mCM.writeSmsToSim(status, IccUtils.bytesToHexString(smsc),
182                    IccUtils.bytesToHexString(pdu), response);
183
184            try {
185                mLock.wait();
186            } catch (InterruptedException e) {
187                log("interrupted while trying to update by index");
188            }
189        }
190        return mSuccess;
191    }
192
193    /**
194     * Retrieves all messages currently stored on ICC.
195     *
196     * @return list of SmsRawData of all sms on ICC
197     */
198    public List<SmsRawData> getAllMessagesFromIccEf() {
199        if (DBG) log("getAllMessagesFromEF");
200
201        Context context = mPhone.getContext();
202
203        context.enforceCallingPermission(
204                "android.permission.RECEIVE_SMS",
205                "Reading messages from SIM");
206        synchronized(mLock) {
207            Message response = mHandler.obtainMessage(EVENT_LOAD_DONE);
208            mPhone.getIccFileHandler().loadEFLinearFixedAll(IccConstants.EF_SMS, response);
209
210            try {
211                mLock.wait();
212            } catch (InterruptedException e) {
213                log("interrupted while trying to load from the SIM");
214            }
215        }
216        return mSms;
217    }
218
219    public boolean enableCellBroadcast(int messageIdentifier) {
220        return enableCellBroadcastRange(messageIdentifier, messageIdentifier);
221    }
222
223    public boolean disableCellBroadcast(int messageIdentifier) {
224        return disableCellBroadcastRange(messageIdentifier, messageIdentifier);
225    }
226
227    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
228        if (DBG) log("enableCellBroadcastRange");
229
230        Context context = mPhone.getContext();
231
232        context.enforceCallingPermission(
233                "android.permission.RECEIVE_SMS",
234                "Enabling cell broadcast SMS");
235
236        String client = context.getPackageManager().getNameForUid(
237                Binder.getCallingUid());
238
239        if (!mCellBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
240            log("Failed to add cell broadcast subscription for MID range " + startMessageId
241                    + " to " + endMessageId + " from client " + client);
242            return false;
243        }
244
245        if (DBG)
246            log("Added cell broadcast subscription for MID range " + startMessageId
247                    + " to " + endMessageId + " from client " + client);
248
249        setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
250
251        return true;
252    }
253
254    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
255        if (DBG) log("disableCellBroadcastRange");
256
257        Context context = mPhone.getContext();
258
259        context.enforceCallingPermission(
260                "android.permission.RECEIVE_SMS",
261                "Disabling cell broadcast SMS");
262
263        String client = context.getPackageManager().getNameForUid(
264                Binder.getCallingUid());
265
266        if (!mCellBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
267            log("Failed to remove cell broadcast subscription for MID range " + startMessageId
268                    + " to " + endMessageId + " from client " + client);
269            return false;
270        }
271
272        if (DBG)
273            log("Removed cell broadcast subscription for MID range " + startMessageId
274                    + " to " + endMessageId + " from client " + client);
275
276        setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
277
278        return true;
279    }
280
281    class CellBroadcastRangeManager extends IntRangeManager {
282        private ArrayList<SmsBroadcastConfigInfo> mConfigList =
283                new ArrayList<SmsBroadcastConfigInfo>();
284
285        /**
286         * Called when the list of enabled ranges has changed. This will be
287         * followed by zero or more calls to {@link #addRange} followed by
288         * a call to {@link #finishUpdate}.
289         */
290        protected void startUpdate() {
291            mConfigList.clear();
292        }
293
294        /**
295         * Called after {@link #startUpdate} to indicate a range of enabled
296         * values.
297         * @param startId the first id included in the range
298         * @param endId the last id included in the range
299         */
300        protected void addRange(int startId, int endId, boolean selected) {
301            mConfigList.add(new SmsBroadcastConfigInfo(startId, endId,
302                        SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected));
303        }
304
305        /**
306         * Called to indicate the end of a range update started by the
307         * previous call to {@link #startUpdate}.
308         * @return true if successful, false otherwise
309         */
310        protected boolean finishUpdate() {
311            if (mConfigList.isEmpty()) {
312                return true;
313            } else {
314                SmsBroadcastConfigInfo[] configs =
315                        mConfigList.toArray(new SmsBroadcastConfigInfo[mConfigList.size()]);
316                return setCellBroadcastConfig(configs);
317            }
318        }
319    }
320
321    private boolean setCellBroadcastConfig(SmsBroadcastConfigInfo[] configs) {
322        if (DBG)
323            log("Calling setGsmBroadcastConfig with " + configs.length + " configurations");
324
325        synchronized (mLock) {
326            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE);
327
328            mSuccess = false;
329            mPhone.mCM.setGsmBroadcastConfig(configs, response);
330
331            try {
332                mLock.wait();
333            } catch (InterruptedException e) {
334                log("interrupted while trying to set cell broadcast config");
335            }
336        }
337
338        return mSuccess;
339    }
340
341    private boolean setCellBroadcastActivation(boolean activate) {
342        if (DBG)
343            log("Calling setCellBroadcastActivation(" + activate + ')');
344
345        synchronized (mLock) {
346            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE);
347
348            mSuccess = false;
349            mPhone.mCM.setGsmBroadcastActivation(activate, response);
350
351            try {
352                mLock.wait();
353            } catch (InterruptedException e) {
354                log("interrupted while trying to set cell broadcast activation");
355            }
356        }
357
358        return mSuccess;
359    }
360
361    @Override
362    protected void log(String msg) {
363        Log.d(LOG_TAG, "[SimSmsInterfaceManager] " + msg);
364    }
365}
366