Home | History | Annotate | Download | only in stk
      1 /*
      2  * Copyright (C) 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 
     17 package com.android.internal.telephony.gsm.stk;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.os.AsyncResult;
     22 import android.os.Handler;
     23 import android.os.HandlerThread;
     24 import android.os.Message;
     25 
     26 import com.android.internal.telephony.IccUtils;
     27 import com.android.internal.telephony.CommandsInterface;
     28 import com.android.internal.telephony.gsm.SimCard;
     29 import com.android.internal.telephony.gsm.SIMFileHandler;
     30 import com.android.internal.telephony.gsm.SIMRecords;
     31 
     32 import android.util.Config;
     33 
     34 import java.io.ByteArrayOutputStream;
     35 
     36 /**
     37  * Enumeration for representing the tag value of COMPREHENSION-TLV objects. If
     38  * you want to get the actual value, call {@link #value() value} method.
     39  *
     40  * {@hide}
     41  */
     42 enum ComprehensionTlvTag {
     43   COMMAND_DETAILS(0x01),
     44   DEVICE_IDENTITIES(0x02),
     45   RESULT(0x03),
     46   DURATION(0x04),
     47   ALPHA_ID(0x05),
     48   USSD_STRING(0x0a),
     49   TEXT_STRING(0x0d),
     50   TONE(0x0e),
     51   ITEM(0x0f),
     52   ITEM_ID(0x10),
     53   RESPONSE_LENGTH(0x11),
     54   FILE_LIST(0x12),
     55   HELP_REQUEST(0x15),
     56   DEFAULT_TEXT(0x17),
     57   EVENT_LIST(0x19),
     58   ICON_ID(0x1e),
     59   ITEM_ICON_ID_LIST(0x1f),
     60   IMMEDIATE_RESPONSE(0x2b),
     61   LANGUAGE(0x2d),
     62   URL(0x31),
     63   BROWSER_TERMINATION_CAUSE(0x34),
     64   TEXT_ATTRIBUTE(0x50);
     65 
     66     private int mValue;
     67 
     68     ComprehensionTlvTag(int value) {
     69         mValue = value;
     70     }
     71 
     72     /**
     73      * Returns the actual value of this COMPREHENSION-TLV object.
     74      *
     75      * @return Actual tag value of this object
     76      */
     77         public int value() {
     78             return mValue;
     79         }
     80 
     81     public static ComprehensionTlvTag fromInt(int value) {
     82         for (ComprehensionTlvTag e : ComprehensionTlvTag.values()) {
     83             if (e.mValue == value) {
     84                 return e;
     85             }
     86         }
     87         return null;
     88     }
     89 }
     90 
     91 class RilMessage {
     92     int mId;
     93     Object mData;
     94     ResultCode mResCode;
     95 
     96     RilMessage(int msgId, String rawData) {
     97         mId = msgId;
     98         mData = rawData;
     99     }
    100 
    101     RilMessage(RilMessage other) {
    102         this.mId = other.mId;
    103         this.mData = other.mData;
    104         this.mResCode = other.mResCode;
    105     }
    106 }
    107 
    108 /**
    109  * Class that implements SIM Toolkit Telephony Service. Interacts with the RIL
    110  * and application.
    111  *
    112  * {@hide}
    113  */
    114 public class StkService extends Handler implements AppInterface {
    115 
    116     // Class members
    117     private static SIMRecords mSimRecords;
    118 
    119     // Service members.
    120     private static StkService sInstance;
    121     private CommandsInterface mCmdIf;
    122     private Context mContext;
    123     private StkCmdMessage mCurrntCmd = null;
    124     private StkCmdMessage mMenuCmd = null;
    125 
    126     private RilMessageDecoder mMsgDecoder = null;
    127 
    128     // Service constants.
    129     static final int MSG_ID_SESSION_END              = 1;
    130     static final int MSG_ID_PROACTIVE_COMMAND        = 2;
    131     static final int MSG_ID_EVENT_NOTIFY             = 3;
    132     static final int MSG_ID_CALL_SETUP               = 4;
    133     static final int MSG_ID_REFRESH                  = 5;
    134     static final int MSG_ID_RESPONSE                 = 6;
    135 
    136     static final int MSG_ID_RIL_MSG_DECODED          = 10;
    137 
    138     // Events to signal SIM presence or absent in the device.
    139     private static final int MSG_ID_SIM_LOADED       = 20;
    140 
    141     private static final int DEV_ID_KEYPAD      = 0x01;
    142     private static final int DEV_ID_DISPLAY     = 0x02;
    143     private static final int DEV_ID_EARPIECE    = 0x03;
    144     private static final int DEV_ID_UICC        = 0x81;
    145     private static final int DEV_ID_TERMINAL    = 0x82;
    146     private static final int DEV_ID_NETWORK     = 0x83;
    147 
    148     /* Intentionally private for singleton */
    149     private StkService(CommandsInterface ci, SIMRecords sr, Context context,
    150             SIMFileHandler fh, SimCard sc) {
    151         if (ci == null || sr == null || context == null || fh == null
    152                 || sc == null) {
    153             throw new NullPointerException(
    154                     "Service: Input parameters must not be null");
    155         }
    156         mCmdIf = ci;
    157         mContext = context;
    158 
    159         // Get the RilMessagesDecoder for decoding the messages.
    160         mMsgDecoder = RilMessageDecoder.getInstance(this, fh);
    161 
    162         // Register ril events handling.
    163         mCmdIf.setOnStkSessionEnd(this, MSG_ID_SESSION_END, null);
    164         mCmdIf.setOnStkProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);
    165         mCmdIf.setOnStkEvent(this, MSG_ID_EVENT_NOTIFY, null);
    166         mCmdIf.setOnStkCallSetUp(this, MSG_ID_CALL_SETUP, null);
    167         //mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null);
    168 
    169         mSimRecords = sr;
    170 
    171         // Register for SIM ready event.
    172         mSimRecords.registerForRecordsLoaded(this, MSG_ID_SIM_LOADED, null);
    173 
    174         mCmdIf.reportStkServiceIsRunning(null);
    175         StkLog.d(this, "StkService: is running");
    176     }
    177 
    178     public void dispose() {
    179         mSimRecords.unregisterForRecordsLoaded(this);
    180         mCmdIf.unSetOnStkSessionEnd(this);
    181         mCmdIf.unSetOnStkProactiveCmd(this);
    182         mCmdIf.unSetOnStkEvent(this);
    183         mCmdIf.unSetOnStkCallSetUp(this);
    184 
    185         this.removeCallbacksAndMessages(null);
    186     }
    187 
    188     protected void finalize() {
    189         StkLog.d(this, "Service finalized");
    190     }
    191 
    192     private void handleRilMsg(RilMessage rilMsg) {
    193         if (rilMsg == null) {
    194             return;
    195         }
    196 
    197         // dispatch messages
    198         CommandParams cmdParams = null;
    199         switch (rilMsg.mId) {
    200         case MSG_ID_EVENT_NOTIFY:
    201             if (rilMsg.mResCode == ResultCode.OK) {
    202                 cmdParams = (CommandParams) rilMsg.mData;
    203                 if (cmdParams != null) {
    204                     handleProactiveCommand(cmdParams);
    205                 }
    206             }
    207             break;
    208         case MSG_ID_PROACTIVE_COMMAND:
    209             cmdParams = (CommandParams) rilMsg.mData;
    210             if (cmdParams != null) {
    211                 if (rilMsg.mResCode == ResultCode.OK) {
    212                     handleProactiveCommand(cmdParams);
    213                 } else {
    214                     // for proactive commands that couldn't be decoded
    215                     // successfully respond with the code generated by the
    216                     // message decoder.
    217                     sendTerminalResponse(cmdParams.cmdDet, rilMsg.mResCode,
    218                             false, 0, null);
    219                 }
    220             }
    221             break;
    222         case MSG_ID_REFRESH:
    223             cmdParams = (CommandParams) rilMsg.mData;
    224             if (cmdParams != null) {
    225                 handleProactiveCommand(cmdParams);
    226             }
    227             break;
    228         case MSG_ID_SESSION_END:
    229             handleSessionEnd();
    230             break;
    231         case MSG_ID_CALL_SETUP:
    232             // prior event notify command supplied all the information
    233             // needed for set up call processing.
    234             break;
    235         }
    236     }
    237 
    238     /**
    239      * Handles RIL_UNSOL_STK_PROACTIVE_COMMAND unsolicited command from RIL.
    240      * Sends valid proactive command data to the application using intents.
    241      *
    242      */
    243     private void handleProactiveCommand(CommandParams cmdParams) {
    244         StkLog.d(this, cmdParams.getCommandType().name());
    245 
    246         StkCmdMessage cmdMsg = new StkCmdMessage(cmdParams);
    247         switch (cmdParams.getCommandType()) {
    248         case SET_UP_MENU:
    249             if (removeMenu(cmdMsg.getMenu())) {
    250                 mMenuCmd = null;
    251             } else {
    252                 mMenuCmd = cmdMsg;
    253             }
    254             sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0,
    255                     null);
    256             break;
    257         case DISPLAY_TEXT:
    258             // when application is not required to respond, send an immediate
    259             // response.
    260             if (!cmdMsg.geTextMessage().responseNeeded) {
    261                 sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false,
    262                         0, null);
    263             }
    264             break;
    265         case REFRESH:
    266             // ME side only handles refresh commands which meant to remove IDLE
    267             // MODE TEXT.
    268             cmdParams.cmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT
    269                     .value();
    270             break;
    271         case SET_UP_IDLE_MODE_TEXT:
    272             sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false,
    273                     0, null);
    274             break;
    275         case LAUNCH_BROWSER:
    276         case SELECT_ITEM:
    277         case GET_INPUT:
    278         case GET_INKEY:
    279         case SEND_DTMF:
    280         case SEND_SMS:
    281         case SEND_SS:
    282         case SEND_USSD:
    283         case PLAY_TONE:
    284         case SET_UP_CALL:
    285             // nothing to do on telephony!
    286             break;
    287         default:
    288             StkLog.d(this, "Unsupported command");
    289             return;
    290         }
    291         mCurrntCmd = cmdMsg;
    292         Intent intent = new Intent(AppInterface.STK_CMD_ACTION);
    293         intent.putExtra("STK CMD", cmdMsg);
    294         mContext.sendBroadcast(intent);
    295     }
    296 
    297     /**
    298      * Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL.
    299      *
    300      */
    301     private void handleSessionEnd() {
    302         StkLog.d(this, "SESSION END");
    303 
    304         mCurrntCmd = mMenuCmd;
    305         Intent intent = new Intent(AppInterface.STK_SESSION_END_ACTION);
    306         mContext.sendBroadcast(intent);
    307     }
    308 
    309     private void sendTerminalResponse(CommandDetails cmdDet,
    310             ResultCode resultCode, boolean includeAdditionalInfo,
    311             int additionalInfo, ResponseData resp) {
    312 
    313         if (cmdDet == null) {
    314             return;
    315         }
    316         ByteArrayOutputStream buf = new ByteArrayOutputStream();
    317 
    318         // command details
    319         int tag = ComprehensionTlvTag.COMMAND_DETAILS.value();
    320         if (cmdDet.compRequired) {
    321             tag |= 0x80;
    322         }
    323         buf.write(tag);
    324         buf.write(0x03); // length
    325         buf.write(cmdDet.commandNumber);
    326         buf.write(cmdDet.typeOfCommand);
    327         buf.write(cmdDet.commandQualifier);
    328 
    329         // device identities
    330         tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
    331         buf.write(tag);
    332         buf.write(0x02); // length
    333         buf.write(DEV_ID_TERMINAL); // source device id
    334         buf.write(DEV_ID_UICC); // destination device id
    335 
    336         // result
    337         tag = 0x80 | ComprehensionTlvTag.RESULT.value();
    338         buf.write(tag);
    339         int length = includeAdditionalInfo ? 2 : 1;
    340         buf.write(length);
    341         buf.write(resultCode.value());
    342 
    343         // additional info
    344         if (includeAdditionalInfo) {
    345             buf.write(additionalInfo);
    346         }
    347 
    348         // Fill optional data for each corresponding command
    349         if (resp != null) {
    350             resp.format(buf);
    351         }
    352 
    353         byte[] rawData = buf.toByteArray();
    354         String hexString = IccUtils.bytesToHexString(rawData);
    355         if (Config.LOGD) {
    356             StkLog.d(this, "TERMINAL RESPONSE: " + hexString);
    357         }
    358 
    359         mCmdIf.sendTerminalResponse(hexString, null);
    360     }
    361 
    362 
    363     private void sendMenuSelection(int menuId, boolean helpRequired) {
    364 
    365         ByteArrayOutputStream buf = new ByteArrayOutputStream();
    366 
    367         // tag
    368         int tag = BerTlv.BER_MENU_SELECTION_TAG;
    369         buf.write(tag);
    370 
    371         // length
    372         buf.write(0x00); // place holder
    373 
    374         // device identities
    375         tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
    376         buf.write(tag);
    377         buf.write(0x02); // length
    378         buf.write(DEV_ID_KEYPAD); // source device id
    379         buf.write(DEV_ID_UICC); // destination device id
    380 
    381         // item identifier
    382         tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value();
    383         buf.write(tag);
    384         buf.write(0x01); // length
    385         buf.write(menuId); // menu identifier chosen
    386 
    387         // help request
    388         if (helpRequired) {
    389             tag = ComprehensionTlvTag.HELP_REQUEST.value();
    390             buf.write(tag);
    391             buf.write(0x00); // length
    392         }
    393 
    394         byte[] rawData = buf.toByteArray();
    395 
    396         // write real length
    397         int len = rawData.length - 2; // minus (tag + length)
    398         rawData[1] = (byte) len;
    399 
    400         String hexString = IccUtils.bytesToHexString(rawData);
    401 
    402         mCmdIf.sendEnvelope(hexString, null);
    403     }
    404 
    405     private void eventDownload(int event, int sourceId, int destinationId,
    406             byte[] additionalInfo, boolean oneShot) {
    407 
    408         ByteArrayOutputStream buf = new ByteArrayOutputStream();
    409 
    410         // tag
    411         int tag = BerTlv.BER_EVENT_DOWNLOAD_TAG;
    412         buf.write(tag);
    413 
    414         // length
    415         buf.write(0x00); // place holder, assume length < 128.
    416 
    417         // event list
    418         tag = 0x80 | ComprehensionTlvTag.EVENT_LIST.value();
    419         buf.write(tag);
    420         buf.write(0x01); // length
    421         buf.write(event); // event value
    422 
    423         // device identities
    424         tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
    425         buf.write(tag);
    426         buf.write(0x02); // length
    427         buf.write(sourceId); // source device id
    428         buf.write(destinationId); // destination device id
    429 
    430         // additional information
    431         if (additionalInfo != null) {
    432             for (byte b : additionalInfo) {
    433                 buf.write(b);
    434             }
    435         }
    436 
    437         byte[] rawData = buf.toByteArray();
    438 
    439         // write real length
    440         int len = rawData.length - 2; // minus (tag + length)
    441         rawData[1] = (byte) len;
    442 
    443         String hexString = IccUtils.bytesToHexString(rawData);
    444 
    445         mCmdIf.sendEnvelope(hexString, null);
    446     }
    447 
    448     /**
    449      * Used for instantiating/updating the Service from the GsmPhone constructor.
    450      *
    451      * @param ci CommandsInterface object
    452      * @param sr SIMRecords object
    453      * @param context phone app context
    454      * @param fh SIM file handler
    455      * @param sc GSM SIM card
    456      * @return The only Service object in the system
    457      */
    458     public static StkService getInstance(CommandsInterface ci, SIMRecords sr,
    459             Context context, SIMFileHandler fh, SimCard sc) {
    460         if (sInstance == null) {
    461             if (ci == null || sr == null || context == null || fh == null
    462                     || sc == null) {
    463                 return null;
    464             }
    465             HandlerThread thread = new HandlerThread("Stk Telephony service");
    466             thread.start();
    467             sInstance = new StkService(ci, sr, context, fh, sc);
    468             StkLog.d(sInstance, "NEW sInstance");
    469         } else if ((sr != null) && (mSimRecords != sr)) {
    470             StkLog.d(sInstance, "Reinitialize the Service with SIMRecords");
    471             mSimRecords = sr;
    472 
    473             // re-Register for SIM ready event.
    474             mSimRecords.registerForRecordsLoaded(sInstance, MSG_ID_SIM_LOADED, null);
    475             StkLog.d(sInstance, "sr changed reinitialize and return current sInstance");
    476         } else {
    477             StkLog.d(sInstance, "Return current sInstance");
    478         }
    479         return sInstance;
    480     }
    481 
    482     /**
    483      * Used by application to get an AppInterface object.
    484      *
    485      * @return The only Service object in the system
    486      */
    487     public static AppInterface getInstance() {
    488         return getInstance(null, null, null, null, null);
    489     }
    490 
    491     @Override
    492     public void handleMessage(Message msg) {
    493 
    494         switch (msg.what) {
    495         case MSG_ID_SESSION_END:
    496         case MSG_ID_PROACTIVE_COMMAND:
    497         case MSG_ID_EVENT_NOTIFY:
    498         case MSG_ID_REFRESH:
    499             StkLog.d(this, "ril message arrived");
    500             String data = null;
    501             if (msg.obj != null) {
    502                 AsyncResult ar = (AsyncResult) msg.obj;
    503                 if (ar != null && ar.result != null) {
    504                     try {
    505                         data = (String) ar.result;
    506                     } catch (ClassCastException e) {
    507                         break;
    508                     }
    509                 }
    510             }
    511             mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
    512             break;
    513         case MSG_ID_CALL_SETUP:
    514             mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null));
    515             break;
    516         case MSG_ID_SIM_LOADED:
    517             break;
    518         case MSG_ID_RIL_MSG_DECODED:
    519             handleRilMsg((RilMessage) msg.obj);
    520             break;
    521         case MSG_ID_RESPONSE:
    522             handleCmdResponse((StkResponseMessage) msg.obj);
    523             break;
    524         default:
    525             throw new AssertionError("Unrecognized STK command: " + msg.what);
    526         }
    527     }
    528 
    529     public synchronized void onCmdResponse(StkResponseMessage resMsg) {
    530         if (resMsg == null) {
    531             return;
    532         }
    533         // queue a response message.
    534         Message msg = this.obtainMessage(MSG_ID_RESPONSE, resMsg);
    535         msg.sendToTarget();
    536     }
    537 
    538     private boolean validateResponse(StkResponseMessage resMsg) {
    539         if (mCurrntCmd != null) {
    540             return (resMsg.cmdDet.compareTo(mCurrntCmd.mCmdDet));
    541         }
    542         return false;
    543     }
    544 
    545     private boolean removeMenu(Menu menu) {
    546         try {
    547             if (menu.items.size() == 1 && menu.items.get(0) == null) {
    548                 return true;
    549             }
    550         } catch (NullPointerException e) {
    551             StkLog.d(this, "Unable to get Menu's items size");
    552             return true;
    553         }
    554         return false;
    555     }
    556 
    557     private void handleCmdResponse(StkResponseMessage resMsg) {
    558         // Make sure the response details match the last valid command. An invalid
    559         // response is a one that doesn't have a corresponding proactive command
    560         // and sending it can "confuse" the baseband/ril.
    561         // One reason for out of order responses can be UI glitches. For example,
    562         // if the application launch an activity, and that activity is stored
    563         // by the framework inside the history stack. That activity will be
    564         // available for relaunch using the latest application dialog
    565         // (long press on the home button). Relaunching that activity can send
    566         // the same command's result again to the StkService and can cause it to
    567         // get out of sync with the SIM.
    568         if (!validateResponse(resMsg)) {
    569             return;
    570         }
    571         ResponseData resp = null;
    572         boolean helpRequired = false;
    573         CommandDetails cmdDet = resMsg.getCmdDetails();
    574 
    575         switch (resMsg.resCode) {
    576         case HELP_INFO_REQUIRED:
    577             helpRequired = true;
    578             // fall through
    579         case OK:
    580         case PRFRMD_WITH_PARTIAL_COMPREHENSION:
    581         case PRFRMD_WITH_MISSING_INFO:
    582         case PRFRMD_WITH_ADDITIONAL_EFS_READ:
    583         case PRFRMD_ICON_NOT_DISPLAYED:
    584         case PRFRMD_MODIFIED_BY_NAA:
    585         case PRFRMD_LIMITED_SERVICE:
    586         case PRFRMD_WITH_MODIFICATION:
    587         case PRFRMD_NAA_NOT_ACTIVE:
    588         case PRFRMD_TONE_NOT_PLAYED:
    589             switch (AppInterface.CommandType.fromInt(cmdDet.typeOfCommand)) {
    590             case SET_UP_MENU:
    591                 helpRequired = resMsg.resCode == ResultCode.HELP_INFO_REQUIRED;
    592                 sendMenuSelection(resMsg.usersMenuSelection, helpRequired);
    593                 return;
    594             case SELECT_ITEM:
    595                 resp = new SelectItemResponseData(resMsg.usersMenuSelection);
    596                 break;
    597             case GET_INPUT:
    598             case GET_INKEY:
    599                 Input input = mCurrntCmd.geInput();
    600                 if (!input.yesNo) {
    601                     // when help is requested there is no need to send the text
    602                     // string object.
    603                     if (!helpRequired) {
    604                         resp = new GetInkeyInputResponseData(resMsg.usersInput,
    605                                 input.ucs2, input.packed);
    606                     }
    607                 } else {
    608                     resp = new GetInkeyInputResponseData(
    609                             resMsg.usersYesNoSelection);
    610                 }
    611                 break;
    612             case DISPLAY_TEXT:
    613             case LAUNCH_BROWSER:
    614                 break;
    615             case SET_UP_CALL:
    616                 mCmdIf.handleCallSetupRequestFromSim(resMsg.usersConfirm, null);
    617                 // No need to send terminal response for SET UP CALL. The user's
    618                 // confirmation result is send back using a dedicated ril message
    619                 // invoked by the CommandInterface call above.
    620                 mCurrntCmd = null;
    621                 return;
    622             }
    623             break;
    624         case NO_RESPONSE_FROM_USER:
    625         case UICC_SESSION_TERM_BY_USER:
    626         case BACKWARD_MOVE_BY_USER:
    627             resp = null;
    628             break;
    629         default:
    630             return;
    631         }
    632         sendTerminalResponse(cmdDet, resMsg.resCode, false, 0, resp);
    633         mCurrntCmd = null;
    634     }
    635 }
    636