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