IccSmsInterfaceManager.java revision 7fce994eb223105829becb6c26e3af7a9739752c
1/*
2 * Copyright (C) 2008 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;
18
19import android.Manifest;
20import android.app.AppOpsManager;
21import android.app.PendingIntent;
22import android.content.Context;
23import android.os.AsyncResult;
24import android.os.Binder;
25import android.os.Handler;
26import android.os.Message;
27import android.telephony.Rlog;
28import android.util.Log;
29
30import com.android.internal.telephony.uicc.IccConstants;
31import com.android.internal.telephony.uicc.IccFileHandler;
32import com.android.internal.util.HexDump;
33
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.List;
37
38import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
39import static android.telephony.SmsManager.STATUS_ON_ICC_READ;
40import static android.telephony.SmsManager.STATUS_ON_ICC_UNREAD;
41
42/**
43 * IccSmsInterfaceManager to provide an inter-process communication to
44 * access Sms in Icc.
45 */
46public abstract class IccSmsInterfaceManager extends ISms.Stub {
47    static final String LOG_TAG = "RIL_IccSms";
48    static final boolean DBG = true;
49
50    protected final Object mLock = new Object();
51    protected boolean mSuccess;
52    private List<SmsRawData> mSms;
53
54    private static final int EVENT_LOAD_DONE = 1;
55    private static final int EVENT_UPDATE_DONE = 2;
56    protected static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3;
57    protected static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4;
58
59    final protected PhoneBase mPhone;
60    final protected Context mContext;
61    final protected AppOpsManager mAppOps;
62    protected SMSDispatcher mDispatcher;
63
64    protected Handler mHandler = new Handler() {
65        @Override
66        public void handleMessage(Message msg) {
67            AsyncResult ar;
68
69            switch (msg.what) {
70                case EVENT_UPDATE_DONE:
71                    ar = (AsyncResult) msg.obj;
72                    synchronized (mLock) {
73                        mSuccess = (ar.exception == null);
74                        mLock.notifyAll();
75                    }
76                    break;
77                case EVENT_LOAD_DONE:
78                    ar = (AsyncResult)msg.obj;
79                    synchronized (mLock) {
80                        if (ar.exception == null) {
81                            mSms = buildValidRawData((ArrayList<byte[]>) ar.result);
82                            //Mark SMS as read after importing it from card.
83                            markMessagesAsRead((ArrayList<byte[]>) ar.result);
84                        } else {
85                            if(DBG) log("Cannot load Sms records");
86                            if (mSms != null)
87                                mSms.clear();
88                        }
89                        mLock.notifyAll();
90                    }
91                    break;
92                case EVENT_SET_BROADCAST_ACTIVATION_DONE:
93                case EVENT_SET_BROADCAST_CONFIG_DONE:
94                    ar = (AsyncResult) msg.obj;
95                    synchronized (mLock) {
96                        mSuccess = (ar.exception == null);
97                        mLock.notifyAll();
98                    }
99                    break;
100            }
101        }
102    };
103
104    protected IccSmsInterfaceManager(PhoneBase phone){
105        mPhone = phone;
106        mContext = phone.getContext();
107        mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
108    }
109
110    protected void markMessagesAsRead(ArrayList<byte[]> messages) {
111        if (messages == null) {
112            return;
113        }
114
115        //IccFileHandler can be null, if icc card is absent.
116        IccFileHandler fh = mPhone.getIccFileHandler();
117        if (fh == null) {
118            //shouldn't really happen, as messages are marked as read, only
119            //after importing it from icc.
120            if (Rlog.isLoggable("SMS", Log.DEBUG)) {
121                log("markMessagesAsRead - aborting, no icc card present.");
122            }
123            return;
124        }
125
126        int count = messages.size();
127
128        for (int i = 0; i < count; i++) {
129             byte[] ba = messages.get(i);
130             if (ba[0] == STATUS_ON_ICC_UNREAD) {
131                 int n = ba.length;
132                 byte[] nba = new byte[n - 1];
133                 System.arraycopy(ba, 1, nba, 0, n - 1);
134                 byte[] record = makeSmsRecordData(STATUS_ON_ICC_READ, nba);
135                 fh.updateEFLinearFixed(IccConstants.EF_SMS, i + 1, record, null, null);
136                 if (Rlog.isLoggable("SMS", Log.DEBUG)) {
137                     log("SMS " + (i + 1) + " marked as read");
138                 }
139             }
140        }
141    }
142
143    protected void enforceReceiveAndSend(String message) {
144        mContext.enforceCallingPermission(
145                Manifest.permission.RECEIVE_SMS, message);
146        mContext.enforceCallingPermission(
147                Manifest.permission.SEND_SMS, message);
148    }
149
150    /**
151     * Update the specified message on the Icc.
152     *
153     * @param index record index of message to update
154     * @param status new message status (STATUS_ON_ICC_READ,
155     *                  STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
156     *                  STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
157     * @param pdu the raw PDU to store
158     * @return success or not
159     *
160     */
161    public boolean
162    updateMessageOnIccEf(String callingPackage, int index, int status, byte[] pdu) {
163        if (DBG) log("updateMessageOnIccEf: index=" + index +
164                " status=" + status + " ==> " +
165                "("+ Arrays.toString(pdu) + ")");
166        enforceReceiveAndSend("Updating message on Icc");
167        if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(),
168                callingPackage) != AppOpsManager.MODE_ALLOWED) {
169            return false;
170        }
171        synchronized(mLock) {
172            mSuccess = false;
173            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
174
175            if (status == STATUS_ON_ICC_FREE) {
176                // RIL_REQUEST_DELETE_SMS_ON_SIM vs RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM
177                // Special case FREE: call deleteSmsOnSim/Ruim instead of
178                // manipulating the record
179                // Will eventually fail if icc card is not present.
180                deleteSms(index, response);
181            } else {
182                //IccFilehandler can be null if ICC card is not present.
183                IccFileHandler fh = mPhone.getIccFileHandler();
184                if (fh == null) {
185                    response.recycle();
186                    return mSuccess; /* is false */
187                }
188                byte[] record = makeSmsRecordData(status, pdu);
189                fh.updateEFLinearFixed(
190                        IccConstants.EF_SMS,
191                        index, record, null, response);
192            }
193            try {
194                mLock.wait();
195            } catch (InterruptedException e) {
196                log("interrupted while trying to update by index");
197            }
198        }
199        return mSuccess;
200    }
201
202    /**
203     * Copy a raw SMS PDU to the Icc.
204     *
205     * @param pdu the raw PDU to store
206     * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
207     *               STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
208     * @return success or not
209     *
210     */
211    public boolean copyMessageToIccEf(String callingPackage, int status, byte[] pdu, byte[] smsc) {
212        //NOTE smsc not used in RUIM
213        if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
214                "pdu=("+ Arrays.toString(pdu) +
215                "), smsc=(" + Arrays.toString(smsc) +")");
216        enforceReceiveAndSend("Copying message to Icc");
217        if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(),
218                callingPackage) != AppOpsManager.MODE_ALLOWED) {
219            return false;
220        }
221        synchronized(mLock) {
222            mSuccess = false;
223            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
224
225            //RIL_REQUEST_WRITE_SMS_TO_SIM vs RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM
226            writeSms(status, smsc, pdu, response);
227
228            try {
229                mLock.wait();
230            } catch (InterruptedException e) {
231                log("interrupted while trying to update by index");
232            }
233        }
234        return mSuccess;
235    }
236
237    /**
238     * Retrieves all messages currently stored on Icc.
239     *
240     * @return list of SmsRawData of all sms on Icc
241     */
242    public List<SmsRawData> getAllMessagesFromIccEf(String callingPackage) {
243        if (DBG) log("getAllMessagesFromEF");
244
245        mContext.enforceCallingPermission(
246                Manifest.permission.RECEIVE_SMS,
247                "Reading messages from Icc");
248        if (mAppOps.noteOp(AppOpsManager.OP_READ_ICC_SMS, Binder.getCallingUid(),
249                callingPackage) != AppOpsManager.MODE_ALLOWED) {
250            return new ArrayList<SmsRawData>();
251        }
252        synchronized(mLock) {
253
254            IccFileHandler fh = mPhone.getIccFileHandler();
255            if (fh == null) {
256                Rlog.e(LOG_TAG, "Cannot load Sms records. No icc card?");
257                if (mSms != null) {
258                    mSms.clear();
259                    return mSms;
260                }
261            }
262
263            Message response = mHandler.obtainMessage(EVENT_LOAD_DONE);
264            fh.loadEFLinearFixedAll(IccConstants.EF_SMS, response);
265
266            try {
267                mLock.wait();
268            } catch (InterruptedException e) {
269                log("interrupted while trying to load from the Icc");
270            }
271        }
272        return mSms;
273    }
274
275    /**
276     * Send a data based SMS to a specific application port.
277     *
278     * @param destAddr the address to send the message to
279     * @param scAddr is the service center address or null to use
280     *  the current default SMSC
281     * @param destPort the port to deliver the message to
282     * @param data the body of the message to send
283     * @param sentIntent if not NULL this <code>PendingIntent</code> is
284     *  broadcast when the message is successfully sent, or failed.
285     *  The result code will be <code>Activity.RESULT_OK<code> for success,
286     *  or one of these errors:<br>
287     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
288     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
289     *  <code>RESULT_ERROR_NULL_PDU</code><br>
290     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
291     *  the extra "errorCode" containing a radio technology specific value,
292     *  generally only useful for troubleshooting.<br>
293     *  The per-application based SMS control checks sentIntent. If sentIntent
294     *  is NULL the caller will be checked against all unknown applications,
295     *  which cause smaller number of SMS to be sent in checking period.
296     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
297     *  broadcast when the message is delivered to the recipient.  The
298     *  raw pdu of the status report is in the extended data ("pdu").
299     */
300    public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
301            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
302        mPhone.getContext().enforceCallingPermission(
303                Manifest.permission.SEND_SMS,
304                "Sending SMS message");
305        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
306            log("sendData: destAddr=" + destAddr + " scAddr=" + scAddr + " destPort=" +
307                destPort + " data='"+ HexDump.toHexString(data)  + "' sentIntent=" +
308                sentIntent + " deliveryIntent=" + deliveryIntent);
309        }
310        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
311                callingPackage) != AppOpsManager.MODE_ALLOWED) {
312            return;
313        }
314        mDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
315    }
316
317    /**
318     * Send a text based SMS.
319     *
320     * @param destAddr the address to send the message to
321     * @param scAddr is the service center address or null to use
322     *  the current default SMSC
323     * @param text the body of the message to send
324     * @param sentIntent if not NULL this <code>PendingIntent</code> is
325     *  broadcast when the message is successfully sent, or failed.
326     *  The result code will be <code>Activity.RESULT_OK<code> for success,
327     *  or one of these errors:<br>
328     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
329     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
330     *  <code>RESULT_ERROR_NULL_PDU</code><br>
331     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
332     *  the extra "errorCode" containing a radio technology specific value,
333     *  generally only useful for troubleshooting.<br>
334     *  The per-application based SMS control checks sentIntent. If sentIntent
335     *  is NULL the caller will be checked against all unknown applications,
336     *  which cause smaller number of SMS to be sent in checking period.
337     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
338     *  broadcast when the message is delivered to the recipient.  The
339     *  raw pdu of the status report is in the extended data ("pdu").
340     */
341    public void sendText(String callingPackage, String destAddr, String scAddr,
342            String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
343        mPhone.getContext().enforceCallingPermission(
344                Manifest.permission.SEND_SMS,
345                "Sending SMS message");
346        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
347            log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +
348                " text='"+ text + "' sentIntent=" +
349                sentIntent + " deliveryIntent=" + deliveryIntent);
350        }
351        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
352                callingPackage) != AppOpsManager.MODE_ALLOWED) {
353            return;
354        }
355        mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent);
356    }
357
358    /**
359     * Send a multi-part text based SMS.
360     *
361     * @param destAddr the address to send the message to
362     * @param scAddr is the service center address or null to use
363     *   the current default SMSC
364     * @param parts an <code>ArrayList</code> of strings that, in order,
365     *   comprise the original message
366     * @param sentIntents if not null, an <code>ArrayList</code> of
367     *   <code>PendingIntent</code>s (one for each message part) that is
368     *   broadcast when the corresponding message part has been sent.
369     *   The result code will be <code>Activity.RESULT_OK<code> for success,
370     *   or one of these errors:
371     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
372     *   <code>RESULT_ERROR_RADIO_OFF</code>
373     *   <code>RESULT_ERROR_NULL_PDU</code>.
374     *  The per-application based SMS control checks sentIntent. If sentIntent
375     *  is NULL the caller will be checked against all unknown applications,
376     *  which cause smaller number of SMS to be sent in checking period.
377     * @param deliveryIntents if not null, an <code>ArrayList</code> of
378     *   <code>PendingIntent</code>s (one for each message part) that is
379     *   broadcast when the corresponding message part has been delivered
380     *   to the recipient.  The raw pdu of the status report is in the
381     *   extended data ("pdu").
382     */
383    public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
384            List<String> parts, List<PendingIntent> sentIntents,
385            List<PendingIntent> deliveryIntents) {
386        mPhone.getContext().enforceCallingPermission(
387                Manifest.permission.SEND_SMS,
388                "Sending SMS message");
389        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
390            int i = 0;
391            for (String part : parts) {
392                log("sendMultipartText: destAddr=" + destAddr + ", srAddr=" + scAddr +
393                        ", part[" + (i++) + "]=" + part);
394            }
395        }
396        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
397                callingPackage) != AppOpsManager.MODE_ALLOWED) {
398            return;
399        }
400        mDispatcher.sendMultipartText(destAddr, scAddr, (ArrayList<String>) parts,
401                (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents);
402    }
403
404    public int getPremiumSmsPermission(String packageName) {
405        return mDispatcher.getPremiumSmsPermission(packageName);
406    }
407
408    public void setPremiumSmsPermission(String packageName, int permission) {
409        mDispatcher.setPremiumSmsPermission(packageName, permission);
410    }
411
412    /**
413     * create SmsRawData lists from all sms record byte[]
414     * Use null to indicate "free" record
415     *
416     * @param messages List of message records from EF_SMS.
417     * @return SmsRawData list of all in-used records
418     */
419    protected ArrayList<SmsRawData> buildValidRawData(ArrayList<byte[]> messages) {
420        int count = messages.size();
421        ArrayList<SmsRawData> ret;
422
423        ret = new ArrayList<SmsRawData>(count);
424
425        for (int i = 0; i < count; i++) {
426            byte[] ba = messages.get(i);
427            if (ba[0] == STATUS_ON_ICC_FREE) {
428                ret.add(null);
429            } else {
430                ret.add(new SmsRawData(messages.get(i)));
431            }
432        }
433
434        return ret;
435    }
436
437    /**
438     * Generates an EF_SMS record from status and raw PDU.
439     *
440     * @param status Message status.  See TS 51.011 10.5.3.
441     * @param pdu Raw message PDU.
442     * @return byte array for the record.
443     */
444    protected byte[] makeSmsRecordData(int status, byte[] pdu) {
445        byte[] data = new byte[IccConstants.SMS_RECORD_LENGTH];
446
447        // Status bits for this record.  See TS 51.011 10.5.3
448        data[0] = (byte)(status & 7);
449
450        System.arraycopy(pdu, 0, data, 1, pdu.length);
451
452        // Pad out with 0xFF's.
453        for (int j = pdu.length+1; j < IccConstants.SMS_RECORD_LENGTH; j++) {
454            data[j] = -1;
455        }
456
457        return data;
458    }
459
460    protected abstract void deleteSms(int index, Message response);
461
462    protected abstract void writeSms(int status, byte[] pdu, byte[] smsc, Message response);
463
464    protected abstract void log(String msg);
465
466}
467