IccSmsInterfaceManager.java revision cbaa45bbf2cab852b6c9c3a887e9f803d4e857ea
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 = "IccSmsInterfaceManager";
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    @Override
162    public boolean
163    updateMessageOnIccEf(String callingPackage, int index, int status, byte[] pdu) {
164        if (DBG) log("updateMessageOnIccEf: index=" + index +
165                " status=" + status + " ==> " +
166                "("+ Arrays.toString(pdu) + ")");
167        enforceReceiveAndSend("Updating message on Icc");
168        if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(),
169                callingPackage) != AppOpsManager.MODE_ALLOWED) {
170            return false;
171        }
172        synchronized(mLock) {
173            mSuccess = false;
174            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
175
176            if (status == STATUS_ON_ICC_FREE) {
177                // RIL_REQUEST_DELETE_SMS_ON_SIM vs RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM
178                // Special case FREE: call deleteSmsOnSim/Ruim instead of
179                // manipulating the record
180                // Will eventually fail if icc card is not present.
181                deleteSms(index, response);
182            } else {
183                //IccFilehandler can be null if ICC card is not present.
184                IccFileHandler fh = mPhone.getIccFileHandler();
185                if (fh == null) {
186                    response.recycle();
187                    return mSuccess; /* is false */
188                }
189                byte[] record = makeSmsRecordData(status, pdu);
190                fh.updateEFLinearFixed(
191                        IccConstants.EF_SMS,
192                        index, record, null, response);
193            }
194            try {
195                mLock.wait();
196            } catch (InterruptedException e) {
197                log("interrupted while trying to update by index");
198            }
199        }
200        return mSuccess;
201    }
202
203    /**
204     * Copy a raw SMS PDU to the Icc.
205     *
206     * @param pdu the raw PDU to store
207     * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
208     *               STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
209     * @return success or not
210     *
211     */
212    @Override
213    public boolean copyMessageToIccEf(String callingPackage, int status, byte[] pdu, byte[] smsc) {
214        //NOTE smsc not used in RUIM
215        if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
216                "pdu=("+ Arrays.toString(pdu) +
217                "), smsc=(" + Arrays.toString(smsc) +")");
218        enforceReceiveAndSend("Copying message to Icc");
219        if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(),
220                callingPackage) != AppOpsManager.MODE_ALLOWED) {
221            return false;
222        }
223        synchronized(mLock) {
224            mSuccess = false;
225            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
226
227            //RIL_REQUEST_WRITE_SMS_TO_SIM vs RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM
228            writeSms(status, smsc, pdu, response);
229
230            try {
231                mLock.wait();
232            } catch (InterruptedException e) {
233                log("interrupted while trying to update by index");
234            }
235        }
236        return mSuccess;
237    }
238
239    /**
240     * Retrieves all messages currently stored on Icc.
241     *
242     * @return list of SmsRawData of all sms on Icc
243     */
244    @Override
245    public List<SmsRawData> getAllMessagesFromIccEf(String callingPackage) {
246        if (DBG) log("getAllMessagesFromEF");
247
248        mContext.enforceCallingPermission(
249                Manifest.permission.RECEIVE_SMS,
250                "Reading messages from Icc");
251        if (mAppOps.noteOp(AppOpsManager.OP_READ_ICC_SMS, Binder.getCallingUid(),
252                callingPackage) != AppOpsManager.MODE_ALLOWED) {
253            return new ArrayList<SmsRawData>();
254        }
255        synchronized(mLock) {
256
257            IccFileHandler fh = mPhone.getIccFileHandler();
258            if (fh == null) {
259                Rlog.e(LOG_TAG, "Cannot load Sms records. No icc card?");
260                if (mSms != null) {
261                    mSms.clear();
262                    return mSms;
263                }
264            }
265
266            Message response = mHandler.obtainMessage(EVENT_LOAD_DONE);
267            fh.loadEFLinearFixedAll(IccConstants.EF_SMS, response);
268
269            try {
270                mLock.wait();
271            } catch (InterruptedException e) {
272                log("interrupted while trying to load from the Icc");
273            }
274        }
275        return mSms;
276    }
277
278    /**
279     * Send a data based SMS to a specific application port.
280     *
281     * @param destAddr the address to send the message to
282     * @param scAddr is the service center address or null to use
283     *  the current default SMSC
284     * @param destPort the port to deliver the message to
285     * @param data the body of the message to send
286     * @param sentIntent if not NULL this <code>PendingIntent</code> is
287     *  broadcast when the message is successfully sent, or failed.
288     *  The result code will be <code>Activity.RESULT_OK<code> for success,
289     *  or one of these errors:<br>
290     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
291     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
292     *  <code>RESULT_ERROR_NULL_PDU</code><br>
293     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
294     *  the extra "errorCode" containing a radio technology specific value,
295     *  generally only useful for troubleshooting.<br>
296     *  The per-application based SMS control checks sentIntent. If sentIntent
297     *  is NULL the caller will be checked against all unknown applications,
298     *  which cause smaller number of SMS to be sent in checking period.
299     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
300     *  broadcast when the message is delivered to the recipient.  The
301     *  raw pdu of the status report is in the extended data ("pdu").
302     */
303    @Override
304    public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
305            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
306        mPhone.getContext().enforceCallingPermission(
307                Manifest.permission.SEND_SMS,
308                "Sending SMS message");
309        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
310            log("sendData: destAddr=" + destAddr + " scAddr=" + scAddr + " destPort=" +
311                destPort + " data='"+ HexDump.toHexString(data)  + "' sentIntent=" +
312                sentIntent + " deliveryIntent=" + deliveryIntent);
313        }
314        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
315                callingPackage) != AppOpsManager.MODE_ALLOWED) {
316            return;
317        }
318        mDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
319    }
320
321    /**
322     * Send a text based SMS.
323     *
324     * @param destAddr the address to send the message to
325     * @param scAddr is the service center address or null to use
326     *  the current default SMSC
327     * @param text the body of the message to send
328     * @param sentIntent if not NULL this <code>PendingIntent</code> is
329     *  broadcast when the message is successfully sent, or failed.
330     *  The result code will be <code>Activity.RESULT_OK<code> for success,
331     *  or one of these errors:<br>
332     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
333     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
334     *  <code>RESULT_ERROR_NULL_PDU</code><br>
335     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
336     *  the extra "errorCode" containing a radio technology specific value,
337     *  generally only useful for troubleshooting.<br>
338     *  The per-application based SMS control checks sentIntent. If sentIntent
339     *  is NULL the caller will be checked against all unknown applications,
340     *  which cause smaller number of SMS to be sent in checking period.
341     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
342     *  broadcast when the message is delivered to the recipient.  The
343     *  raw pdu of the status report is in the extended data ("pdu").
344     */
345    @Override
346    public void sendText(String callingPackage, String destAddr, String scAddr,
347            String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
348        mPhone.getContext().enforceCallingPermission(
349                Manifest.permission.SEND_SMS,
350                "Sending SMS message");
351        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
352            log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +
353                " text='"+ text + "' sentIntent=" +
354                sentIntent + " deliveryIntent=" + deliveryIntent);
355        }
356        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
357                callingPackage) != AppOpsManager.MODE_ALLOWED) {
358            return;
359        }
360        mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent);
361    }
362
363    /**
364     * Send a multi-part text based SMS.
365     *
366     * @param destAddr the address to send the message to
367     * @param scAddr is the service center address or null to use
368     *   the current default SMSC
369     * @param parts an <code>ArrayList</code> of strings that, in order,
370     *   comprise the original message
371     * @param sentIntents if not null, an <code>ArrayList</code> of
372     *   <code>PendingIntent</code>s (one for each message part) that is
373     *   broadcast when the corresponding message part has been sent.
374     *   The result code will be <code>Activity.RESULT_OK<code> for success,
375     *   or one of these errors:
376     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
377     *   <code>RESULT_ERROR_RADIO_OFF</code>
378     *   <code>RESULT_ERROR_NULL_PDU</code>.
379     *  The per-application based SMS control checks sentIntent. If sentIntent
380     *  is NULL the caller will be checked against all unknown applications,
381     *  which cause smaller number of SMS to be sent in checking period.
382     * @param deliveryIntents if not null, an <code>ArrayList</code> of
383     *   <code>PendingIntent</code>s (one for each message part) that is
384     *   broadcast when the corresponding message part has been delivered
385     *   to the recipient.  The raw pdu of the status report is in the
386     *   extended data ("pdu").
387     */
388    @Override
389    public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
390            List<String> parts, List<PendingIntent> sentIntents,
391            List<PendingIntent> deliveryIntents) {
392        mPhone.getContext().enforceCallingPermission(
393                Manifest.permission.SEND_SMS,
394                "Sending SMS message");
395        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
396            int i = 0;
397            for (String part : parts) {
398                log("sendMultipartText: destAddr=" + destAddr + ", srAddr=" + scAddr +
399                        ", part[" + (i++) + "]=" + part);
400            }
401        }
402        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
403                callingPackage) != AppOpsManager.MODE_ALLOWED) {
404            return;
405        }
406        mDispatcher.sendMultipartText(destAddr, scAddr, (ArrayList<String>) parts,
407                (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents);
408    }
409
410    @Override
411    public int getPremiumSmsPermission(String packageName) {
412        return mDispatcher.getPremiumSmsPermission(packageName);
413    }
414
415    @Override
416    public void setPremiumSmsPermission(String packageName, int permission) {
417        mDispatcher.setPremiumSmsPermission(packageName, permission);
418    }
419
420    /**
421     * create SmsRawData lists from all sms record byte[]
422     * Use null to indicate "free" record
423     *
424     * @param messages List of message records from EF_SMS.
425     * @return SmsRawData list of all in-used records
426     */
427    protected ArrayList<SmsRawData> buildValidRawData(ArrayList<byte[]> messages) {
428        int count = messages.size();
429        ArrayList<SmsRawData> ret;
430
431        ret = new ArrayList<SmsRawData>(count);
432
433        for (int i = 0; i < count; i++) {
434            byte[] ba = messages.get(i);
435            if (ba[0] == STATUS_ON_ICC_FREE) {
436                ret.add(null);
437            } else {
438                ret.add(new SmsRawData(messages.get(i)));
439            }
440        }
441
442        return ret;
443    }
444
445    /**
446     * Generates an EF_SMS record from status and raw PDU.
447     *
448     * @param status Message status.  See TS 51.011 10.5.3.
449     * @param pdu Raw message PDU.
450     * @return byte array for the record.
451     */
452    protected byte[] makeSmsRecordData(int status, byte[] pdu) {
453        byte[] data = new byte[IccConstants.SMS_RECORD_LENGTH];
454
455        // Status bits for this record.  See TS 51.011 10.5.3
456        data[0] = (byte)(status & 7);
457
458        System.arraycopy(pdu, 0, data, 1, pdu.length);
459
460        // Pad out with 0xFF's.
461        for (int j = pdu.length+1; j < IccConstants.SMS_RECORD_LENGTH; j++) {
462            data[j] = -1;
463        }
464
465        return data;
466    }
467
468    protected abstract void deleteSms(int index, Message response);
469
470    protected abstract void writeSms(int status, byte[] pdu, byte[] smsc, Message response);
471
472    protected abstract void log(String msg);
473
474}
475