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