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