1 /*
2  * Copyright (C) 2017 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.incallui.call;
18 
19 import android.annotation.TargetApi;
20 import android.app.Notification;
21 import android.bluetooth.BluetoothDevice;
22 import android.content.ActivityNotFoundException;
23 import android.content.Intent;
24 import android.os.Looper;
25 import android.support.annotation.MainThread;
26 import android.support.annotation.VisibleForTesting;
27 import android.telecom.InCallService;
28 import com.android.dialer.common.Assert;
29 import com.android.dialer.common.LogUtil;
30 import java.util.List;
31 
32 /** Wrapper around Telecom APIs. */
33 public class TelecomAdapter implements InCallServiceListener {
34 
35   private static final String ADD_CALL_MODE_KEY = "add_call_mode";
36 
37   private static TelecomAdapter instance;
38   private InCallService inCallService;
39 
TelecomAdapter()40   private TelecomAdapter() {}
41 
42   @MainThread
getInstance()43   public static TelecomAdapter getInstance() {
44     if (!Looper.getMainLooper().isCurrentThread()) {
45       throw new IllegalStateException();
46     }
47     if (instance == null) {
48       instance = new TelecomAdapter();
49     }
50     return instance;
51   }
52 
53   @VisibleForTesting(otherwise = VisibleForTesting.NONE)
setInstanceForTesting(TelecomAdapter telecomAdapter)54   public static void setInstanceForTesting(TelecomAdapter telecomAdapter) {
55     instance = telecomAdapter;
56   }
57 
58   @Override
setInCallService(InCallService inCallService)59   public void setInCallService(InCallService inCallService) {
60     this.inCallService = inCallService;
61   }
62 
63   @Override
clearInCallService()64   public void clearInCallService() {
65     inCallService = null;
66   }
67 
getTelecomCallById(String callId)68   private android.telecom.Call getTelecomCallById(String callId) {
69     DialerCall call = CallList.getInstance().getCallById(callId);
70     return call == null ? null : call.getTelecomCall();
71   }
72 
mute(boolean shouldMute)73   public void mute(boolean shouldMute) {
74     if (inCallService != null) {
75       inCallService.setMuted(shouldMute);
76     } else {
77       LogUtil.e("TelecomAdapter.mute", "mInCallService is null");
78     }
79   }
80 
setAudioRoute(int route)81   public void setAudioRoute(int route) {
82     if (inCallService != null) {
83       inCallService.setAudioRoute(route);
84     } else {
85       LogUtil.e("TelecomAdapter.setAudioRoute", "mInCallService is null");
86     }
87   }
88 
merge(String callId)89   public void merge(String callId) {
90     android.telecom.Call call = getTelecomCallById(callId);
91     if (call != null) {
92       List<android.telecom.Call> conferenceable = call.getConferenceableCalls();
93       if (!conferenceable.isEmpty()) {
94         call.conference(conferenceable.get(0));
95         // It's safe to clear restrict count for merge action.
96         DialerCall.clearRestrictedCount();
97       } else {
98         if (call.getDetails().can(android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE)) {
99           call.mergeConference();
100           // It's safe to clear restrict count for merge action.
101           DialerCall.clearRestrictedCount();
102         }
103       }
104     } else {
105       LogUtil.e("TelecomAdapter.merge", "call not in call list " + callId);
106     }
107   }
108 
swap(String callId)109   public void swap(String callId) {
110     android.telecom.Call call = getTelecomCallById(callId);
111     if (call != null) {
112       if (call.getDetails().can(android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE)) {
113         call.swapConference();
114       }
115     } else {
116       LogUtil.e("TelecomAdapter.swap", "call not in call list " + callId);
117     }
118   }
119 
addCall()120   public void addCall() {
121     if (inCallService != null) {
122       Intent intent = new Intent(Intent.ACTION_DIAL);
123       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
124 
125       // when we request the dialer come up, we also want to inform
126       // it that we're going through the "add call" option from the
127       // InCallScreen / PhoneUtils.
128       intent.putExtra(ADD_CALL_MODE_KEY, true);
129       try {
130         LogUtil.d("TelecomAdapter.addCall", "Sending the add DialerCall intent");
131         inCallService.startActivity(intent);
132       } catch (ActivityNotFoundException e) {
133         // This is rather rare but possible.
134         // Note: this method is used even when the phone is encrypted. At that moment
135         // the system may not find any Activity which can accept this Intent.
136         LogUtil.e("TelecomAdapter.addCall", "Activity for adding calls isn't found.", e);
137       }
138     }
139   }
140 
playDtmfTone(String callId, char digit)141   public void playDtmfTone(String callId, char digit) {
142     android.telecom.Call call = getTelecomCallById(callId);
143     if (call != null) {
144       call.playDtmfTone(digit);
145     } else {
146       LogUtil.e("TelecomAdapter.playDtmfTone", "call not in call list " + callId);
147     }
148   }
149 
stopDtmfTone(String callId)150   public void stopDtmfTone(String callId) {
151     android.telecom.Call call = getTelecomCallById(callId);
152     if (call != null) {
153       call.stopDtmfTone();
154     } else {
155       LogUtil.e("TelecomAdapter.stopDtmfTone", "call not in call list " + callId);
156     }
157   }
158 
postDialContinue(String callId, boolean proceed)159   public void postDialContinue(String callId, boolean proceed) {
160     android.telecom.Call call = getTelecomCallById(callId);
161     if (call != null) {
162       call.postDialContinue(proceed);
163     } else {
164       LogUtil.e("TelecomAdapter.postDialContinue", "call not in call list " + callId);
165     }
166   }
167 
canAddCall()168   public boolean canAddCall() {
169     if (inCallService != null) {
170       return inCallService.canAddCall();
171     }
172     return false;
173   }
174 
175   /**
176    * Start a foreground notification. Calling it multiple times with the same id only updates the
177    * existing notification. Whoever called this function are responsible for calling {@link
178    * #stopForegroundNotification()} to remove the notification.
179    */
startForegroundNotification(int id, Notification notification)180   public void startForegroundNotification(int id, Notification notification) {
181     Assert.isNotNull(
182         inCallService, "No inCallService available for starting foreground notification");
183     inCallService.startForeground(id, notification);
184   }
185 
186   /**
187    * Stop a started foreground notification. This does not stop {@code mInCallService} from running.
188    */
stopForegroundNotification()189   public void stopForegroundNotification() {
190     if (inCallService != null) {
191       inCallService.stopForeground(true /*removeNotification*/);
192     } else {
193       LogUtil.e(
194           "TelecomAdapter.stopForegroundNotification",
195           "no inCallService available for stopping foreground notification");
196     }
197   }
198 
199   @TargetApi(28)
requestBluetoothAudio(BluetoothDevice bluetoothDevice)200   public void requestBluetoothAudio(BluetoothDevice bluetoothDevice) {
201     if (inCallService != null) {
202       inCallService.requestBluetoothAudio(bluetoothDevice);
203     } else {
204       LogUtil.e("TelecomAdapter.requestBluetoothAudio", "inCallService is null");
205     }
206   }
207 }
208