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 mTelephonyManager = Objects.requireNonNull(telephonyManager); 66 mCallback = Objects.requireNonNull(callback); 67 mSubscriptionManager.addOnSubscriptionsChangedListener( 68 mExecutor, 69 new SubscriptionManager.OnSubscriptionsChangedListener() { 70 @Override 71 public void onSubscriptionsChanged() { 72 updateTelephonyListeners(); 73 } 74 75 @Override 76 public void onAddListenerFailed() { 77 Slog.wtf( 78 "SoundTriggerPhoneCallStateHandler", 79 "Failed to add a telephony listener"); 80 } 81 }); 82 } 83 84 private final class MyCallStateListener extends TelephonyCallback 85 implements TelephonyCallback.CallStateListener { 86 87 final TelephonyManager mTelephonyManagerForSubId; 88 89 // Manager corresponding to the sub-id MyCallStateListener(TelephonyManager telephonyManager)90 MyCallStateListener(TelephonyManager telephonyManager) { 91 mTelephonyManagerForSubId = telephonyManager; 92 } 93 cleanup()94 void cleanup() { 95 mExecutor.execute(() -> mTelephonyManagerForSubId.unregisterTelephonyCallback(this)); 96 } 97 98 @Override onCallStateChanged(int unused)99 public void onCallStateChanged(int unused) { 100 updateCallStatus(); 101 } 102 } 103 104 /** Compute the current call status, and dispatch callback if it has changed. */ updateCallStatus()105 private void updateCallStatus() { 106 boolean callStatus = checkCallStatus(); 107 if (mIsPhoneCallOngoing.compareAndSet(!callStatus, callStatus)) { 108 mCallback.onPhoneCallStateChanged(callStatus); 109 } 110 } 111 112 /** 113 * Synchronously query the current telephony call state across all subscriptions 114 * 115 * @return - {@code true} if in call, {@code false} if not in call. 116 */ checkCallStatus()117 private boolean checkCallStatus() { 118 List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList(); 119 if (infoList == null) return false; 120 return infoList.stream() 121 .filter(s -> (s.getSubscriptionId() != SubscriptionManager.INVALID_SUBSCRIPTION_ID)) 122 .anyMatch(s -> isCallOngoingFromState( 123 mTelephonyManager 124 .createForSubscriptionId(s.getSubscriptionId()) 125 .getCallStateForSubscription())); 126 } 127 updateTelephonyListeners()128 private void updateTelephonyListeners() { 129 synchronized (mLock) { 130 for (var listener : mListenerList) { 131 listener.cleanup(); 132 } 133 mListenerList.clear(); 134 List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList(); 135 if (infoList == null) return; 136 infoList.stream() 137 .filter(s -> s.getSubscriptionId() 138 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) 139 .map(s -> mTelephonyManager.createForSubscriptionId(s.getSubscriptionId())) 140 .forEach(manager -> { 141 synchronized (mLock) { 142 var listener = new MyCallStateListener(manager); 143 mListenerList.add(listener); 144 manager.registerTelephonyCallback(mExecutor, listener); 145 } 146 }); 147 } 148 } 149 isCallOngoingFromState(@nnotation.CallState int callState)150 private static boolean isCallOngoingFromState(@Annotation.CallState int callState) { 151 return switch (callState) { 152 case TelephonyManager.CALL_STATE_IDLE, TelephonyManager.CALL_STATE_RINGING -> false; 153 case TelephonyManager.CALL_STATE_OFFHOOK -> true; 154 default -> throw new IllegalStateException( 155 "Received unexpected call state from Telephony Manager: " + callState); 156 }; 157 } 158 } 159