1 /*
2  * Copyright (C) 2015 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 package com.android.car.dialer.telecom;
17 
18 import android.bluetooth.BluetoothDevice;
19 import android.content.Intent;
20 import android.os.Binder;
21 import android.os.IBinder;
22 import android.os.Process;
23 import android.telecom.Call;
24 import android.telecom.CallAudioState;
25 import android.telecom.InCallService;
26 import android.telecom.PhoneAccountHandle;
27 
28 import androidx.lifecycle.LiveData;
29 
30 import com.android.car.dialer.bluetooth.PhoneAccountManager;
31 import com.android.car.dialer.framework.InCallServiceProxy;
32 import com.android.car.dialer.log.L;
33 
34 import java.util.concurrent.CopyOnWriteArrayList;
35 
36 import javax.inject.Inject;
37 import javax.inject.Named;
38 
39 import dagger.hilt.android.AndroidEntryPoint;
40 
41 /**
42  * An implementation of {@link InCallService}. This service is bounded by android telecom and
43  * {@link UiCallManager}. For incoming calls it will launch Dialer app.
44  */
45 @AndroidEntryPoint(InCallServiceProxy.class)
46 public class InCallServiceImpl extends Hilt_InCallServiceImpl {
47     private static final String TAG = "CD.InCallService";
48 
49     /** An action which indicates a bind is from local component. */
50     public static final String ACTION_LOCAL_BIND = "local_bind";
51 
52     private CopyOnWriteArrayList<CallAudioStateCallback> mCallAudioStateCallbacks =
53             new CopyOnWriteArrayList<>();
54 
55     @Inject InCallRouter mInCallRouter;
56     @Inject PhoneAccountManager mPhoneAccountManager;
57     @Inject @Named("Hfp") LiveData<BluetoothDevice> mCurrentHfpDeviceLiveData;
58 
59     /** Listens to active call list changes. Callbacks will be called on main thread. */
60     public interface ActiveCallListChangedCallback {
61 
62         /**
63          * Called when a new call is added.
64          *
65          * @return if the given call has been handled by this callback.
66          */
onTelecomCallAdded(Call telecomCall)67         boolean onTelecomCallAdded(Call telecomCall);
68 
69         /**
70          * Called when an existing call is removed.
71          *
72          * @return if the given call has been handled by this callback.
73          */
onTelecomCallRemoved(Call telecomCall)74         boolean onTelecomCallRemoved(Call telecomCall);
75     }
76 
77     /** Listens to call audio state changes. Callbacks will be called on the main thread. */
78     public interface CallAudioStateCallback {
79         /**
80          * Called when the call audio state has changed.
81          */
onCallAudioStateChanged(CallAudioState callAudioState)82         void onCallAudioStateChanged(CallAudioState callAudioState);
83     }
84 
85     @Override
onCreate()86     public void onCreate() {
87         super.onCreate();
88         mInCallRouter.start();
89     }
90 
91     @Override
onDestroy()92     public void onDestroy() {
93         super.onDestroy();
94         mInCallRouter.stop();
95     }
96 
97     @Override
onCallAdded(Call telecomCall)98     public void onCallAdded(Call telecomCall) {
99         L.d(TAG, "onCallAdded: %s", telecomCall);
100         if (telecomCall.getState() == Call.STATE_SELECT_PHONE_ACCOUNT) {
101             BluetoothDevice currentHfpDevice = mCurrentHfpDeviceLiveData.getValue();
102             PhoneAccountHandle currentPhoneAccountHandle =
103                     mPhoneAccountManager.getMatchingPhoneAccount(currentHfpDevice);
104             if (currentPhoneAccountHandle != null) {
105                 telecomCall.phoneAccountSelected(currentPhoneAccountHandle, false);
106             } else {
107                 L.e(TAG, "Not able to get the phone account handle for current hfp device.");
108             }
109         }
110         mInCallRouter.onCallAdded(telecomCall);
111     }
112 
113     @Override
onCallRemoved(Call telecomCall)114     public void onCallRemoved(Call telecomCall) {
115         L.d(TAG, "onCallRemoved: %s", telecomCall);
116         mInCallRouter.onCallRemoved(telecomCall);
117     }
118 
119     @Override
onBind(Intent intent)120     public IBinder onBind(Intent intent) {
121         L.d(TAG, "onBind: %s", intent);
122         return ACTION_LOCAL_BIND.equals(intent.getAction())
123                 ? new LocalBinder()
124                 : super.onBind(intent);
125     }
126 
127     @Override
onUnbind(Intent intent)128     public boolean onUnbind(Intent intent) {
129         L.d(TAG, "onUnbind, intent: %s", intent);
130         if (ACTION_LOCAL_BIND.equals(intent.getAction())) {
131             return false;
132         }
133         return super.onUnbind(intent);
134     }
135 
136     @Override
onCallAudioStateChanged(CallAudioState audioState)137     public void onCallAudioStateChanged(CallAudioState audioState) {
138         for (CallAudioStateCallback callback : mCallAudioStateCallbacks) {
139             callback.onCallAudioStateChanged(audioState);
140         }
141     }
142 
143     @Override
onBringToForeground(boolean showDialpad)144     public void onBringToForeground(boolean showDialpad) {
145         L.d(TAG, "onBringToForeground: %s", showDialpad);
146         mInCallRouter.routeToFullScreenIncomingCallPage(showDialpad);
147     }
148 
addCallAudioStateChangedCallback(CallAudioStateCallback callback)149     public void addCallAudioStateChangedCallback(CallAudioStateCallback callback) {
150         mCallAudioStateCallbacks.add(callback);
151     }
152 
removeCallAudioStateChangedCallback(CallAudioStateCallback callback)153     public void removeCallAudioStateChangedCallback(CallAudioStateCallback callback) {
154         mCallAudioStateCallbacks.remove(callback);
155     }
156 
addActiveCallListChangedCallback(ActiveCallListChangedCallback callback)157     public void addActiveCallListChangedCallback(ActiveCallListChangedCallback callback) {
158         mInCallRouter.registerActiveCallListChangedCallback(callback);
159     }
160 
removeActiveCallListChangedCallback(ActiveCallListChangedCallback callback)161     public void removeActiveCallListChangedCallback(ActiveCallListChangedCallback callback) {
162         mInCallRouter.unregisterActiveCallHandler(callback);
163     }
164 
165     /**
166      * Local binder only available for Car Dialer package.
167      */
168     public class LocalBinder extends Binder {
169 
170         /**
171          * Returns a reference to {@link InCallServiceImpl}. Any process other than Dialer
172          * process won't be able to get a reference.
173          */
getService()174         public InCallServiceImpl getService() {
175             if (getCallingPid() == Process.myPid()) {
176                 return InCallServiceImpl.this;
177             }
178             return null;
179         }
180     }
181 }
182