| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.phone.otasp; |
| |
| import static com.android.phone.PhoneGlobals.getPhone; |
| |
| import android.app.Service; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.AsyncResult; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.telephony.ServiceState; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| |
| import com.android.internal.telephony.GsmCdmaConnection; |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.PhoneConstants; |
| import com.android.internal.telephony.ServiceStateTracker; |
| import com.android.phone.PhoneGlobals; |
| import com.android.phone.PhoneUtils; |
| |
| /** |
| * otasp activation service handles all logic related with OTASP call. |
| * OTASP is a CDMA-specific feature: OTA or OTASP == Over The Air service provisioning |
| * In practice, in a normal successful OTASP call, events come in as follows: |
| * - SPL_UNLOCKED within a couple of seconds after the call starts |
| * - PRL_DOWNLOADED and MDN_DOWNLOADED and COMMITTED within a span of 2 seconds |
| * - poll cdma subscription from RIL after COMMITTED |
| * - SIM reloading with provisioned MDN and MIN |
| */ |
| public class OtaspActivationService extends Service { |
| private static final String TAG = OtaspActivationService.class.getSimpleName(); |
| private static final boolean DBG = true; |
| /** |
| * non-interactive otasp number |
| */ |
| private static final String OTASP_NUMBER = GsmCdmaConnection.OTASP_NUMBER; |
| |
| /** |
| * Otasp call follows with SIM reloading which might triggers a retry loop on activation |
| * failure. A max retry limit could help prevent retry loop. |
| */ |
| private static final int OTASP_CALL_RETRIES_MAX = 3; |
| private static final int OTASP_CALL_RETRY_PERIOD_IN_MS = 3000; |
| private static int sOtaspCallRetries = 0; |
| |
| /* events */ |
| private static final int EVENT_CALL_STATE_CHANGED = 0; |
| private static final int EVENT_CDMA_OTASP_CALL_RETRY = 1; |
| private static final int EVENT_CDMA_PROVISION_STATUS_UPDATE = 2; |
| private static final int EVENT_SERVICE_STATE_CHANGED = 3; |
| private static final int EVENT_START_OTASP_CALL = 4; |
| |
| /* use iccid to detect hot sim swap */ |
| private static String sIccId = null; |
| |
| private Phone mPhone; |
| /* committed flag indicates Otasp call succeed */ |
| private boolean mIsOtaspCallCommitted = false; |
| |
| @Override |
| public void onCreate() { |
| logd("otasp service onCreate"); |
| mPhone = PhoneGlobals.getPhone(); |
| ServiceStateTracker sst = mPhone.getServiceStateTracker(); |
| if (sst != null && sst.getOtasp() != TelephonyManager.OTASP_NEEDED) { |
| logd("OTASP is not needed."); |
| return; |
| } |
| if ((sIccId == null) || !sIccId.equals(mPhone.getIccSerialNumber())) { |
| // reset to allow activation retry on new sim |
| sIccId = mPhone.getIccSerialNumber(); |
| sOtaspCallRetries = 0; |
| } |
| sOtaspCallRetries++; |
| logd("OTASP call tried " + sOtaspCallRetries + " times"); |
| if (sOtaspCallRetries > OTASP_CALL_RETRIES_MAX) { |
| logd("OTASP call exceeds max retries => activation failed"); |
| updateActivationState(this, false); |
| onComplete(); |
| return; |
| } |
| mHandler.sendEmptyMessage(EVENT_START_OTASP_CALL); |
| } |
| |
| @Override |
| public int onStartCommand(Intent intent, int flags, int startId) { |
| return START_REDELIVER_INTENT; |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| return null; |
| } |
| |
| private Handler mHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case EVENT_SERVICE_STATE_CHANGED: |
| logd("EVENT_SERVICE_STATE_CHANGED"); |
| onStartOtaspCall(); |
| break; |
| case EVENT_START_OTASP_CALL: |
| logd("EVENT_START_OTASP_CALL"); |
| onStartOtaspCall(); |
| break; |
| case EVENT_CALL_STATE_CHANGED: |
| logd("OTASP_CALL_STATE_CHANGED"); |
| onOtaspCallStateChanged(); |
| break; |
| case EVENT_CDMA_PROVISION_STATUS_UPDATE: |
| logd("OTASP_ACTIVATION_STATUS_UPDATE_EVENT"); |
| onCdmaProvisionStatusUpdate((AsyncResult) msg.obj); |
| break; |
| case EVENT_CDMA_OTASP_CALL_RETRY: |
| logd("EVENT_CDMA_OTASP_CALL_RETRY"); |
| onStartOtaspCall(); |
| break; |
| default: |
| loge("invalid msg: " + msg.what + " not handled."); |
| } |
| } |
| }; |
| |
| /** |
| * Starts the OTASP call without any UI. |
| * platform only support background non-interactive otasp call, but users could still dial |
| * interactive OTASP number through dialer if carrier allows (some carrier will |
| * explicitly block any outgoing *288XX number). |
| */ |
| private void onStartOtaspCall() { |
| unregisterAll(); |
| if (mPhone.getServiceState().getState() != ServiceState.STATE_IN_SERVICE) { |
| loge("OTASP call failure, wait for network available."); |
| mPhone.registerForServiceStateChanged(mHandler, EVENT_SERVICE_STATE_CHANGED, null); |
| return; |
| } |
| // otasp call follows with CDMA OTA PROVISION STATUS update which signals activation result |
| mPhone.registerForCdmaOtaStatusChange(mHandler, EVENT_CDMA_PROVISION_STATUS_UPDATE, null); |
| mPhone.registerForPreciseCallStateChanged(mHandler, EVENT_CALL_STATE_CHANGED, null); |
| logd("startNonInteractiveOtasp: placing call to '" + OTASP_NUMBER + "'..."); |
| int callStatus = PhoneUtils.placeOtaspCall(this, |
| getPhone(), |
| OTASP_NUMBER); |
| if (callStatus == PhoneUtils.CALL_STATUS_DIALED) { |
| if (DBG) logd(" ==> success return from placeCall(): callStatus = " + callStatus); |
| } else { |
| loge(" ==> failure return from placeCall(): callStatus = " + callStatus); |
| mHandler.sendEmptyMessageDelayed(EVENT_CDMA_OTASP_CALL_RETRY, |
| OTASP_CALL_RETRY_PERIOD_IN_MS); |
| } |
| } |
| |
| /** |
| * register for cdma ota provision status |
| * see RIL_CDMA_OTA_ProvisionStatus in include/telephony/ril.h |
| */ |
| private void onCdmaProvisionStatusUpdate(AsyncResult r) { |
| int[] otaStatus = (int[]) r.result; |
| logd("onCdmaProvisionStatusUpdate: " + otaStatus[0]); |
| if (Phone.CDMA_OTA_PROVISION_STATUS_COMMITTED == otaStatus[0]) { |
| mIsOtaspCallCommitted = true; |
| } |
| } |
| |
| /** |
| * update activation state upon call disconnected. |
| * check the mIsOtaspCallCommitted bit, and if that's true it means that activation |
| * was successful. |
| */ |
| private void onOtaspCallStateChanged() { |
| logd("onOtaspCallStateChanged: " + mPhone.getState()); |
| if (mPhone.getState().equals(PhoneConstants.State.IDLE)) { |
| if (mIsOtaspCallCommitted) { |
| logd("Otasp activation succeed"); |
| updateActivationState(this, true); |
| } else { |
| logd("Otasp activation failed"); |
| updateActivationState(this, false); |
| } |
| onComplete(); |
| } |
| } |
| |
| private void onComplete() { |
| logd("otasp service onComplete"); |
| unregisterAll(); |
| stopSelf(); |
| } |
| |
| private void unregisterAll() { |
| mPhone.unregisterForCdmaOtaStatusChange(mHandler); |
| mPhone.unregisterForSubscriptionInfoReady(mHandler); |
| mPhone.unregisterForServiceStateChanged(mHandler); |
| mPhone.unregisterForPreciseCallStateChanged(mHandler); |
| mHandler.removeCallbacksAndMessages(null); |
| } |
| |
| public static void updateActivationState(Context context, boolean success) { |
| final TelephonyManager mTelephonyMgr = TelephonyManager.from(context); |
| int state = (success) ? TelephonyManager.SIM_ACTIVATION_STATE_ACTIVATED : |
| TelephonyManager.SIM_ACTIVATION_STATE_DEACTIVATED; |
| int subId = SubscriptionManager.getDefaultSubscriptionId(); |
| mTelephonyMgr.setVoiceActivationState(subId, state); |
| mTelephonyMgr.setDataActivationState(subId, state); |
| } |
| |
| private static void logd(String s) { |
| android.util.Log.d(TAG, s); |
| } |
| |
| private static void loge(String s) { |
| android.util.Log.e(TAG, s); |
| } |
| } |