diff options
Diffstat (limited to 'services/QualifiedNetworksService/src/com/android')
7 files changed, 556 insertions, 68 deletions
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java index f1b6e37..3b2b63f 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java @@ -16,6 +16,8 @@ package com.android.telephony.qns; +import static com.android.telephony.qns.QnsConstants.INVALID_ID; + import android.annotation.NonNull; import android.net.NetworkCapabilities; import android.os.Handler; @@ -50,6 +52,7 @@ public class QnsCallStatusTracker { private List<CallState> mCallStates = new ArrayList<>(); private QnsRegistrant mCallTypeChangedEventListener; private QnsRegistrant mEmergencyCallTypeChangedEventListener; + private final QnsTimer mQnsTimer; private int mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE; private int mLastEmergencyCallType = QnsConstants.CALL_TYPE_IDLE; private boolean mEmergencyOverIms; @@ -144,7 +147,6 @@ public class QnsCallStatusTracker { private static final int EVENT_PACKET_LOSS_TIMER_EXPIRED = 3402; private static final int EVENT_HYSTERESIS_FOR_NORMAL_QUALITY = 3403; private static final int EVENT_POLLING_CHECK_LOW_QUALITY = 3404; - private static final int EVENT_LOW_QUALITY_HANDLER_MAX = 3405; private static final int STATE_NORMAL_QUALITY = 0; private static final int STATE_SUSPECT_LOW_QUALITY = 1; @@ -156,6 +158,9 @@ public class QnsCallStatusTracker { private static final int LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE = -1; private int mState = STATE_NORMAL_QUALITY; + private int mPacketLossTimerId = INVALID_ID; + private int mHysteresisTimerId = INVALID_ID; + private int mPollingCheckTimerId = INVALID_ID; private MediaQualityStatus mMediaQualityStatus; private String mTag; @@ -204,12 +209,14 @@ public class QnsCallStatusTracker { return; } else { // check normal quality is stable or not. - this.sendEmptyMessageDelayed(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY, + mHysteresisTimerId = mQnsTimer.registerTimer( + Message.obtain(this, EVENT_HYSTERESIS_FOR_NORMAL_QUALITY), HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS); } } else { // Threshold breached. - this.removeMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY); + mQnsTimer.unregisterTimer(mHysteresisTimerId); + mHysteresisTimerId = INVALID_ID; switch (mState) { case STATE_NORMAL_QUALITY: case STATE_SUSPECT_LOW_QUALITY: @@ -223,7 +230,8 @@ public class QnsCallStatusTracker { needNotify = true; } } else { - removeMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED); + mQnsTimer.unregisterTimer(mPacketLossTimerId); + mPacketLossTimerId = INVALID_ID; enterLowQualityState(status); needNotify = true; } @@ -250,28 +258,30 @@ public class QnsCallStatusTracker { void enterLowQualityState(MediaQualityStatus status) { Log.d(mTag, "enterLowQualityState " + status); mState = STATE_LOW_QUALITY; - this.sendEmptyMessageDelayed( - EVENT_POLLING_CHECK_LOW_QUALITY, LOW_QUALITY_CHECK_INTERVAL_MILLIS); + mPollingCheckTimerId = mQnsTimer.registerTimer( + Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY), + LOW_QUALITY_CHECK_INTERVAL_MILLIS); } void enterSuspectLowQualityState(int delayMillis) { Log.d(mTag, "enterSuspectLowQualityState."); - if (!this.hasMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED)) { - this.removeMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED); - } + mQnsTimer.unregisterTimer(mPacketLossTimerId); Log.d(mTag, "Packet loss timer start. " + delayMillis); Message msg = this.obtainMessage( EVENT_PACKET_LOSS_TIMER_EXPIRED, mTransportType, 0); - this.sendMessageDelayed(msg, delayMillis); + mPacketLossTimerId = mQnsTimer.registerTimer(msg, delayMillis); mState = STATE_SUSPECT_LOW_QUALITY; } void exitLowQualityState() { mState = STATE_NORMAL_QUALITY; - for (int i = EVENT_PACKET_LOSS_TIMER_EXPIRED; - i < EVENT_LOW_QUALITY_HANDLER_MAX; i++) { - this.removeMessages(i); - } + this.removeCallbacksAndMessages(null); + mQnsTimer.unregisterTimer(mPacketLossTimerId); + mQnsTimer.unregisterTimer(mHysteresisTimerId); + mQnsTimer.unregisterTimer(mPollingCheckTimerId); + mPacketLossTimerId = INVALID_ID; + mHysteresisTimerId = INVALID_ID; + mPollingCheckTimerId = INVALID_ID; notifyLowMediaQuality(0); } @@ -283,9 +293,10 @@ public class QnsCallStatusTracker { int reason = thresholdBreached(mMediaQualityStatus); if (reason > 0) { notifyLowMediaQuality(thresholdBreached(mMediaQualityStatus)); - } else if (this.hasMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY)) { + } else if (mHysteresisTimerId != INVALID_ID) { // hysteresis time to be normal state is running. let's check after that. - this.sendEmptyMessageDelayed(EVENT_POLLING_CHECK_LOW_QUALITY, + mPollingCheckTimerId = mQnsTimer.registerTimer( + Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY), HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS); } else { Log.w(mTag, "Unexpected case."); @@ -296,19 +307,22 @@ public class QnsCallStatusTracker { void updateForHandover(int transportType) { // restart timers that they need to be restarted on new transport type. if (mState == STATE_SUSPECT_LOW_QUALITY) { - this.removeMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED); + mQnsTimer.unregisterTimer(mPacketLossTimerId); Message msg = this.obtainMessage( EVENT_PACKET_LOSS_TIMER_EXPIRED, transportType, 0); - this.sendMessageDelayed(msg, (mConfigManager.getRTPMetricsData()).mPktLossTime); + mPacketLossTimerId = mQnsTimer.registerTimer(msg, + (mConfigManager.getRTPMetricsData()).mPktLossTime); } - if (this.hasMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY)) { - this.removeMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY); - this.sendEmptyMessageDelayed(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY, + if (mHysteresisTimerId != INVALID_ID) { + mQnsTimer.unregisterTimer(mHysteresisTimerId); + mHysteresisTimerId = mQnsTimer.registerTimer( + Message.obtain(this, EVENT_HYSTERESIS_FOR_NORMAL_QUALITY), HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS); } if (mState == STATE_LOW_QUALITY) { - this.removeMessages(EVENT_POLLING_CHECK_LOW_QUALITY); - this.sendEmptyMessageDelayed(EVENT_POLLING_CHECK_LOW_QUALITY, + mQnsTimer.unregisterTimer(mPollingCheckTimerId); + mPollingCheckTimerId = mQnsTimer.registerTimer( + Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY), LOW_QUALITY_CHECK_AFTER_HO_MILLIS); } } @@ -635,7 +649,7 @@ public class QnsCallStatusTracker { return QnsConstants.INVALID_VALUE; } long qualityLevel = sumDownLinkQualityLevelVolume / totalDuration; - Log.d(mLogTag, "getDownLinkQualityLevel for [" + AccessNetworkConstants + Log.d(mLogTag, "getDownLinkQualityLevel for [" + QnsConstants .transportTypeToString(transportType) + "] totalQualityVolume: " + sumDownLinkQualityLevelVolume + ", totalDuration: " + totalDuration + " level:" + qualityLevel); @@ -727,17 +741,19 @@ public class QnsCallStatusTracker { } QnsCallStatusTracker(QnsTelephonyListener telephonyListener, - QnsCarrierConfigManager configManager, int slotIndex) { - this(telephonyListener, configManager, slotIndex, null); + QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex) { + this(telephonyListener, configManager, qnsTimer, slotIndex, null); } /** Only for test */ @VisibleForTesting QnsCallStatusTracker(QnsTelephonyListener telephonyListener, - QnsCarrierConfigManager configManager, int slotIndex, Looper looper) { + QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex, + Looper looper) { mLogTag = QnsCallStatusTracker.class.getSimpleName() + "_" + slotIndex; mTelephonyListener = telephonyListener; mConfigManager = configManager; + mQnsTimer = qnsTimer; mActiveCallTracker = new ActiveCallTracker(slotIndex, looper); mTelephonyListener.addCallStatesChangedCallback(mCallStatesConsumer); mTelephonyListener.addSrvccStateChangedCallback(mSrvccStateConsumer); @@ -842,6 +858,7 @@ public class QnsCallStatusTracker { } else { mActiveCallTracker.callStarted(callType, netCapability); } + mQnsTimer.updateCallState(callType); } boolean isCallIdle() { diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java index 04780e7..5862bc2 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java @@ -22,6 +22,7 @@ import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -44,6 +45,7 @@ class QnsComponents { private final SparseArray<WifiBackhaulMonitor> mWifiBackhaulMonitors; private final List<Integer> mSlotIds; private IwlanNetworkStatusTracker mIwlanNetworkStatusTracker; + private QnsTimer mQnsTimer; private WifiQualityMonitor mWifiQualityMonitor; private QnsMetrics mQnsMetrics; @@ -88,20 +90,26 @@ class QnsComponents { mQnsCarrierConfigManagers.put( slotId, new QnsCarrierConfigManager(mContext, mQnsEventDispatchers.get(slotId), slotId)); + if (mQnsTimer == null) { + mQnsTimer = new QnsTimer(mContext); + } mQnsCallStatusTracker.put( slotId, - new QnsCallStatusTracker(mQnsTelephonyListeners.get(slotId), - mQnsCarrierConfigManagers.get(slotId), slotId)); + new QnsCallStatusTracker( + mQnsTelephonyListeners.get(slotId), + mQnsCarrierConfigManagers.get(slotId), + mQnsTimer, + slotId)); mWifiBackhaulMonitors.put( slotId, new WifiBackhaulMonitor( mContext, mQnsCarrierConfigManagers.get(slotId), mQnsImsManagers.get(slotId), + mQnsTimer, slotId)); - if (mWifiQualityMonitor == null) { - mWifiQualityMonitor = new WifiQualityMonitor(mContext); + mWifiQualityMonitor = new WifiQualityMonitor(mContext, mQnsTimer); } if (mIwlanNetworkStatusTracker == null) { mIwlanNetworkStatusTracker = new IwlanNetworkStatusTracker(mContext); @@ -131,6 +139,7 @@ class QnsComponents { QnsProvisioningListener qnsProvisioningListener, QnsTelephonyListener qnsTelephonyListener, QnsCallStatusTracker qnsCallStatusTracker, + QnsTimer qnsTimer, WifiBackhaulMonitor wifiBackhaulMonitor, WifiQualityMonitor wifiQualityMonitor, QnsMetrics qnsMetrics, @@ -149,6 +158,7 @@ class QnsComponents { mWifiBackhaulMonitors.put(slotId, wifiBackhaulMonitor); mWifiQualityMonitor = wifiQualityMonitor; + mQnsTimer = qnsTimer; mIwlanNetworkStatusTracker = iwlanNetworkStatusTracker; mIwlanNetworkStatusTracker.initBySlotIndex( qnsCarrierConfigManager, @@ -214,6 +224,11 @@ class QnsComponents { return mWifiQualityMonitor; } + /** Returns instance of QnsTimer. */ + QnsTimer getQnsTimer() { + return mQnsTimer; + } + /** Returns instance of WifiQualityMonitor. */ QnsMetrics getQnsMetrics() { return mQnsMetrics; @@ -247,6 +262,10 @@ class QnsComponents { mQnsCallStatusTracker.remove(slotId); qnsCallStatusTracker.close(); } + if (mSlotIds.size() == 1) { + mQnsTimer.close(); + mQnsTimer = null; + } QnsCarrierConfigManager qnsCarrierConfigManager = mQnsCarrierConfigManagers.get(slotId); if (qnsCarrierConfigManager != null) { mQnsCarrierConfigManagers.remove(slotId); @@ -286,4 +305,16 @@ class QnsComponents { mSlotIds.remove(Integer.valueOf(slotId)); Log.d(mLogTag, "QnsComponents closed for slot " + slotId); } + + void dump(PrintWriter pw) { + if (mIwlanNetworkStatusTracker != null) { + mIwlanNetworkStatusTracker.dump(pw, " "); + } + if (mIwlanNetworkStatusTracker != null) { + mWifiQualityMonitor.dump(pw, " "); + } + if (mIwlanNetworkStatusTracker != null) { + mQnsTimer.dump(pw, " "); + } + } } diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTimer.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTimer.java new file mode 100644 index 0000000..f2439bd --- /dev/null +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTimer.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2023 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.telephony.qns; + +import static com.android.telephony.qns.QnsConstants.CALL_TYPE_IDLE; +import static com.android.telephony.qns.QnsConstants.INVALID_ID; +import static com.android.telephony.qns.QnsUtils.getSystemElapsedRealTime; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.os.PowerManager; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; +import java.util.Comparator; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.concurrent.atomic.AtomicInteger; + +/** This class handles the delayed events triggered in QNS. */ +class QnsTimer { + + private static final String TAG = QnsTimer.class.getSimpleName(); + private static final int EVENT_QNS_TIMER_EXPIRED = 1; + static final String ACTION_ALARM_TIMER_EXPIRED = + "com.android.telephony.qns.action.ALARM_TIMER_EXPIRED"; + + private static final AtomicInteger sTimerId = new AtomicInteger(); + private final Context mContext; + private final AlarmManager mAlarmManager; + private final PowerManager mPowerManager; + private final HandlerThread mHandlerThread; + private final BroadcastReceiver mBroadcastReceiver; + private final PriorityQueue<TimerInfo> mTimerInfos; + private PendingIntent mPendingIntent; + private long mMinAlarmTimeMs = 10000; + private int mCurrentAlarmTimerId = INVALID_ID; + private int mCurrentHandlerTimerId = INVALID_ID; + private boolean mIsAlarmRequired; + @VisibleForTesting final Handler mHandler; + private long mLastAlarmTriggerAtMs = Long.MAX_VALUE; + private int mCallType = CALL_TYPE_IDLE; + + QnsTimer(Context context) { + mContext = context; + mAlarmManager = mContext.getSystemService(AlarmManager.class); + mPowerManager = mContext.getSystemService(PowerManager.class); + mBroadcastReceiver = new AlarmReceiver(); + mTimerInfos = + new PriorityQueue<>(Comparator.comparingLong(TimerInfo::getExpireAtElapsedMillis)); + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new QnsTimerHandler(); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ACTION_ALARM_TIMER_EXPIRED); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + intentFilter.addAction(Intent.ACTION_SCREEN_ON); + intentFilter.addAction(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED); + intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); + mContext.registerReceiver(mBroadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED); + } + + /** + * This method uses AlarmManager to execute the delayed event passed as param. + * + * @param msg message to process. + * @param delayMs timer value for the delay. + * @return unique timer id associated with the registered timer. + */ + int registerTimer(Message msg, long delayMs) { + int timerId = sTimerId.getAndIncrement(); + TimerInfo timerInfo = new TimerInfo(timerId); + timerInfo.setMessage(msg); + timerInfo.setExpireAtElapsedMillis(getSystemElapsedRealTime() + delayMs); + logd("register timer for timerId=" + timerId + ", with delay=" + delayMs); + mHandler.post( + () -> { + mTimerInfos.add(timerInfo); + updateToShortestDelay(mIsAlarmRequired, true); + }); + return timerId; + } + + /** + * This method unregisters the timer associated to given timerId. + * + * @param timerId timer id associated with the running timer. + */ + void unregisterTimer(int timerId) { + if (timerId == INVALID_ID) { + return; + } + logd("unregisterTimer for timerId=" + timerId); + mHandler.post( + () -> { + logd("Cancel timerId=" + timerId); + TimerInfo timerInfo = new TimerInfo(timerId); + if (mTimerInfos.remove(timerInfo) && timerId == mCurrentAlarmTimerId) { + updateToShortestDelay(mIsAlarmRequired, true); + } + }); + } + + /** + * It updates the call state in QnsTimer. If the call is active the minimum timer value for an + * alarm is updated to 0ms. Otherwise the value will be based on device state (Idle, Light Idle + * or Screen off). + * + * @param type Call type {@code @QnsConstants.QnsCallType} + */ + void updateCallState(@QnsConstants.QnsCallType int type) { + if (mCallType == CALL_TYPE_IDLE && type != CALL_TYPE_IDLE) { + mHandler.post( + () -> { + mMinAlarmTimeMs = 0; + if (mIsAlarmRequired) { + updateToShortestDelay(true, false); + } + }); + } + mCallType = type; + if (mCallType == CALL_TYPE_IDLE && mIsAlarmRequired) { + if (mPowerManager.isDeviceIdleMode()) { + mMinAlarmTimeMs = 60000; + } else if (mPowerManager.isDeviceLightIdleMode()) { + mMinAlarmTimeMs = 30000; + } else { + mMinAlarmTimeMs = 10000; // SCREEN_OFF case + } + } + } + + /** + * This method performs the following actions: 1. checks if the shortest timer is set for + * handler or alarm. If not it overrides the earlier set timer with the shortest one. 2. checks + * for timers in the list those have passed the current elapsed time; and notifies them to + * respective handlers. + * + * @param isAlarmRequired flag indicates if timer is need to setup with Alarm. + * @param skipTimerUpdate flag indicates if current scheduled alarm timers needs any change. + * This flag will be false when call type changes or device moves or come out of idle state + * because such cases mandates timer update. + */ + private void updateToShortestDelay(boolean isAlarmRequired, boolean skipTimerUpdate) { + TimerInfo timerInfo = mTimerInfos.peek(); + long elapsedTime = getSystemElapsedRealTime(); + while (timerInfo != null && timerInfo.getExpireAtElapsedMillis() <= elapsedTime) { + logd("Notify timerInfo=" + timerInfo); + timerInfo.getMessage().sendToTarget(); + mTimerInfos.poll(); + timerInfo = mTimerInfos.peek(); + } + if (timerInfo == null) { + logd("No timers are pending to run"); + clearAllTimers(); + return; + } + long delay = timerInfo.getExpireAtElapsedMillis() - elapsedTime; + // Delayed Handler will always set for shortest delay. + if (timerInfo.getTimerId() != mCurrentHandlerTimerId) { + mHandler.removeMessages(EVENT_QNS_TIMER_EXPIRED); + mHandler.sendEmptyMessageDelayed(EVENT_QNS_TIMER_EXPIRED, delay); + mCurrentHandlerTimerId = timerInfo.getTimerId(); + } + + // Alarm will always set for shortest from Math.max(delay, mMinAlarmTimeMs) + if (timerInfo.getTimerId() != mCurrentAlarmTimerId || !skipTimerUpdate) { + if (isAlarmRequired) { + delay = Math.max(delay, mMinAlarmTimeMs); + // check if smaller timer alarm is already running for active timer info. + if (mTimerInfos.contains(new TimerInfo(mCurrentAlarmTimerId)) + && mLastAlarmTriggerAtMs - elapsedTime < delay + && mPendingIntent != null) { + logd( + "Skip update since minimum Alarm Timer already running for timerId=" + + mCurrentAlarmTimerId); + return; + } + logd("Setup alarm for delay " + delay); + mLastAlarmTriggerAtMs = elapsedTime + delay; + setupAlarmFor(mLastAlarmTriggerAtMs); + } else if (mPendingIntent != null) { + mAlarmManager.cancel(mPendingIntent); + mPendingIntent = null; + } + mCurrentAlarmTimerId = timerInfo.getTimerId(); + logd("Update timer to timer id=" + mCurrentAlarmTimerId); + } + } + + private void clearAllTimers() { + mHandler.removeMessages(EVENT_QNS_TIMER_EXPIRED); + if (mPendingIntent != null) { + logd("Cancel Alarm"); + mAlarmManager.cancel(mPendingIntent); + } + mPendingIntent = null; + } + + private void setupAlarmFor(long triggerAtMillis) { + mPendingIntent = + PendingIntent.getBroadcast( + mContext, + 0, + new Intent(ACTION_ALARM_TIMER_EXPIRED), + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + mAlarmManager.setExactAndAllowWhileIdle( + AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, mPendingIntent); + } + + private class AlarmReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + logd("onReceive action=" + action); + switch (action) { + case ACTION_ALARM_TIMER_EXPIRED: + mHandler.sendEmptyMessage(EVENT_QNS_TIMER_EXPIRED); + break; + case Intent.ACTION_SCREEN_OFF: + mHandler.post( + () -> { + mMinAlarmTimeMs = (mCallType == CALL_TYPE_IDLE) ? 10000 : 0; + if (!mIsAlarmRequired) { + mIsAlarmRequired = true; + updateToShortestDelay(true, false); + } + }); + break; + case Intent.ACTION_SCREEN_ON: + mHandler.post( + () -> { + if (mIsAlarmRequired) { + mIsAlarmRequired = false; + updateToShortestDelay(false, false); + } + }); + break; + case PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED: + mHandler.post( + () -> { + if (mPowerManager.isDeviceLightIdleMode()) { + mMinAlarmTimeMs = (mCallType == CALL_TYPE_IDLE) ? 30000 : 0; + if (!mIsAlarmRequired) { + mIsAlarmRequired = true; + updateToShortestDelay(true, false); + } + } else { + mMinAlarmTimeMs = (mCallType == CALL_TYPE_IDLE) ? 10000 : 0; + } + }); + break; + case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: + mHandler.post( + () -> { + if (mPowerManager.isDeviceIdleMode()) { + mMinAlarmTimeMs = (mCallType == CALL_TYPE_IDLE) ? 60000 : 0; + if (!mIsAlarmRequired) { + mIsAlarmRequired = true; + updateToShortestDelay(true, false); + } + } else { + mMinAlarmTimeMs = (mCallType == CALL_TYPE_IDLE) ? 10000 : 0; + } + }); + break; + default: + break; + } + } + } + + private class QnsTimerHandler extends Handler { + QnsTimerHandler() { + super(mHandlerThread.getLooper()); + } + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + logd("handleMessage msg.what=" + msg.what); + switch (msg.what) { + case EVENT_QNS_TIMER_EXPIRED: + logd("Timer expired"); + updateToShortestDelay(mIsAlarmRequired, true); + break; + default: + break; + } + } + } + + static class TimerInfo { + private final int mTimerId; + private long mExpireAtElapsedMillis; + private Message mMsg; + + TimerInfo(int timerId) { + mTimerId = timerId; + } + + public int getTimerId() { + return mTimerId; + } + + public Message getMessage() { + return mMsg; + } + + public void setMessage(Message msg) { + mMsg = msg; + } + + public long getExpireAtElapsedMillis() { + return mExpireAtElapsedMillis; + } + + public void setExpireAtElapsedMillis(long expireAtElapsedMillis) { + mExpireAtElapsedMillis = expireAtElapsedMillis; + } + + /** Timers are equals if they share the same timer id. */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TimerInfo)) return false; + TimerInfo timerInfo = (TimerInfo) o; + return mTimerId == timerInfo.mTimerId; + } + + @Override + public int hashCode() { + return Objects.hash(mTimerId); + } + + @Override + public String toString() { + return "TimerInfo{" + + "mTimerId=" + + mTimerId + + ", mExpireAtElapsedMillis=" + + mExpireAtElapsedMillis + + ", mMsg=" + + mMsg + + '}'; + } + } + + @VisibleForTesting + PriorityQueue<TimerInfo> getTimersInfo() { + return mTimerInfos; + } + + void close() { + logd("Closing QnsTimer"); + mHandlerThread.quitSafely(); + mContext.unregisterReceiver(mBroadcastReceiver); + mTimerInfos.clear(); + clearAllTimers(); + } + + private void logd(String s) { + Log.d(TAG, s); + } + + /** + * Dumps the state of {@link QnsTimer} + * + * @param pw {@link PrintWriter} to write the state of the object. + * @param prefix String to append at start of dumped log. + */ + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "------------------------------"); + pw.println(prefix + "QnsTimer:"); + pw.println( + prefix + + "mIsAlarmRequired=" + + mIsAlarmRequired + + ", mCurrentAlarmTimerId=" + + mCurrentAlarmTimerId + + ", mCurrentHandlerTimerId=" + + mCurrentHandlerTimerId + + ", latest timerId=" + + sTimerId.get() + + ", Current elapsed time=" + + getSystemElapsedRealTime()); + pw.println(prefix + "mTimerInfos=" + mTimerInfos); + pw.println(prefix + "mPendingIntent=" + mPendingIntent); + } +} diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QualifiedNetworksServiceImpl.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QualifiedNetworksServiceImpl.java index 9ac37b1..679098e 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QualifiedNetworksServiceImpl.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QualifiedNetworksServiceImpl.java @@ -358,14 +358,7 @@ public class QualifiedNetworksServiceImpl extends QualifiedNetworksService { NetworkAvailabilityProviderImpl provider = providerMap.getValue(); provider.dump(pw, " "); } - IwlanNetworkStatusTracker iwlanNst = mQnsComponents.getIwlanNetworkStatusTracker(); - if (iwlanNst != null) { - iwlanNst.dump(pw, " "); - } - WifiQualityMonitor wQM = mQnsComponents.getWifiQualityMonitor(); - if (wQM != null) { - wQM.dump(pw, " "); - } + mQnsComponents.dump(pw); pw.println("=============================="); } } diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java index 1c15346..21eaf7c 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java @@ -18,6 +18,7 @@ package com.android.telephony.qns; import static com.android.telephony.qns.DataConnectionStatusTracker.STATE_CONNECTED; import static com.android.telephony.qns.DataConnectionStatusTracker.STATE_HANDOVER; +import static com.android.telephony.qns.QnsConstants.INVALID_ID; import android.annotation.IntDef; import android.net.NetworkCapabilities; @@ -161,6 +162,7 @@ class RestrictManager { private QnsCallStatusTracker mQnsCallStatusTracker; private QnsCallStatusTracker.ActiveCallTracker mActiveCallTracker; private QnsImsManager mQnsImsManager; + private QnsTimer mQnsTimer; private WifiBackhaulMonitor mWifiBackhaulMonitor; private QnsMetrics mQnsMetrics; private int mNetCapability; @@ -174,6 +176,7 @@ class RestrictManager { private int mFallbackCounterOnDataConnectionFail; private boolean mIsRttStatusCheckRegistered = false; private int mLastDataConnectionTransportType; + private int mFallbackTimerId = -1; private boolean mIsTimerRunningOnDataConnectionFail = false; private Pair<Integer, Long> mDeferredThrottlingEvent = null; @@ -183,6 +186,7 @@ class RestrictManager { @Annotation.CallState private int mCallState; private Map<Integer, RestrictInfo> mRestrictInfos = new ConcurrentHashMap<>(); + private Map<Restriction, Integer> mRestrictionTimers = new ConcurrentHashMap<>(); private class RestrictManagerHandler extends Handler { RestrictManagerHandler(Looper l) { @@ -239,6 +243,9 @@ class RestrictManager { .getRestrictionMap() .get(restriction.mRestrictType)) { releaseRestriction(transportType, restriction.mRestrictType); + mQnsTimer.unregisterTimer(mRestrictionTimers + .getOrDefault(restriction, INVALID_ID)); + mRestrictionTimers.remove(restriction); } break; @@ -248,6 +255,7 @@ class RestrictManager { "Initial Data Connection fail timer expired" + mIsTimerRunningOnDataConnectionFail); + mQnsTimer.unregisterTimer(mFallbackTimerId); if (mIsTimerRunningOnDataConnectionFail) { int currTransportType = message.arg1; fallbackToOtherTransportOnDataConnectionFail(currTransportType); @@ -452,6 +460,7 @@ class RestrictManager { mTelephonyListener = qnsComponents.getQnsTelephonyListener(mSlotId); mQnsEventDispatcher = qnsComponents.getQnsEventDispatcher(mSlotId); mQnsCarrierConfigManager = qnsComponents.getQnsCarrierConfigManager(mSlotId); + mQnsTimer = qnsComponents.getQnsTimer(); mHandler = new RestrictManagerHandler(loop); mNetCapability = netCapability; mDataConnectionStatusTracker = dcst; @@ -520,6 +529,7 @@ class RestrictManager { if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) { mQnsImsManager.unregisterImsRegistrationStatusChanged(mHandler); } + mRestrictionTimers.clear(); } private void onWfcModeChanged(int prefMode, @QnsConstants.CellularCoverage int coverage) { @@ -778,9 +788,7 @@ class RestrictManager { mIsTimerRunningOnDataConnectionFail = false; mRetryCounterOnDataConnectionFail = 0; - if (mHandler.hasMessages(EVENT_INITIAL_DATA_CONNECTION_FAIL_RETRY_TIMER_EXPIRED)) { - mHandler.removeMessages(EVENT_INITIAL_DATA_CONNECTION_FAIL_RETRY_TIMER_EXPIRED); - } + mQnsTimer.unregisterTimer(mFallbackTimerId); } private void processDataConnectionDisconnected() { @@ -918,7 +926,7 @@ class RestrictManager { transportType, 0, null); - mHandler.sendMessageDelayed(msg, (long) fallbackRetryTimer); + mFallbackTimerId = mQnsTimer.registerTimer(msg, fallbackRetryTimer); mIsTimerRunningOnDataConnectionFail = true; } } @@ -1293,6 +1301,7 @@ class RestrictManager { removeReleaseRestrictionMessage(restriction); } restrictionMap.remove(restriction.mRestrictType); + mRestrictionTimers.remove(restriction); needNotify = true; } if (needNotify && !skipNotify) { @@ -1329,7 +1338,8 @@ class RestrictManager { Message msg = mHandler.obtainMessage(EVENT_RELEASE_RESTRICTION, transportType, 0, restriction); long delayInMillis = restriction.mReleaseTime - SystemClock.elapsedRealtime(); - mHandler.sendMessageDelayed(msg, delayInMillis); + int timerId = mQnsTimer.registerTimer(msg, delayInMillis); + mRestrictionTimers.put(restriction, timerId); Log.d( mLogTag, restrictTypeToString(restriction.mRestrictType) @@ -1343,7 +1353,8 @@ class RestrictManager { Log.e(mLogTag, "removeReleaseRestrictionMessage restriction is null"); return; } - mHandler.removeMessages(EVENT_RELEASE_RESTRICTION, restriction); + mQnsTimer.unregisterTimer(mRestrictionTimers.getOrDefault(restriction, INVALID_ID)); + mRestrictionTimers.remove(restriction); } void registerRestrictInfoChanged(Handler h, int what) { diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiBackhaulMonitor.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiBackhaulMonitor.java index be3c22c..6bef150 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiBackhaulMonitor.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiBackhaulMonitor.java @@ -16,6 +16,8 @@ package com.android.telephony.qns; +import static com.android.telephony.qns.QnsConstants.INVALID_ID; + import android.content.Context; import android.net.ConnectivityManager; import android.net.LinkProperties; @@ -28,6 +30,8 @@ import android.os.Message; import android.telephony.AccessNetworkConstants; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -56,6 +60,7 @@ class WifiBackhaulMonitor { private final HandlerThread mHandlerThread; private final Handler mHandler; private final QnsCarrierConfigManager mConfigManager; + private final QnsTimer mQnsTimer; private boolean mRttResult = false; ArrayList<InetAddress> mValidIpList = new ArrayList<>(); @@ -65,6 +70,7 @@ class WifiBackhaulMonitor { private boolean mIsIwlanConnected = false; private boolean mIsRttRunning = false; private String mInterfaceName = null; + private int mRttTimerId = INVALID_ID; private class BackhaulHandler extends Handler { BackhaulHandler() { @@ -118,6 +124,7 @@ class WifiBackhaulMonitor { Context context, QnsCarrierConfigManager configManager, QnsImsManager imsManager, + QnsTimer qnstimer, int slotIndex) { mSlotIndex = slotIndex; mTag = WifiBackhaulMonitor.class.getSimpleName() + "[" + mSlotIndex + "]"; @@ -125,6 +132,7 @@ class WifiBackhaulMonitor { mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); mConfigManager = configManager; mQnsImsManager = imsManager; + mQnsTimer = qnstimer; mNetworkCallback = new WiFiStatusCallback(); mRegistrantList = new QnsRegistrantList(); mHandlerThread = new HandlerThread(mTag); @@ -174,8 +182,9 @@ class WifiBackhaulMonitor { /** Triggers the request to check RTT. */ void requestRttCheck() { if (!mIsRttRunning) { - if (mHandler.hasMessages(EVENT_START_RTT_CHECK)) { - mHandler.removeMessages(EVENT_START_RTT_CHECK); + if (mRttTimerId != INVALID_ID) { + mQnsTimer.unregisterTimer(mRttTimerId); + mRttTimerId = INVALID_ID; } mHandler.sendEmptyMessage(EVENT_START_RTT_CHECK); } else { @@ -232,13 +241,17 @@ class WifiBackhaulMonitor { } private void startRttSchedule(int delay) { - mHandler.sendEmptyMessageDelayed(EVENT_START_RTT_CHECK, delay); + log("start RTT schedule for " + delay); + mRttTimerId = mQnsTimer.registerTimer(Message.obtain(mHandler, EVENT_START_RTT_CHECK), + delay); mIsRttScheduled = true; } private void stopRttSchedule() { if (mIsRttScheduled) { - mHandler.removeMessages(EVENT_START_RTT_CHECK); + log("stop RTT schedule"); + mQnsTimer.unregisterTimer(mRttTimerId); + mRttTimerId = INVALID_ID; mIsRttScheduled = false; } } @@ -350,6 +363,11 @@ class WifiBackhaulMonitor { mIsRttScheduled = false; } + @VisibleForTesting + int getRttTimerId() { + return mRttTimerId; + } + private void log(String s) { Log.d(mTag, s); } diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiQualityMonitor.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiQualityMonitor.java index bf758a9..0c5ea2a 100644 --- a/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiQualityMonitor.java +++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiQualityMonitor.java @@ -53,6 +53,8 @@ public class WifiQualityMonitor extends QualityMonitor { private final ConnectivityManager mConnectivityManager; private final WiFiThresholdCallback mWiFiThresholdCallback; private final NetworkRequest.Builder mBuilder; + private final QnsTimer mQnsTimer; + private final List<Integer> mTimerIds; private int mWifiRssi; @VisibleForTesting Handler mHandler; @@ -60,6 +62,7 @@ public class WifiQualityMonitor extends QualityMonitor { private static final int BACKHAUL_TIMER_DEFAULT = 3000; static final int INVALID_RSSI = -127; private boolean mIsRegistered = false; + private boolean mIsBackhaulRunning; private class WiFiThresholdCallback extends ConnectivityManager.NetworkCallback { /** Callback Received based on meeting Wifi RSSI Threshold Registered or Wifi Lost */ @@ -94,11 +97,7 @@ public class WifiQualityMonitor extends QualityMonitor { synchronized void validateWqmStatus(int wifiRssi) { if (isWifiRssiValid(wifiRssi)) { Log.d(mTag, "Registered Threshold @ Wqm Status check =" + mRegisteredThreshold); - if (!mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED)) { - mHandler.obtainMessage(EVENT_WIFI_RSSI_CHANGED, wifiRssi, 0).sendToTarget(); - } else { - Log.d(mTag, "BackhaulCheck in Progress , skip validation"); - } + mHandler.obtainMessage(EVENT_WIFI_RSSI_CHANGED, wifiRssi, 0).sendToTarget(); } else { Log.d(mTag, "Cancel backhaul if running for invalid SS received"); clearBackHaulTimer(); @@ -117,21 +116,24 @@ public class WifiQualityMonitor extends QualityMonitor { } private void clearBackHaulTimer() { - if (mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED)) { - Log.d(mTag, "Stop all active backhaul timers"); - mHandler.removeMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED); - mWaitingThresholds.clear(); + Log.d(mTag, "Stop all active backhaul timers"); + for (int timerId : mTimerIds) { + mQnsTimer.unregisterTimer(timerId); } + mTimerIds.clear(); + mWaitingThresholds.clear(); } /** * Create WifiQualityMonitor object for accessing WifiManager, ConnectivityManager to monitor * RSSI, build parameters for registering threshold & callback listening. */ - WifiQualityMonitor(Context context) { + WifiQualityMonitor(Context context, QnsTimer qnsTimer) { super(QualityMonitor.class.getSimpleName() + "-I"); mTag = WifiQualityMonitor.class.getSimpleName() + "-I"; mContext = context; + mQnsTimer = qnsTimer; + mTimerIds = new ArrayList<>(); HandlerThread handlerThread = new HandlerThread(mTag); handlerThread.start(); mHandler = new WiFiEventsHandler(handlerThread.getLooper()); @@ -239,9 +241,7 @@ public class WifiQualityMonitor extends QualityMonitor { } private void validateForWifiBackhaul(int wifiRssi) { - if (mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED)) { - mHandler.removeMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED); - } + mIsBackhaulRunning = false; for (Map.Entry<String, List<Threshold>> entry : mThresholdsList.entrySet()) { if (mWaitingThresholds.getOrDefault(entry.getKey(), false)) { continue; @@ -262,9 +262,13 @@ public class WifiQualityMonitor extends QualityMonitor { } if (backhaul > 0) { mWaitingThresholds.put(key, true); - if (!mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED)) { - Log.d(mTag, "Starting backhaul timer = " + backhaul); - mHandler.sendEmptyMessageDelayed(EVENT_WIFI_NOTIFY_TIMER_EXPIRED, backhaul); + Log.d(mTag, "Starting backhaul timer = " + backhaul); + if (!mIsBackhaulRunning) { + mTimerIds.add( + mQnsTimer.registerTimer( + Message.obtain(mHandler, EVENT_WIFI_NOTIFY_TIMER_EXPIRED), + backhaul)); + mIsBackhaulRunning = true; } } else { Log.d(mTag, "Notify for RSSI Threshold Registered w/o Backhaul = " + backhaul); @@ -302,7 +306,6 @@ public class WifiQualityMonitor extends QualityMonitor { unregisterCallback(); if (mThresholdsList.isEmpty()) { clearBackHaulTimer(); - mWaitingThresholds.clear(); } } else { Log.d(mTag, "Listening to threshold = " + mRegisteredThreshold); @@ -378,8 +381,8 @@ public class WifiQualityMonitor extends QualityMonitor { prefix + ", mIsRegistered=" + mIsRegistered - + ", backhaulstatus =" - + mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED)); + + ", mIsBackhaulRunning=" + + mIsBackhaulRunning); pw.println( prefix + "mWifiRssi=" |