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 
17 package com.android.incallui;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.os.Build.VERSION_CODES;
23 import android.support.annotation.NonNull;
24 import android.support.annotation.RequiresApi;
25 import android.telecom.CallAudioState;
26 import android.telecom.VideoProfile;
27 import com.android.dialer.common.LogUtil;
28 import com.android.dialer.common.concurrent.DialerExecutorComponent;
29 import com.android.dialer.logging.DialerImpression;
30 import com.android.dialer.logging.Logger;
31 import com.android.incallui.call.CallList;
32 import com.android.incallui.call.DialerCall;
33 import com.android.incallui.call.TelecomAdapter;
34 import com.android.incallui.speakeasy.SpeakEasyCallManager;
35 import com.google.common.util.concurrent.FutureCallback;
36 import com.google.common.util.concurrent.Futures;
37 import com.google.common.util.concurrent.ListenableFuture;
38 
39 /**
40  * Accepts broadcast Intents which will be prepared by {@link StatusBarNotifier} and thus sent from
41  * the notification manager. This should be visible from outside, but shouldn't be exported.
42  */
43 public class NotificationBroadcastReceiver extends BroadcastReceiver {
44 
45   /**
46    * Intent Action used for hanging up the current call from Notification bar. This will choose
47    * first ringing call, first active call, or first background call (typically in STATE_HOLDING
48    * state).
49    */
50   public static final String ACTION_DECLINE_INCOMING_CALL =
51       "com.android.incallui.ACTION_DECLINE_INCOMING_CALL";
52 
53   public static final String ACTION_HANG_UP_ONGOING_CALL =
54       "com.android.incallui.ACTION_HANG_UP_ONGOING_CALL";
55   public static final String ACTION_ANSWER_VIDEO_INCOMING_CALL =
56       "com.android.incallui.ACTION_ANSWER_VIDEO_INCOMING_CALL";
57   public static final String ACTION_ANSWER_VOICE_INCOMING_CALL =
58       "com.android.incallui.ACTION_ANSWER_VOICE_INCOMING_CALL";
59   public static final String ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST =
60       "com.android.incallui.ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST";
61   public static final String ACTION_DECLINE_VIDEO_UPGRADE_REQUEST =
62       "com.android.incallui.ACTION_DECLINE_VIDEO_UPGRADE_REQUEST";
63   public static final String ACTION_TURN_ON_SPEAKER = "com.android.incallui.ACTION_TURN_ON_SPEAKER";
64   public static final String ACTION_TURN_OFF_SPEAKER =
65       "com.android.incallui.ACTION_TURN_OFF_SPEAKER";
66   public static final String ACTION_ANSWER_SPEAKEASY_CALL =
67       "com.android.incallui.ACTION_ANSWER_SPEAKEASY_CALL";
68 
69   @RequiresApi(VERSION_CODES.N_MR1)
70   public static final String ACTION_PULL_EXTERNAL_CALL =
71       "com.android.incallui.ACTION_PULL_EXTERNAL_CALL";
72 
73   public static final String EXTRA_NOTIFICATION_ID =
74       "com.android.incallui.extra.EXTRA_NOTIFICATION_ID";
75 
76   @Override
onReceive(Context context, Intent intent)77   public void onReceive(Context context, Intent intent) {
78     final String action = intent.getAction();
79     LogUtil.i("NotificationBroadcastReceiver.onReceive", "Broadcast from Notification: " + action);
80 
81     // TODO: Commands of this nature should exist in the CallList.
82     if (action.equals(ACTION_ANSWER_VIDEO_INCOMING_CALL)) {
83       answerIncomingCall(VideoProfile.STATE_BIDIRECTIONAL, context);
84     } else if (action.equals(ACTION_ANSWER_VOICE_INCOMING_CALL)) {
85       answerIncomingCall(VideoProfile.STATE_AUDIO_ONLY, context);
86     } else if (action.equals(ACTION_ANSWER_SPEAKEASY_CALL)) {
87       markIncomingCallAsSpeakeasyCall();
88       answerIncomingCall(VideoProfile.STATE_AUDIO_ONLY, context);
89     } else if (action.equals(ACTION_DECLINE_INCOMING_CALL)) {
90       Logger.get(context)
91           .logImpression(DialerImpression.Type.REJECT_INCOMING_CALL_FROM_NOTIFICATION);
92       declineIncomingCall();
93     } else if (action.equals(ACTION_HANG_UP_ONGOING_CALL)) {
94       hangUpOngoingCall();
95     } else if (action.equals(ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST)) {
96       acceptUpgradeRequest(context);
97     } else if (action.equals(ACTION_DECLINE_VIDEO_UPGRADE_REQUEST)) {
98       declineUpgradeRequest();
99     } else if (action.equals(ACTION_PULL_EXTERNAL_CALL)) {
100       context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
101       int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
102       InCallPresenter.getInstance().getExternalCallNotifier().pullExternalCall(notificationId);
103     } else if (action.equals(ACTION_TURN_ON_SPEAKER)) {
104       TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_SPEAKER);
105     } else if (action.equals(ACTION_TURN_OFF_SPEAKER)) {
106       TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
107     }
108   }
109 
acceptUpgradeRequest(Context context)110   private void acceptUpgradeRequest(Context context) {
111     CallList callList = InCallPresenter.getInstance().getCallList();
112     if (callList == null) {
113       StatusBarNotifier.clearAllCallNotifications();
114       LogUtil.e("NotificationBroadcastReceiver.acceptUpgradeRequest", "call list is empty");
115     } else {
116       DialerCall call = callList.getVideoUpgradeRequestCall();
117       if (call != null) {
118         call.getVideoTech().acceptVideoRequest(context);
119       }
120     }
121   }
122 
declineUpgradeRequest()123   private void declineUpgradeRequest() {
124     CallList callList = InCallPresenter.getInstance().getCallList();
125     if (callList == null) {
126       StatusBarNotifier.clearAllCallNotifications();
127       LogUtil.e("NotificationBroadcastReceiver.declineUpgradeRequest", "call list is empty");
128     } else {
129       DialerCall call = callList.getVideoUpgradeRequestCall();
130       if (call != null) {
131         call.getVideoTech().declineVideoRequest();
132       }
133     }
134   }
135 
hangUpOngoingCall()136   private void hangUpOngoingCall() {
137     CallList callList = InCallPresenter.getInstance().getCallList();
138     if (callList == null) {
139       StatusBarNotifier.clearAllCallNotifications();
140       LogUtil.e("NotificationBroadcastReceiver.hangUpOngoingCall", "call list is empty");
141     } else {
142       DialerCall call = callList.getOutgoingCall();
143       if (call == null) {
144         call = callList.getActiveOrBackgroundCall();
145       }
146       LogUtil.i(
147           "NotificationBroadcastReceiver.hangUpOngoingCall", "disconnecting call, call: " + call);
148       if (call != null) {
149         call.disconnect();
150       }
151     }
152   }
153 
markIncomingCallAsSpeakeasyCall()154   private void markIncomingCallAsSpeakeasyCall() {
155     CallList callList = InCallPresenter.getInstance().getCallList();
156     if (callList == null) {
157       LogUtil.e(
158           "NotificationBroadcastReceiver.markIncomingCallAsSpeakeasyCall", "call list is empty");
159     } else {
160       DialerCall call = callList.getIncomingCall();
161       if (call != null) {
162         call.setIsSpeakEasyCall(true);
163       }
164     }
165   }
166 
answerIncomingCall(int videoState, @NonNull Context context)167   private void answerIncomingCall(int videoState, @NonNull Context context) {
168     CallList callList = InCallPresenter.getInstance().getCallList();
169     if (callList == null) {
170       StatusBarNotifier.clearAllCallNotifications();
171       LogUtil.e("NotificationBroadcastReceiver.answerIncomingCall", "call list is empty");
172     } else {
173       DialerCall call = callList.getIncomingCall();
174       if (call != null) {
175 
176         SpeakEasyCallManager speakEasyCallManager =
177             InCallPresenter.getInstance().getSpeakEasyCallManager();
178         ListenableFuture<Void> answerPrecondition;
179 
180         if (speakEasyCallManager != null) {
181           answerPrecondition = speakEasyCallManager.onNewIncomingCall(call);
182         } else {
183           answerPrecondition = Futures.immediateFuture(null);
184         }
185 
186         Futures.addCallback(
187             answerPrecondition,
188             new FutureCallback<Void>() {
189               @Override
190               public void onSuccess(Void result) {
191                 answerIncomingCallCallback(call, videoState);
192               }
193 
194               @Override
195               public void onFailure(Throwable t) {
196                 answerIncomingCallCallback(call, videoState);
197                 // TODO(erfanian): Enumerate all error states and specify recovery strategies.
198                 throw new RuntimeException("Failed to successfully complete pre call tasks.", t);
199               }
200             },
201             DialerExecutorComponent.get(context).uiExecutor());
202       }
203     }
204   }
205 
answerIncomingCallCallback(@onNull DialerCall call, int videoState)206   private void answerIncomingCallCallback(@NonNull DialerCall call, int videoState) {
207     call.answer(videoState);
208     InCallPresenter.getInstance().showInCall(false /* showDialpad */, false /* newOutgoingCall */);
209   }
210 
declineIncomingCall()211   private void declineIncomingCall() {
212     CallList callList = InCallPresenter.getInstance().getCallList();
213     if (callList == null) {
214       StatusBarNotifier.clearAllCallNotifications();
215       LogUtil.e("NotificationBroadcastReceiver.declineIncomingCall", "call list is empty");
216     } else {
217       DialerCall call = callList.getIncomingCall();
218       if (call != null) {
219         call.reject(false /* rejectWithMessage */, null);
220       }
221     }
222   }
223 }
224