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.cat; 18 19 import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants 20 .IDLE_SCREEN_AVAILABLE_EVENT; 21 import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants 22 .LANGUAGE_SELECTION_EVENT; 23 import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants 24 .USER_ACTIVITY_EVENT; 25 26 import android.annotation.UnsupportedAppUsage; 27 import android.app.ActivityManagerNative; 28 import android.app.IActivityManager; 29 import android.app.backup.BackupManager; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.pm.PackageManager; 33 import android.content.pm.ResolveInfo; 34 import android.content.res.Configuration; 35 import android.content.res.Resources.NotFoundException; 36 import android.os.AsyncResult; 37 import android.os.Handler; 38 import android.os.LocaleList; 39 import android.os.Message; 40 import android.os.RemoteException; 41 import android.telephony.SubscriptionManager; 42 import android.telephony.TelephonyManager; 43 44 import com.android.internal.telephony.CommandsInterface; 45 import com.android.internal.telephony.PhoneConstants; 46 import com.android.internal.telephony.SubscriptionController; 47 import com.android.internal.telephony.uicc.IccCardStatus.CardState; 48 import com.android.internal.telephony.uicc.IccFileHandler; 49 import com.android.internal.telephony.uicc.IccRecords; 50 import com.android.internal.telephony.uicc.IccRefreshResponse; 51 import com.android.internal.telephony.uicc.IccUtils; 52 import com.android.internal.telephony.uicc.UiccCard; 53 import com.android.internal.telephony.uicc.UiccCardApplication; 54 import com.android.internal.telephony.uicc.UiccController; 55 import com.android.internal.telephony.uicc.UiccProfile; 56 57 import java.io.ByteArrayOutputStream; 58 import java.util.List; 59 import java.util.Locale; 60 61 class RilMessage { 62 @UnsupportedAppUsage 63 int mId; 64 @UnsupportedAppUsage 65 Object mData; 66 ResultCode mResCode; 67 68 @UnsupportedAppUsage RilMessage(int msgId, String rawData)69 RilMessage(int msgId, String rawData) { 70 mId = msgId; 71 mData = rawData; 72 } 73 RilMessage(RilMessage other)74 RilMessage(RilMessage other) { 75 mId = other.mId; 76 mData = other.mData; 77 mResCode = other.mResCode; 78 } 79 } 80 81 /** 82 * Class that implements SIM Toolkit Telephony Service. Interacts with the RIL 83 * and application. 84 * 85 * {@hide} 86 */ 87 public class CatService extends Handler implements AppInterface { 88 private static final boolean DBG = false; 89 90 // Class members 91 private static IccRecords mIccRecords; 92 private static UiccCardApplication mUiccApplication; 93 94 // Service members. 95 // Protects singleton instance lazy initialization. 96 @UnsupportedAppUsage 97 private static final Object sInstanceLock = new Object(); 98 @UnsupportedAppUsage 99 private static CatService[] sInstance = null; 100 @UnsupportedAppUsage 101 private CommandsInterface mCmdIf; 102 @UnsupportedAppUsage 103 private Context mContext; 104 @UnsupportedAppUsage 105 private CatCmdMessage mCurrntCmd = null; 106 @UnsupportedAppUsage 107 private CatCmdMessage mMenuCmd = null; 108 109 @UnsupportedAppUsage 110 private RilMessageDecoder mMsgDecoder = null; 111 @UnsupportedAppUsage 112 private boolean mStkAppInstalled = false; 113 114 @UnsupportedAppUsage 115 private UiccController mUiccController; 116 private CardState mCardState = CardState.CARDSTATE_ABSENT; 117 118 // Service constants. 119 protected static final int MSG_ID_SESSION_END = 1; 120 protected static final int MSG_ID_PROACTIVE_COMMAND = 2; 121 protected static final int MSG_ID_EVENT_NOTIFY = 3; 122 protected static final int MSG_ID_CALL_SETUP = 4; 123 static final int MSG_ID_REFRESH = 5; 124 static final int MSG_ID_RESPONSE = 6; 125 static final int MSG_ID_SIM_READY = 7; 126 127 protected static final int MSG_ID_ICC_CHANGED = 8; 128 protected static final int MSG_ID_ALPHA_NOTIFY = 9; 129 130 static final int MSG_ID_RIL_MSG_DECODED = 10; 131 132 // Events to signal SIM presence or absent in the device. 133 private static final int MSG_ID_ICC_RECORDS_LOADED = 20; 134 135 //Events to signal SIM REFRESH notificatations 136 private static final int MSG_ID_ICC_REFRESH = 30; 137 138 private static final int DEV_ID_KEYPAD = 0x01; 139 private static final int DEV_ID_DISPLAY = 0x02; 140 private static final int DEV_ID_UICC = 0x81; 141 private static final int DEV_ID_TERMINAL = 0x82; 142 private static final int DEV_ID_NETWORK = 0x83; 143 144 static final String STK_DEFAULT = "Default Message"; 145 146 @UnsupportedAppUsage 147 private int mSlotId; 148 149 /* For multisim catservice should not be singleton */ CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir, Context context, IccFileHandler fh, UiccProfile uiccProfile, int slotId)150 private CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir, 151 Context context, IccFileHandler fh, UiccProfile uiccProfile, int slotId) { 152 if (ci == null || ca == null || ir == null || context == null || fh == null 153 || uiccProfile == null) { 154 throw new NullPointerException( 155 "Service: Input parameters must not be null"); 156 } 157 mCmdIf = ci; 158 mContext = context; 159 mSlotId = slotId; 160 161 // Get the RilMessagesDecoder for decoding the messages. 162 mMsgDecoder = RilMessageDecoder.getInstance(this, fh, slotId); 163 if (null == mMsgDecoder) { 164 CatLog.d(this, "Null RilMessageDecoder instance"); 165 return; 166 } 167 mMsgDecoder.start(); 168 169 // Register ril events handling. 170 mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null); 171 mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null); 172 mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null); 173 mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null); 174 //mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null); 175 176 mCmdIf.registerForIccRefresh(this, MSG_ID_ICC_REFRESH, null); 177 mCmdIf.setOnCatCcAlphaNotify(this, MSG_ID_ALPHA_NOTIFY, null); 178 179 mIccRecords = ir; 180 mUiccApplication = ca; 181 182 // Register for SIM ready event. 183 mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null); 184 CatLog.d(this, "registerForRecordsLoaded slotid=" + mSlotId + " instance:" + this); 185 186 187 mUiccController = UiccController.getInstance(); 188 mUiccController.registerForIccChanged(this, MSG_ID_ICC_CHANGED, null); 189 190 // Check if STK application is available 191 mStkAppInstalled = isStkAppInstalled(); 192 193 CatLog.d(this, "Running CAT service on Slotid: " + mSlotId + 194 ". STK app installed:" + mStkAppInstalled); 195 } 196 197 /** 198 * Used for instantiating the Service from the Card. 199 * 200 * @param ci CommandsInterface object 201 * @param context phone app context 202 * @param ic Icc card 203 * @param slotId to know the index of card 204 * @return The only Service object in the system 205 */ getInstance(CommandsInterface ci, Context context, UiccProfile uiccProfile, int slotId)206 public static CatService getInstance(CommandsInterface ci, 207 Context context, UiccProfile uiccProfile, int slotId) { 208 UiccCardApplication ca = null; 209 IccFileHandler fh = null; 210 IccRecords ir = null; 211 if (uiccProfile != null) { 212 /* Since Cat is not tied to any application, but rather is Uicc application 213 * in itself - just get first FileHandler and IccRecords object 214 */ 215 ca = uiccProfile.getApplicationIndex(0); 216 if (ca != null) { 217 fh = ca.getIccFileHandler(); 218 ir = ca.getIccRecords(); 219 } 220 } 221 222 synchronized (sInstanceLock) { 223 if (sInstance == null) { 224 int simCount = TelephonyManager.getDefault().getSimCount(); 225 sInstance = new CatService[simCount]; 226 for (int i = 0; i < simCount; i++) { 227 sInstance[i] = null; 228 } 229 } 230 if (sInstance[slotId] == null) { 231 if (ci == null || ca == null || ir == null || context == null || fh == null 232 || uiccProfile == null) { 233 return null; 234 } 235 236 sInstance[slotId] = new CatService(ci, ca, ir, context, fh, uiccProfile, slotId); 237 } else if ((ir != null) && (mIccRecords != ir)) { 238 if (mIccRecords != null) { 239 mIccRecords.unregisterForRecordsLoaded(sInstance[slotId]); 240 } 241 242 mIccRecords = ir; 243 mUiccApplication = ca; 244 245 mIccRecords.registerForRecordsLoaded(sInstance[slotId], 246 MSG_ID_ICC_RECORDS_LOADED, null); 247 CatLog.d(sInstance[slotId], "registerForRecordsLoaded slotid=" + slotId 248 + " instance:" + sInstance[slotId]); 249 } 250 return sInstance[slotId]; 251 } 252 } 253 254 @UnsupportedAppUsage dispose()255 public void dispose() { 256 synchronized (sInstanceLock) { 257 CatLog.d(this, "Disposing CatService object"); 258 mIccRecords.unregisterForRecordsLoaded(this); 259 260 // Clean up stk icon if dispose is called 261 broadcastCardStateAndIccRefreshResp(CardState.CARDSTATE_ABSENT, null); 262 263 mCmdIf.unSetOnCatSessionEnd(this); 264 mCmdIf.unSetOnCatProactiveCmd(this); 265 mCmdIf.unSetOnCatEvent(this); 266 mCmdIf.unSetOnCatCallSetUp(this); 267 mCmdIf.unSetOnCatCcAlphaNotify(this); 268 269 mCmdIf.unregisterForIccRefresh(this); 270 if (mUiccController != null) { 271 mUiccController.unregisterForIccChanged(this); 272 mUiccController = null; 273 } 274 mMsgDecoder.dispose(); 275 mMsgDecoder = null; 276 removeCallbacksAndMessages(null); 277 if (sInstance != null) { 278 if (SubscriptionManager.isValidSlotIndex(mSlotId)) { 279 sInstance[mSlotId] = null; 280 } else { 281 CatLog.d(this, "error: invaild slot id: " + mSlotId); 282 } 283 } 284 } 285 } 286 287 @Override finalize()288 protected void finalize() { 289 CatLog.d(this, "Service finalized"); 290 } 291 handleRilMsg(RilMessage rilMsg)292 private void handleRilMsg(RilMessage rilMsg) { 293 if (rilMsg == null) { 294 return; 295 } 296 297 // dispatch messages 298 CommandParams cmdParams = null; 299 switch (rilMsg.mId) { 300 case MSG_ID_EVENT_NOTIFY: 301 if (rilMsg.mResCode == ResultCode.OK) { 302 cmdParams = (CommandParams) rilMsg.mData; 303 if (cmdParams != null) { 304 handleCommand(cmdParams, false); 305 } 306 } 307 break; 308 case MSG_ID_PROACTIVE_COMMAND: 309 try { 310 cmdParams = (CommandParams) rilMsg.mData; 311 } catch (ClassCastException e) { 312 // for error handling : cast exception 313 CatLog.d(this, "Fail to parse proactive command"); 314 // Don't send Terminal Resp if command detail is not available 315 if (mCurrntCmd != null) { 316 sendTerminalResponse(mCurrntCmd.mCmdDet, ResultCode.CMD_DATA_NOT_UNDERSTOOD, 317 false, 0x00, null); 318 } 319 break; 320 } 321 if (cmdParams != null) { 322 if (rilMsg.mResCode == ResultCode.OK) { 323 handleCommand(cmdParams, true); 324 } else { 325 // for proactive commands that couldn't be decoded 326 // successfully respond with the code generated by the 327 // message decoder. 328 sendTerminalResponse(cmdParams.mCmdDet, rilMsg.mResCode, 329 false, 0, null); 330 } 331 } 332 break; 333 case MSG_ID_REFRESH: 334 cmdParams = (CommandParams) rilMsg.mData; 335 if (cmdParams != null) { 336 handleCommand(cmdParams, false); 337 } 338 break; 339 case MSG_ID_SESSION_END: 340 handleSessionEnd(); 341 break; 342 case MSG_ID_CALL_SETUP: 343 // prior event notify command supplied all the information 344 // needed for set up call processing. 345 break; 346 } 347 } 348 349 /** 350 * This function validates the events in SETUP_EVENT_LIST which are currently 351 * supported by the Android framework. In case of SETUP_EVENT_LIST has NULL events 352 * or no events, all the events need to be reset. 353 */ isSupportedSetupEventCommand(CatCmdMessage cmdMsg)354 private boolean isSupportedSetupEventCommand(CatCmdMessage cmdMsg) { 355 boolean flag = true; 356 357 for (int eventVal: cmdMsg.getSetEventList().eventList) { 358 CatLog.d(this,"Event: " + eventVal); 359 switch (eventVal) { 360 /* Currently android is supporting only the below events in SetupEventList 361 * Language Selection. */ 362 case IDLE_SCREEN_AVAILABLE_EVENT: 363 case LANGUAGE_SELECTION_EVENT: 364 case USER_ACTIVITY_EVENT: 365 break; 366 default: 367 flag = false; 368 } 369 } 370 return flag; 371 } 372 373 /** 374 * Handles RIL_UNSOL_STK_EVENT_NOTIFY or RIL_UNSOL_STK_PROACTIVE_COMMAND command 375 * from RIL. 376 * Sends valid proactive command data to the application using intents. 377 * RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE will be send back if the command is 378 * from RIL_UNSOL_STK_PROACTIVE_COMMAND. 379 */ handleCommand(CommandParams cmdParams, boolean isProactiveCmd)380 private void handleCommand(CommandParams cmdParams, boolean isProactiveCmd) { 381 CatLog.d(this, cmdParams.getCommandType().name()); 382 383 // Log all proactive commands. 384 if (isProactiveCmd) { 385 if (mUiccController != null) { 386 mUiccController.addCardLog("ProactiveCommand mSlotId=" + mSlotId + 387 " cmdParams=" + cmdParams); 388 } 389 } 390 391 CharSequence message; 392 ResultCode resultCode; 393 CatCmdMessage cmdMsg = new CatCmdMessage(cmdParams); 394 switch (cmdParams.getCommandType()) { 395 case SET_UP_MENU: 396 if (removeMenu(cmdMsg.getMenu())) { 397 mMenuCmd = null; 398 } else { 399 mMenuCmd = cmdMsg; 400 } 401 resultCode = cmdParams.mLoadIconFailed ? ResultCode.PRFRMD_ICON_NOT_DISPLAYED 402 : ResultCode.OK; 403 sendTerminalResponse(cmdParams.mCmdDet, resultCode, false, 0, null); 404 break; 405 case DISPLAY_TEXT: 406 break; 407 case SET_UP_IDLE_MODE_TEXT: 408 resultCode = cmdParams.mLoadIconFailed ? ResultCode.PRFRMD_ICON_NOT_DISPLAYED 409 : ResultCode.OK; 410 sendTerminalResponse(cmdParams.mCmdDet,resultCode, false, 0, null); 411 break; 412 case SET_UP_EVENT_LIST: 413 if (isSupportedSetupEventCommand(cmdMsg)) { 414 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); 415 } else { 416 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.BEYOND_TERMINAL_CAPABILITY, 417 false, 0, null); 418 } 419 break; 420 case PROVIDE_LOCAL_INFORMATION: 421 ResponseData resp; 422 switch (cmdParams.mCmdDet.commandQualifier) { 423 case CommandParamsFactory.DTTZ_SETTING: 424 resp = new DTTZResponseData(null); 425 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, resp); 426 break; 427 case CommandParamsFactory.LANGUAGE_SETTING: 428 resp = new LanguageResponseData(Locale.getDefault().getLanguage()); 429 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, resp); 430 break; 431 default: 432 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); 433 } 434 // No need to start STK app here. 435 return; 436 case LAUNCH_BROWSER: 437 if ((((LaunchBrowserParams) cmdParams).mConfirmMsg.text != null) 438 && (((LaunchBrowserParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) { 439 message = mContext.getText(com.android.internal.R.string.launchBrowserDefault); 440 ((LaunchBrowserParams) cmdParams).mConfirmMsg.text = message.toString(); 441 } 442 break; 443 case SELECT_ITEM: 444 case GET_INPUT: 445 case GET_INKEY: 446 break; 447 case REFRESH: 448 case RUN_AT: 449 if (STK_DEFAULT.equals(((DisplayTextParams)cmdParams).mTextMsg.text)) { 450 // Remove the default text which was temporarily added and shall not be shown 451 ((DisplayTextParams)cmdParams).mTextMsg.text = null; 452 } 453 break; 454 case SEND_DTMF: 455 case SEND_SMS: 456 case SEND_SS: 457 case SEND_USSD: 458 if ((((DisplayTextParams)cmdParams).mTextMsg.text != null) 459 && (((DisplayTextParams)cmdParams).mTextMsg.text.equals(STK_DEFAULT))) { 460 message = mContext.getText(com.android.internal.R.string.sending); 461 ((DisplayTextParams)cmdParams).mTextMsg.text = message.toString(); 462 } 463 break; 464 case PLAY_TONE: 465 break; 466 case SET_UP_CALL: 467 if ((((CallSetupParams) cmdParams).mConfirmMsg.text != null) 468 && (((CallSetupParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) { 469 message = mContext.getText(com.android.internal.R.string.SetupCallDefault); 470 ((CallSetupParams) cmdParams).mConfirmMsg.text = message.toString(); 471 } 472 break; 473 case LANGUAGE_NOTIFICATION: 474 String language = ((LanguageParams) cmdParams).mLanguage; 475 ResultCode result = ResultCode.OK; 476 if (language != null && language.length() > 0) { 477 try { 478 changeLanguage(language); 479 } catch (RemoteException e) { 480 result = ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS; 481 } 482 } 483 sendTerminalResponse(cmdParams.mCmdDet, result, false, 0, null); 484 return; 485 case OPEN_CHANNEL: 486 case CLOSE_CHANNEL: 487 case RECEIVE_DATA: 488 case SEND_DATA: 489 BIPClientParams cmd = (BIPClientParams) cmdParams; 490 /* Per 3GPP specification 102.223, 491 * if the alpha identifier is not provided by the UICC, 492 * the terminal MAY give information to the user 493 * noAlphaUsrCnf defines if you need to show user confirmation or not 494 */ 495 boolean noAlphaUsrCnf = false; 496 try { 497 noAlphaUsrCnf = mContext.getResources().getBoolean( 498 com.android.internal.R.bool.config_stkNoAlphaUsrCnf); 499 } catch (NotFoundException e) { 500 noAlphaUsrCnf = false; 501 } 502 if ((cmd.mTextMsg.text == null) && (cmd.mHasAlphaId || noAlphaUsrCnf)) { 503 CatLog.d(this, "cmd " + cmdParams.getCommandType() + " with null alpha id"); 504 // If alpha length is zero, we just respond with OK. 505 if (isProactiveCmd) { 506 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); 507 } else if (cmdParams.getCommandType() == CommandType.OPEN_CHANNEL) { 508 mCmdIf.handleCallSetupRequestFromSim(true, null); 509 } 510 return; 511 } 512 // Respond with permanent failure to avoid retry if STK app is not present. 513 if (!mStkAppInstalled) { 514 CatLog.d(this, "No STK application found."); 515 if (isProactiveCmd) { 516 sendTerminalResponse(cmdParams.mCmdDet, 517 ResultCode.BEYOND_TERMINAL_CAPABILITY, 518 false, 0, null); 519 return; 520 } 521 } 522 /* 523 * CLOSE_CHANNEL, RECEIVE_DATA and SEND_DATA can be delivered by 524 * either PROACTIVE_COMMAND or EVENT_NOTIFY. 525 * If PROACTIVE_COMMAND is used for those commands, send terminal 526 * response here. 527 */ 528 if (isProactiveCmd && 529 ((cmdParams.getCommandType() == CommandType.CLOSE_CHANNEL) || 530 (cmdParams.getCommandType() == CommandType.RECEIVE_DATA) || 531 (cmdParams.getCommandType() == CommandType.SEND_DATA))) { 532 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); 533 } 534 break; 535 default: 536 CatLog.d(this, "Unsupported command"); 537 return; 538 } 539 mCurrntCmd = cmdMsg; 540 broadcastCatCmdIntent(cmdMsg); 541 } 542 543 broadcastCatCmdIntent(CatCmdMessage cmdMsg)544 private void broadcastCatCmdIntent(CatCmdMessage cmdMsg) { 545 Intent intent = new Intent(AppInterface.CAT_CMD_ACTION); 546 intent.putExtra("STK CMD", cmdMsg); 547 intent.putExtra("SLOT_ID", mSlotId); 548 intent.setComponent(AppInterface.getDefaultSTKApplication()); 549 CatLog.d(this, "Sending CmdMsg: " + cmdMsg+ " on slotid:" + mSlotId); 550 mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); 551 } 552 553 /** 554 * Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL. 555 * 556 */ handleSessionEnd()557 private void handleSessionEnd() { 558 CatLog.d(this, "SESSION END on "+ mSlotId); 559 560 mCurrntCmd = mMenuCmd; 561 Intent intent = new Intent(AppInterface.CAT_SESSION_END_ACTION); 562 intent.putExtra("SLOT_ID", mSlotId); 563 intent.setComponent(AppInterface.getDefaultSTKApplication()); 564 mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); 565 } 566 567 568 @UnsupportedAppUsage sendTerminalResponse(CommandDetails cmdDet, ResultCode resultCode, boolean includeAdditionalInfo, int additionalInfo, ResponseData resp)569 private void sendTerminalResponse(CommandDetails cmdDet, 570 ResultCode resultCode, boolean includeAdditionalInfo, 571 int additionalInfo, ResponseData resp) { 572 573 if (cmdDet == null) { 574 return; 575 } 576 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 577 578 Input cmdInput = null; 579 if (mCurrntCmd != null) { 580 cmdInput = mCurrntCmd.geInput(); 581 } 582 583 // command details 584 int tag = ComprehensionTlvTag.COMMAND_DETAILS.value(); 585 if (cmdDet.compRequired) { 586 tag |= 0x80; 587 } 588 buf.write(tag); 589 buf.write(0x03); // length 590 buf.write(cmdDet.commandNumber); 591 buf.write(cmdDet.typeOfCommand); 592 buf.write(cmdDet.commandQualifier); 593 594 // device identities 595 // According to TS102.223/TS31.111 section 6.8 Structure of 596 // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N, 597 // the ME should set the CR(comprehension required) flag to 598 // comprehension not required.(CR=0)" 599 // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N, 600 // the CR flag is not set. 601 tag = ComprehensionTlvTag.DEVICE_IDENTITIES.value(); 602 buf.write(tag); 603 buf.write(0x02); // length 604 buf.write(DEV_ID_TERMINAL); // source device id 605 buf.write(DEV_ID_UICC); // destination device id 606 607 // result 608 tag = ComprehensionTlvTag.RESULT.value(); 609 if (cmdDet.compRequired) { 610 tag |= 0x80; 611 } 612 buf.write(tag); 613 int length = includeAdditionalInfo ? 2 : 1; 614 buf.write(length); 615 buf.write(resultCode.value()); 616 617 // additional info 618 if (includeAdditionalInfo) { 619 buf.write(additionalInfo); 620 } 621 622 // Fill optional data for each corresponding command 623 if (resp != null) { 624 resp.format(buf); 625 } else { 626 encodeOptionalTags(cmdDet, resultCode, cmdInput, buf); 627 } 628 629 byte[] rawData = buf.toByteArray(); 630 String hexString = IccUtils.bytesToHexString(rawData); 631 if (DBG) { 632 CatLog.d(this, "TERMINAL RESPONSE: " + hexString); 633 } 634 635 mCmdIf.sendTerminalResponse(hexString, null); 636 } 637 encodeOptionalTags(CommandDetails cmdDet, ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf)638 private void encodeOptionalTags(CommandDetails cmdDet, 639 ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf) { 640 CommandType cmdType = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand); 641 if (cmdType != null) { 642 switch (cmdType) { 643 case GET_INPUT: 644 case GET_INKEY: 645 // Please refer to the clause 6.8.21 of ETSI 102.223. 646 // The terminal shall supply the command execution duration 647 // when it issues TERMINAL RESPONSE for GET INKEY command with variable timeout. 648 // GET INPUT command should also be handled in the same manner. 649 if ((resultCode.value() == ResultCode.NO_RESPONSE_FROM_USER.value()) && 650 (cmdInput != null) && (cmdInput.duration != null)) { 651 getInKeyResponse(buf, cmdInput); 652 } 653 break; 654 case PROVIDE_LOCAL_INFORMATION: 655 if ((cmdDet.commandQualifier == CommandParamsFactory.LANGUAGE_SETTING) && 656 (resultCode.value() == ResultCode.OK.value())) { 657 getPliResponse(buf); 658 } 659 break; 660 default: 661 CatLog.d(this, "encodeOptionalTags() Unsupported Cmd details=" + cmdDet); 662 break; 663 } 664 } else { 665 CatLog.d(this, "encodeOptionalTags() bad Cmd details=" + cmdDet); 666 } 667 } 668 getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput)669 private void getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput) { 670 int tag = ComprehensionTlvTag.DURATION.value(); 671 672 buf.write(tag); 673 buf.write(0x02); // length 674 buf.write(cmdInput.duration.timeUnit.SECOND.value()); // Time (Unit,Seconds) 675 buf.write(cmdInput.duration.timeInterval); // Time Duration 676 } 677 getPliResponse(ByteArrayOutputStream buf)678 private void getPliResponse(ByteArrayOutputStream buf) { 679 // Locale Language Setting 680 final String lang = Locale.getDefault().getLanguage(); 681 682 if (lang != null) { 683 // tag 684 int tag = ComprehensionTlvTag.LANGUAGE.value(); 685 buf.write(tag); 686 ResponseData.writeLength(buf, lang.length()); 687 buf.write(lang.getBytes(), 0, lang.length()); 688 } 689 } 690 sendMenuSelection(int menuId, boolean helpRequired)691 private void sendMenuSelection(int menuId, boolean helpRequired) { 692 693 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 694 695 // tag 696 int tag = BerTlv.BER_MENU_SELECTION_TAG; 697 buf.write(tag); 698 699 // length 700 buf.write(0x00); // place holder 701 702 // device identities 703 tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value(); 704 buf.write(tag); 705 buf.write(0x02); // length 706 buf.write(DEV_ID_KEYPAD); // source device id 707 buf.write(DEV_ID_UICC); // destination device id 708 709 // item identifier 710 tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value(); 711 buf.write(tag); 712 buf.write(0x01); // length 713 buf.write(menuId); // menu identifier chosen 714 715 // help request 716 if (helpRequired) { 717 tag = ComprehensionTlvTag.HELP_REQUEST.value(); 718 buf.write(tag); 719 buf.write(0x00); // length 720 } 721 722 byte[] rawData = buf.toByteArray(); 723 724 // write real length 725 int len = rawData.length - 2; // minus (tag + length) 726 rawData[1] = (byte) len; 727 728 String hexString = IccUtils.bytesToHexString(rawData); 729 730 mCmdIf.sendEnvelope(hexString, null); 731 } 732 eventDownload(int event, int sourceId, int destinationId, byte[] additionalInfo, boolean oneShot)733 private void eventDownload(int event, int sourceId, int destinationId, 734 byte[] additionalInfo, boolean oneShot) { 735 736 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 737 738 // tag 739 int tag = BerTlv.BER_EVENT_DOWNLOAD_TAG; 740 buf.write(tag); 741 742 // length 743 buf.write(0x00); // place holder, assume length < 128. 744 745 // event list 746 tag = 0x80 | ComprehensionTlvTag.EVENT_LIST.value(); 747 buf.write(tag); 748 buf.write(0x01); // length 749 buf.write(event); // event value 750 751 // device identities 752 tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value(); 753 buf.write(tag); 754 buf.write(0x02); // length 755 buf.write(sourceId); // source device id 756 buf.write(destinationId); // destination device id 757 758 /* 759 * Check for type of event download to be sent to UICC - Browser 760 * termination,Idle screen available, User activity, Language selection 761 * etc as mentioned under ETSI TS 102 223 section 7.5 762 */ 763 764 /* 765 * Currently the below events are supported: 766 * Language Selection Event. 767 * Other event download commands should be encoded similar way 768 */ 769 /* TODO: eventDownload should be extended for other Envelope Commands */ 770 switch (event) { 771 case IDLE_SCREEN_AVAILABLE_EVENT: 772 CatLog.d(sInstance, " Sending Idle Screen Available event download to ICC"); 773 break; 774 case LANGUAGE_SELECTION_EVENT: 775 CatLog.d(sInstance, " Sending Language Selection event download to ICC"); 776 tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value(); 777 buf.write(tag); 778 // Language length should be 2 byte 779 buf.write(0x02); 780 break; 781 case USER_ACTIVITY_EVENT: 782 break; 783 default: 784 break; 785 } 786 787 // additional information 788 if (additionalInfo != null) { 789 for (byte b : additionalInfo) { 790 buf.write(b); 791 } 792 } 793 794 byte[] rawData = buf.toByteArray(); 795 796 // write real length 797 int len = rawData.length - 2; // minus (tag + length) 798 rawData[1] = (byte) len; 799 800 String hexString = IccUtils.bytesToHexString(rawData); 801 802 CatLog.d(this, "ENVELOPE COMMAND: " + hexString); 803 804 mCmdIf.sendEnvelope(hexString, null); 805 } 806 807 /** 808 * Used by application to get an AppInterface object. 809 * 810 * @return The only Service object in the system 811 */ 812 //TODO Need to take care for MSIM getInstance()813 public static AppInterface getInstance() { 814 int slotId = PhoneConstants.DEFAULT_CARD_INDEX; 815 SubscriptionController sControl = SubscriptionController.getInstance(); 816 if (sControl != null) { 817 slotId = sControl.getSlotIndex(sControl.getDefaultSubId()); 818 } 819 return getInstance(null, null, null, slotId); 820 } 821 822 /** 823 * Used by application to get an AppInterface object. 824 * 825 * @return The only Service object in the system 826 */ getInstance(int slotId)827 public static AppInterface getInstance(int slotId) { 828 return getInstance(null, null, null, slotId); 829 } 830 831 @Override handleMessage(Message msg)832 public void handleMessage(Message msg) { 833 CatLog.d(this, "handleMessage[" + msg.what + "]"); 834 835 switch (msg.what) { 836 case MSG_ID_SESSION_END: 837 case MSG_ID_PROACTIVE_COMMAND: 838 case MSG_ID_EVENT_NOTIFY: 839 case MSG_ID_REFRESH: 840 CatLog.d(this, "ril message arrived,slotid:" + mSlotId); 841 String data = null; 842 if (msg.obj != null) { 843 AsyncResult ar = (AsyncResult) msg.obj; 844 if (ar != null && ar.result != null) { 845 try { 846 data = (String) ar.result; 847 } catch (ClassCastException e) { 848 break; 849 } 850 } 851 } 852 mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data)); 853 break; 854 case MSG_ID_CALL_SETUP: 855 mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null)); 856 break; 857 case MSG_ID_ICC_RECORDS_LOADED: 858 break; 859 case MSG_ID_RIL_MSG_DECODED: 860 handleRilMsg((RilMessage) msg.obj); 861 break; 862 case MSG_ID_RESPONSE: 863 handleCmdResponse((CatResponseMessage) msg.obj); 864 break; 865 case MSG_ID_ICC_CHANGED: 866 CatLog.d(this, "MSG_ID_ICC_CHANGED"); 867 updateIccAvailability(); 868 break; 869 case MSG_ID_ICC_REFRESH: 870 if (msg.obj != null) { 871 AsyncResult ar = (AsyncResult) msg.obj; 872 if (ar != null && ar.result != null) { 873 broadcastCardStateAndIccRefreshResp(CardState.CARDSTATE_PRESENT, 874 (IccRefreshResponse) ar.result); 875 } else { 876 CatLog.d(this,"Icc REFRESH with exception: " + ar.exception); 877 } 878 } else { 879 CatLog.d(this, "IccRefresh Message is null"); 880 } 881 break; 882 case MSG_ID_ALPHA_NOTIFY: 883 CatLog.d(this, "Received CAT CC Alpha message from card"); 884 if (msg.obj != null) { 885 AsyncResult ar = (AsyncResult) msg.obj; 886 if (ar != null && ar.result != null) { 887 broadcastAlphaMessage((String)ar.result); 888 } else { 889 CatLog.d(this, "CAT Alpha message: ar.result is null"); 890 } 891 } else { 892 CatLog.d(this, "CAT Alpha message: msg.obj is null"); 893 } 894 break; 895 default: 896 throw new AssertionError("Unrecognized CAT command: " + msg.what); 897 } 898 } 899 900 /** 901 ** This function sends a CARD status (ABSENT, PRESENT, REFRESH) to STK_APP. 902 ** This is triggered during ICC_REFRESH or CARD STATE changes. In case 903 ** REFRESH, additional information is sent in 'refresh_result' 904 ** 905 **/ broadcastCardStateAndIccRefreshResp(CardState cardState, IccRefreshResponse iccRefreshState)906 private void broadcastCardStateAndIccRefreshResp(CardState cardState, 907 IccRefreshResponse iccRefreshState) { 908 Intent intent = new Intent(AppInterface.CAT_ICC_STATUS_CHANGE); 909 boolean cardPresent = (cardState == CardState.CARDSTATE_PRESENT); 910 911 if (iccRefreshState != null) { 912 //This case is when MSG_ID_ICC_REFRESH is received. 913 intent.putExtra(AppInterface.REFRESH_RESULT, iccRefreshState.refreshResult); 914 CatLog.d(this, "Sending IccResult with Result: " 915 + iccRefreshState.refreshResult); 916 } 917 918 // This sends an intent with CARD_ABSENT (0 - false) /CARD_PRESENT (1 - true). 919 intent.putExtra(AppInterface.CARD_STATUS, cardPresent); 920 intent.setComponent(AppInterface.getDefaultSTKApplication()); 921 intent.putExtra("SLOT_ID", mSlotId); 922 CatLog.d(this, "Sending Card Status: " 923 + cardState + " " + "cardPresent: " + cardPresent + "SLOT_ID: " + mSlotId); 924 mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); 925 } 926 broadcastAlphaMessage(String alphaString)927 private void broadcastAlphaMessage(String alphaString) { 928 CatLog.d(this, "Broadcasting CAT Alpha message from card: " + alphaString); 929 Intent intent = new Intent(AppInterface.CAT_ALPHA_NOTIFY_ACTION); 930 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 931 intent.putExtra(AppInterface.ALPHA_STRING, alphaString); 932 intent.putExtra("SLOT_ID", mSlotId); 933 intent.setComponent(AppInterface.getDefaultSTKApplication()); 934 mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); 935 } 936 937 @Override onCmdResponse(CatResponseMessage resMsg)938 public synchronized void onCmdResponse(CatResponseMessage resMsg) { 939 if (resMsg == null) { 940 return; 941 } 942 // queue a response message. 943 Message msg = obtainMessage(MSG_ID_RESPONSE, resMsg); 944 msg.sendToTarget(); 945 } 946 validateResponse(CatResponseMessage resMsg)947 private boolean validateResponse(CatResponseMessage resMsg) { 948 boolean validResponse = false; 949 if ((resMsg.mCmdDet.typeOfCommand == CommandType.SET_UP_EVENT_LIST.value()) 950 || (resMsg.mCmdDet.typeOfCommand == CommandType.SET_UP_MENU.value())) { 951 CatLog.d(this, "CmdType: " + resMsg.mCmdDet.typeOfCommand); 952 validResponse = true; 953 } else if (mCurrntCmd != null) { 954 validResponse = resMsg.mCmdDet.compareTo(mCurrntCmd.mCmdDet); 955 CatLog.d(this, "isResponse for last valid cmd: " + validResponse); 956 } 957 return validResponse; 958 } 959 removeMenu(Menu menu)960 private boolean removeMenu(Menu menu) { 961 try { 962 if (menu.items.size() == 1 && menu.items.get(0) == null) { 963 return true; 964 } 965 } catch (NullPointerException e) { 966 CatLog.d(this, "Unable to get Menu's items size"); 967 return true; 968 } 969 return false; 970 } 971 handleCmdResponse(CatResponseMessage resMsg)972 private void handleCmdResponse(CatResponseMessage resMsg) { 973 // Make sure the response details match the last valid command. An invalid 974 // response is a one that doesn't have a corresponding proactive command 975 // and sending it can "confuse" the baseband/ril. 976 // One reason for out of order responses can be UI glitches. For example, 977 // if the application launch an activity, and that activity is stored 978 // by the framework inside the history stack. That activity will be 979 // available for relaunch using the latest application dialog 980 // (long press on the home button). Relaunching that activity can send 981 // the same command's result again to the CatService and can cause it to 982 // get out of sync with the SIM. This can happen in case of 983 // non-interactive type Setup Event List and SETUP_MENU proactive commands. 984 // Stk framework would have already sent Terminal Response to Setup Event 985 // List and SETUP_MENU proactive commands. After sometime Stk app will send 986 // Envelope Command/Event Download. In which case, the response details doesn't 987 // match with last valid command (which are not related). 988 // However, we should allow Stk framework to send the message to ICC. 989 if (!validateResponse(resMsg)) { 990 return; 991 } 992 ResponseData resp = null; 993 boolean helpRequired = false; 994 CommandDetails cmdDet = resMsg.getCmdDetails(); 995 AppInterface.CommandType type = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand); 996 997 switch (resMsg.mResCode) { 998 case HELP_INFO_REQUIRED: 999 helpRequired = true; 1000 // fall through 1001 case OK: 1002 case PRFRMD_WITH_PARTIAL_COMPREHENSION: 1003 case PRFRMD_WITH_MISSING_INFO: 1004 case PRFRMD_WITH_ADDITIONAL_EFS_READ: 1005 case PRFRMD_ICON_NOT_DISPLAYED: 1006 case PRFRMD_MODIFIED_BY_NAA: 1007 case PRFRMD_LIMITED_SERVICE: 1008 case PRFRMD_WITH_MODIFICATION: 1009 case PRFRMD_NAA_NOT_ACTIVE: 1010 case PRFRMD_TONE_NOT_PLAYED: 1011 case LAUNCH_BROWSER_ERROR: 1012 case TERMINAL_CRNTLY_UNABLE_TO_PROCESS: 1013 switch (type) { 1014 case SET_UP_MENU: 1015 helpRequired = resMsg.mResCode == ResultCode.HELP_INFO_REQUIRED; 1016 sendMenuSelection(resMsg.mUsersMenuSelection, helpRequired); 1017 return; 1018 case SELECT_ITEM: 1019 resp = new SelectItemResponseData(resMsg.mUsersMenuSelection); 1020 break; 1021 case GET_INPUT: 1022 case GET_INKEY: 1023 Input input = mCurrntCmd.geInput(); 1024 if (!input.yesNo) { 1025 // when help is requested there is no need to send the text 1026 // string object. 1027 if (!helpRequired) { 1028 resp = new GetInkeyInputResponseData(resMsg.mUsersInput, 1029 input.ucs2, input.packed); 1030 } 1031 } else { 1032 resp = new GetInkeyInputResponseData( 1033 resMsg.mUsersYesNoSelection); 1034 } 1035 break; 1036 case DISPLAY_TEXT: 1037 if (resMsg.mResCode == ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS) { 1038 // For screenbusy case there will be addtional information in the terminal 1039 // response. And the value of the additional information byte is 0x01. 1040 resMsg.setAdditionalInfo(0x01); 1041 } else { 1042 resMsg.mIncludeAdditionalInfo = false; 1043 resMsg.mAdditionalInfo = 0; 1044 } 1045 break; 1046 case LAUNCH_BROWSER: 1047 if (resMsg.mResCode == ResultCode.LAUNCH_BROWSER_ERROR) { 1048 // Additional info for Default URL unavailable. 1049 resMsg.setAdditionalInfo(0x04); 1050 } else { 1051 resMsg.mIncludeAdditionalInfo = false; 1052 resMsg.mAdditionalInfo = 0; 1053 } 1054 break; 1055 // 3GPP TS.102.223: Open Channel alpha confirmation should not send TR 1056 case OPEN_CHANNEL: 1057 case SET_UP_CALL: 1058 mCmdIf.handleCallSetupRequestFromSim(resMsg.mUsersConfirm, null); 1059 // No need to send terminal response for SET UP CALL. The user's 1060 // confirmation result is send back using a dedicated ril message 1061 // invoked by the CommandInterface call above. 1062 mCurrntCmd = null; 1063 return; 1064 case SET_UP_EVENT_LIST: 1065 if (IDLE_SCREEN_AVAILABLE_EVENT == resMsg.mEventValue) { 1066 eventDownload(resMsg.mEventValue, DEV_ID_DISPLAY, DEV_ID_UICC, 1067 resMsg.mAddedInfo, false); 1068 } else { 1069 eventDownload(resMsg.mEventValue, DEV_ID_TERMINAL, DEV_ID_UICC, 1070 resMsg.mAddedInfo, false); 1071 } 1072 // No need to send the terminal response after event download. 1073 return; 1074 default: 1075 break; 1076 } 1077 break; 1078 case BACKWARD_MOVE_BY_USER: 1079 case USER_NOT_ACCEPT: 1080 // if the user dismissed the alert dialog for a 1081 // setup call/open channel, consider that as the user 1082 // rejecting the call. Use dedicated API for this, rather than 1083 // sending a terminal response. 1084 if (type == CommandType.SET_UP_CALL || type == CommandType.OPEN_CHANNEL) { 1085 mCmdIf.handleCallSetupRequestFromSim(false, null); 1086 mCurrntCmd = null; 1087 return; 1088 } else { 1089 resp = null; 1090 } 1091 break; 1092 case NO_RESPONSE_FROM_USER: 1093 // No need to send terminal response for SET UP CALL on user timeout, 1094 // instead use dedicated API 1095 if (type == CommandType.SET_UP_CALL) { 1096 mCmdIf.handleCallSetupRequestFromSim(false, null); 1097 mCurrntCmd = null; 1098 return; 1099 } 1100 case UICC_SESSION_TERM_BY_USER: 1101 resp = null; 1102 break; 1103 default: 1104 return; 1105 } 1106 sendTerminalResponse(cmdDet, resMsg.mResCode, resMsg.mIncludeAdditionalInfo, 1107 resMsg.mAdditionalInfo, resp); 1108 mCurrntCmd = null; 1109 } 1110 1111 @UnsupportedAppUsage isStkAppInstalled()1112 private boolean isStkAppInstalled() { 1113 Intent intent = new Intent(AppInterface.CAT_CMD_ACTION); 1114 PackageManager pm = mContext.getPackageManager(); 1115 List<ResolveInfo> broadcastReceivers = 1116 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); 1117 int numReceiver = broadcastReceivers == null ? 0 : broadcastReceivers.size(); 1118 1119 return (numReceiver > 0); 1120 } 1121 update(CommandsInterface ci, Context context, UiccProfile uiccProfile)1122 public void update(CommandsInterface ci, 1123 Context context, UiccProfile uiccProfile) { 1124 UiccCardApplication ca = null; 1125 IccRecords ir = null; 1126 1127 if (uiccProfile != null) { 1128 /* Since Cat is not tied to any application, but rather is Uicc application 1129 * in itself - just get first FileHandler and IccRecords object 1130 */ 1131 ca = uiccProfile.getApplicationIndex(0); 1132 if (ca != null) { 1133 ir = ca.getIccRecords(); 1134 } 1135 } 1136 1137 synchronized (sInstanceLock) { 1138 if ((ir != null) && (mIccRecords != ir)) { 1139 if (mIccRecords != null) { 1140 mIccRecords.unregisterForRecordsLoaded(this); 1141 } 1142 1143 CatLog.d(this, 1144 "Reinitialize the Service with SIMRecords and UiccCardApplication"); 1145 mIccRecords = ir; 1146 mUiccApplication = ca; 1147 1148 // re-Register for SIM ready event. 1149 mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null); 1150 CatLog.d(this, "registerForRecordsLoaded slotid=" + mSlotId + " instance:" + this); 1151 } 1152 } 1153 } 1154 updateIccAvailability()1155 void updateIccAvailability() { 1156 if (null == mUiccController) { 1157 return; 1158 } 1159 1160 CardState newState = CardState.CARDSTATE_ABSENT; 1161 UiccCard newCard = mUiccController.getUiccCard(mSlotId); 1162 if (newCard != null) { 1163 newState = newCard.getCardState(); 1164 } 1165 CardState oldState = mCardState; 1166 mCardState = newState; 1167 CatLog.d(this,"New Card State = " + newState + " " + "Old Card State = " + oldState); 1168 if (oldState == CardState.CARDSTATE_PRESENT && 1169 newState != CardState.CARDSTATE_PRESENT) { 1170 broadcastCardStateAndIccRefreshResp(newState, null); 1171 } else if (oldState != CardState.CARDSTATE_PRESENT && 1172 newState == CardState.CARDSTATE_PRESENT) { 1173 // Card moved to PRESENT STATE. 1174 mCmdIf.reportStkServiceIsRunning(null); 1175 } 1176 } 1177 changeLanguage(String language)1178 private void changeLanguage(String language) throws RemoteException { 1179 IActivityManager am = ActivityManagerNative.getDefault(); 1180 Configuration config = am.getConfiguration(); 1181 config.setLocales(new LocaleList(new Locale(language), LocaleList.getDefault())); 1182 config.userSetLocale = true; 1183 am.updatePersistentConfiguration(config); 1184 BackupManager.dataChanged("com.android.providers.settings"); 1185 } 1186 } 1187