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