| /* |
| * Copyright (C) 2018 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.ans; |
| |
| import android.app.AlarmManager; |
| import android.content.Context; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.telephony.Rlog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * ANSServiceStateEvaluator class which will evaluate the data service state of a subId |
| * compared to another. |
| */ |
| public class ANSServiceStateEvaluator { |
| private Context mContext; |
| private final Object mLock = new Object(); |
| private int mOppDataSubId; |
| private int mPrimarySubId; |
| /* new opportunistic data service state */ |
| private int mOppDataNewState = ANSServiceStateMonitor.SERVICE_STATE_UNKNOWN; |
| private int mPrimaryNewState = ANSServiceStateMonitor.SERVICE_STATE_UNKNOWN; |
| private boolean mIsWaitingForTimeout = false; |
| |
| @VisibleForTesting |
| protected ANSServiceEvaluatorCallback mServiceEvaluatorCallback; |
| |
| @VisibleForTesting |
| protected ANSServiceStateMonitor mOppDataSubMonitor; |
| |
| @VisibleForTesting |
| protected ANSServiceStateMonitor mPrimarySubMonitor; |
| |
| @VisibleForTesting |
| protected AlarmManager mAlarmManager; |
| |
| private static final int WAIT_FOR_DATA_SERVICE_PERIOD = (int) TimeUnit.SECONDS.toMillis(10); |
| private static final String LOG_TAG = "ANSServiceStateEvaluator"; |
| private static final boolean DBG = true; |
| |
| /* message to indicate no data for WAIT_FOR_DATA_SERVICE_PERIOD */ |
| private static final int MSG_WAIT_FOR_DATA_SERVICE_TIMOUT = 1; |
| |
| /** |
| * call back to confirm service state evaluation |
| */ |
| public interface ANSServiceEvaluatorCallback { |
| |
| /** |
| * call back to confirm bad service |
| */ |
| void onBadDataService(); |
| } |
| |
| private Handler mHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_WAIT_FOR_DATA_SERVICE_TIMOUT: |
| mIsWaitingForTimeout = false; |
| logDebug("Msg received to get data"); |
| evaluateUpdatedState(); |
| break; |
| |
| default: |
| log("invalid message"); |
| break; |
| } |
| } |
| }; |
| |
| public final AlarmManager.OnAlarmListener mDataServiceWaitTimer = |
| (AlarmManager.OnAlarmListener) () -> { |
| logDebug("Alarm fired"); |
| mHandler.sendEmptyMessage(MSG_WAIT_FOR_DATA_SERVICE_TIMOUT); |
| }; |
| |
| /** |
| * set alarm to wait for data service |
| */ |
| private void setDataServiceWaitAlarm() { |
| mAlarmManager.set(AlarmManager.RTC, System.currentTimeMillis() |
| + WAIT_FOR_DATA_SERVICE_PERIOD, LOG_TAG, mDataServiceWaitTimer, null); |
| } |
| |
| /** |
| * stop the alarm |
| */ |
| private void stopDataServiceWaitAlarm() { |
| mAlarmManager.cancel(mDataServiceWaitTimer); |
| } |
| |
| private boolean evaluateIfBadServiceState() { |
| /* if we have not received update on both subId, we can not take decision, yes */ |
| log("evaluateIfBadServiceState: mPrimaryNewState: " |
| + ANSServiceStateMonitor.getStateString(mPrimaryNewState) + " mOppDataNewState: " |
| + ANSServiceStateMonitor.getStateString(mOppDataNewState)); |
| |
| if ((mPrimaryNewState == ANSServiceStateMonitor.SERVICE_STATE_UNKNOWN) |
| || (mOppDataNewState == ANSServiceStateMonitor.SERVICE_STATE_UNKNOWN)) { |
| return false; |
| } |
| |
| /* Evaluate if primary subscription has good service and if |
| opportunistic data subscription is not, if yes return true. |
| */ |
| switch (mPrimaryNewState) { |
| case ANSServiceStateMonitor.SERVICE_STATE_NO_SERVICE: |
| /* no need to make any change */ |
| return false; |
| case ANSServiceStateMonitor.SERVICE_STATE_BAD: |
| if (mOppDataNewState != ANSServiceStateMonitor.SERVICE_STATE_NO_SERVICE) { |
| return false; |
| } |
| |
| break; |
| case ANSServiceStateMonitor.SERVICE_STATE_GOOD: |
| if (mOppDataNewState == ANSServiceStateMonitor.SERVICE_STATE_GOOD) { |
| return false; |
| } |
| break; |
| default: |
| log("invalid state"); |
| break; |
| } |
| |
| return true; |
| } |
| |
| private void evaluateUpdatedState() { |
| logDebug("evaluateUpdatedState " + mIsWaitingForTimeout); |
| if (!mIsWaitingForTimeout && evaluateIfBadServiceState()) { |
| mServiceEvaluatorCallback.onBadDataService(); |
| } |
| } |
| |
| /* service monitor callback will get called for service state change on a particular subId. */ |
| @VisibleForTesting |
| protected ANSServiceStateMonitor.ANSServiceMonitorCallback mServiceMonitorCallback = |
| new ANSServiceStateMonitor.ANSServiceMonitorCallback() { |
| @Override |
| public void onServiceMonitorUpdate(int subId, int state) { |
| logDebug("onServiceMonitorUpdate subId: " + subId + " state: " |
| + ANSServiceStateMonitor.getStateString(state)); |
| synchronized (mLock) { |
| if (mServiceEvaluatorCallback == null) { |
| return; |
| } |
| |
| if (subId == mPrimarySubId) { |
| mPrimaryNewState = state; |
| } else if (subId == mOppDataSubId) { |
| mOppDataNewState = state; |
| } else { |
| logDebug("invalid sub id"); |
| } |
| |
| evaluateUpdatedState(); |
| } |
| } |
| }; |
| |
| public ANSServiceStateEvaluator(Context c, |
| ANSServiceEvaluatorCallback serviceEvaluatorCallback) { |
| init(c, serviceEvaluatorCallback); |
| } |
| |
| @VisibleForTesting |
| protected void init(Context c, ANSServiceEvaluatorCallback serviceEvaluatorCallback) { |
| mContext = c; |
| mServiceEvaluatorCallback = serviceEvaluatorCallback; |
| mOppDataSubMonitor = new ANSServiceStateMonitor(mContext, mServiceMonitorCallback); |
| mPrimarySubMonitor = new ANSServiceStateMonitor(mContext, mServiceMonitorCallback); |
| mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); |
| } |
| |
| /** |
| * start service state evaluation for dataSubId compared to voiceSubId. |
| * This API evaluates the service state conditions of dataSubId and decides whether |
| * data service is bad compared to voiceSubId |
| * @param dataSubId current data subscription id |
| * @param voiceSubId voice subscription id |
| */ |
| public void startEvaluation(int dataSubId, int voiceSubId) { |
| logDebug("Start evaluation"); |
| /* make sure to clean up if there is any evaluation going on. */ |
| stopEvaluation(); |
| setDataServiceWaitAlarm(); |
| synchronized (mLock) { |
| mIsWaitingForTimeout = true; |
| mOppDataSubId = dataSubId; |
| mPrimarySubId = voiceSubId; |
| mOppDataSubMonitor.startListeningForNetworkConditionChange(dataSubId); |
| mPrimarySubMonitor.startListeningForNetworkConditionChange(voiceSubId); |
| } |
| } |
| |
| /** |
| * stop service state evaluation |
| */ |
| public void stopEvaluation() { |
| logDebug("Stop evaluation"); |
| synchronized (mLock) { |
| mOppDataSubId = -1; |
| mPrimarySubId = -1; |
| if (mIsWaitingForTimeout) { |
| stopDataServiceWaitAlarm(); |
| } |
| mIsWaitingForTimeout = false; |
| mOppDataSubMonitor.stopListeningForNetworkConditionChange(); |
| mPrimarySubMonitor.stopListeningForNetworkConditionChange(); |
| mOppDataNewState = ANSServiceStateMonitor.SERVICE_STATE_UNKNOWN; |
| mPrimaryNewState = ANSServiceStateMonitor.SERVICE_STATE_UNKNOWN; |
| } |
| } |
| |
| |
| private void log(String msg) { |
| Rlog.d(LOG_TAG, msg); |
| } |
| |
| private void logDebug(String msg) { |
| if (DBG) { |
| Rlog.d(LOG_TAG, msg); |
| } |
| } |
| } |