1 /** 2 * Copyright (C) 2023 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.server.soundtrigger; 18 19 import android.telephony.Annotation; 20 import android.telephony.SubscriptionInfo; 21 import android.telephony.SubscriptionManager; 22 import android.telephony.TelephonyCallback; 23 import android.telephony.TelephonyManager; 24 import android.util.Slog; 25 26 import com.android.internal.annotations.GuardedBy; 27 import com.android.internal.telephony.flags.Flags; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 import java.util.Objects; 32 import java.util.concurrent.ExecutorService; 33 import java.util.concurrent.Executors; 34 import java.util.concurrent.atomic.AtomicBoolean; 35 36 /** 37 * Handles monitoring telephony call state across active subscriptions. 38 * 39 * @hide 40 */ 41 public class PhoneCallStateHandler { 42 43 public interface Callback { onPhoneCallStateChanged(boolean isInPhoneCall)44 void onPhoneCallStateChanged(boolean isInPhoneCall); 45 } 46 47 private final Object mLock = new Object(); 48 49 // Actually never contended due to executor. 50 @GuardedBy("mLock") 51 private final List<MyCallStateListener> mListenerList = new ArrayList<>(); 52 53 private final AtomicBoolean mIsPhoneCallOngoing = new AtomicBoolean(false); 54 55 private final SubscriptionManager mSubscriptionManager; 56 private final TelephonyManager mTelephonyManager; 57 private final Callback mCallback; 58 59 private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); 60 PhoneCallStateHandler( SubscriptionManager subscriptionManager, TelephonyManager telephonyManager, Callback callback)61 public PhoneCallStateHandler( 62 SubscriptionManager subscriptionManager, 63 TelephonyManager telephonyManager, 64 Callback callback) { 65 mSubscriptionManager = Objects.requireNonNull(subscriptionManager) 66 .createForAllUserProfiles(); 67 mTelephonyManager = Objects.requireNonNull(telephonyManager); 68 mCallback = Objects.requireNonNull(callback); 69 mSubscriptionManager.addOnSubscriptionsChangedListener( 70 mExecutor, 71 new SubscriptionManager.OnSubscriptionsChangedListener() { 72 @Override 73 public void onSubscriptionsChanged() { 74 updateTelephonyListeners(); 75 } 76 77 @Override 78 public void onAddListenerFailed() { 79 Slog.wtf( 80 "SoundTriggerPhoneCallStateHandler", 81 "Failed to add a telephony listener"); 82 } 83 }); 84 } 85 86 private final class MyCallStateListener extends TelephonyCallback 87 implements TelephonyCallback.CallStateListener { 88 89 final TelephonyManager mTelephonyManagerForSubId; 90 91 // Manager corresponding to the sub-id MyCallStateListener(TelephonyManager telephonyManager)92 MyCallStateListener(TelephonyManager telephonyManager) { 93 mTelephonyManagerForSubId = telephonyManager; 94 } 95 cleanup()96 void cleanup() { 97 mExecutor.execute(() -> mTelephonyManagerForSubId.unregisterTelephonyCallback(this)); 98 } 99 100 @Override onCallStateChanged(int unused)101 public void onCallStateChanged(int unused) { 102 updateCallStatus(); 103 } 104 } 105 106 /** Compute the current call status, and dispatch callback if it has changed. */ updateCallStatus()107 private void updateCallStatus() { 108 boolean callStatus = checkCallStatus(); 109 if (mIsPhoneCallOngoing.compareAndSet(!callStatus, callStatus)) { 110 mCallback.onPhoneCallStateChanged(callStatus); 111 } 112 } 113 114 /** 115 * Synchronously query the current telephony call state across all subscriptions 116 * 117 * @return - {@code true} if in call, {@code false} if not in call. 118 */ checkCallStatus()119 private boolean checkCallStatus() { 120 List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList(); 121 if (infoList == null) return false; 122 if (!Flags.enforceTelephonyFeatureMapping()) { 123 return infoList.stream() 124 .filter(s -> (s.getSubscriptionId() 125 != SubscriptionManager.INVALID_SUBSCRIPTION_ID)) 126 .anyMatch(s -> isCallOngoingFromState( 127 mTelephonyManager 128 .createForSubscriptionId(s.getSubscriptionId()) 129 .getCallStateForSubscription())); 130 } else { 131 return infoList.stream() 132 .filter(s -> (s.getSubscriptionId() 133 != SubscriptionManager.INVALID_SUBSCRIPTION_ID)) 134 .anyMatch(s -> { 135 try { 136 return isCallOngoingFromState(mTelephonyManager 137 .createForSubscriptionId(s.getSubscriptionId()) 138 .getCallStateForSubscription()); 139 } catch (UnsupportedOperationException e) { 140 return false; 141 } 142 }); 143 } 144 } 145 146 private void updateTelephonyListeners() { 147 synchronized (mLock) { 148 for (var listener : mListenerList) { 149 listener.cleanup(); 150 } 151 mListenerList.clear(); 152 List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList(); 153 if (infoList == null) return; 154 infoList.stream() 155 .filter(s -> s.getSubscriptionId() 156 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) 157 .map(s -> mTelephonyManager.createForSubscriptionId(s.getSubscriptionId())) 158 .forEach(manager -> { 159 synchronized (mLock) { 160 var listener = new MyCallStateListener(manager); 161 mListenerList.add(listener); 162 manager.registerTelephonyCallback(mExecutor, listener); 163 } 164 }); 165 } 166 } 167 168 private static boolean isCallOngoingFromState(@Annotation.CallState int callState) { 169 return switch (callState) { 170 case TelephonyManager.CALL_STATE_IDLE, TelephonyManager.CALL_STATE_RINGING -> false; 171 case TelephonyManager.CALL_STATE_OFFHOOK -> true; 172 default -> throw new IllegalStateException( 173 "Received unexpected call state from Telephony Manager: " + callState); 174 }; 175 } 176 } 177