• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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