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