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