1 /* 2 * Copyright (C) 2016 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.Context; 20 import android.os.SystemClock; 21 import android.support.annotation.FloatRange; 22 import android.support.annotation.NonNull; 23 import android.support.v4.os.UserManagerCompat; 24 import android.telecom.VideoProfile; 25 import com.android.dialer.common.Assert; 26 import com.android.dialer.common.LogUtil; 27 import com.android.dialer.common.concurrent.DialerExecutorComponent; 28 import com.android.dialer.common.concurrent.ThreadUtil; 29 import com.android.dialer.logging.DialerImpression; 30 import com.android.dialer.logging.Logger; 31 import com.android.incallui.answer.protocol.AnswerScreen; 32 import com.android.incallui.answer.protocol.AnswerScreenDelegate; 33 import com.android.incallui.answerproximitysensor.AnswerProximitySensor; 34 import com.android.incallui.answerproximitysensor.PseudoScreenState; 35 import com.android.incallui.call.CallList; 36 import com.android.incallui.call.DialerCall; 37 import com.android.incallui.call.DialerCallListener; 38 import com.android.incallui.incalluilock.InCallUiLock; 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 /** Manages changes for an incoming call screen. */ 44 public class AnswerScreenPresenter 45 implements AnswerScreenDelegate, DialerCall.CannedTextResponsesLoadedListener { 46 private static final int ACCEPT_REJECT_CALL_TIME_OUT_IN_MILLIS = 5000; 47 48 @NonNull private final Context context; 49 @NonNull private final AnswerScreen answerScreen; 50 @NonNull private final DialerCall call; 51 private long actionPerformedTimeMillis; 52 AnswerScreenPresenter( @onNull Context context, @NonNull AnswerScreen answerScreen, @NonNull DialerCall call)53 AnswerScreenPresenter( 54 @NonNull Context context, @NonNull AnswerScreen answerScreen, @NonNull DialerCall call) { 55 LogUtil.i("AnswerScreenPresenter.constructor", null); 56 this.context = Assert.isNotNull(context); 57 this.answerScreen = Assert.isNotNull(answerScreen); 58 this.call = Assert.isNotNull(call); 59 if (isSmsResponseAllowed(call)) { 60 answerScreen.setTextResponses(call.getCannedSmsResponses()); 61 } 62 call.addCannedTextResponsesLoadedListener(this); 63 64 PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState(); 65 if (AnswerProximitySensor.shouldUse(context, call)) { 66 new AnswerProximitySensor(context, call, pseudoScreenState); 67 } else { 68 pseudoScreenState.setOn(true); 69 } 70 } 71 72 @Override isActionTimeout()73 public boolean isActionTimeout() { 74 return actionPerformedTimeMillis != 0 75 && SystemClock.elapsedRealtime() - actionPerformedTimeMillis 76 >= ACCEPT_REJECT_CALL_TIME_OUT_IN_MILLIS; 77 } 78 79 @Override acquireInCallUiLock(String tag)80 public InCallUiLock acquireInCallUiLock(String tag) { 81 return InCallPresenter.getInstance().acquireInCallUiLock(tag); 82 } 83 84 @Override onAnswerScreenUnready()85 public void onAnswerScreenUnready() { 86 call.removeCannedTextResponsesLoadedListener(this); 87 } 88 89 @Override onRejectCallWithMessage(String message)90 public void onRejectCallWithMessage(String message) { 91 call.reject(true /* rejectWithMessage */, message); 92 addTimeoutCheck(); 93 } 94 95 @Override onAnswer(boolean answerVideoAsAudio)96 public void onAnswer(boolean answerVideoAsAudio) { 97 98 DialerCall incomingCall = CallList.getInstance().getIncomingCall(); 99 InCallActivity inCallActivity = 100 (InCallActivity) answerScreen.getAnswerScreenFragment().getActivity(); 101 ListenableFuture<Void> answerPrecondition; 102 103 if (incomingCall != null && inCallActivity != null) { 104 answerPrecondition = inCallActivity.getSpeakEasyCallManager().onNewIncomingCall(incomingCall); 105 } else { 106 answerPrecondition = Futures.immediateFuture(null); 107 } 108 109 Futures.addCallback( 110 answerPrecondition, 111 new FutureCallback<Void>() { 112 @Override 113 public void onSuccess(Void result) { 114 onAnswerCallback(answerVideoAsAudio); 115 } 116 117 @Override 118 public void onFailure(Throwable t) { 119 onAnswerCallback(answerVideoAsAudio); 120 // TODO(erfanian): Enumerate all error states and specify recovery strategies. 121 throw new RuntimeException("Failed to successfully complete pre call tasks.", t); 122 } 123 }, 124 DialerExecutorComponent.get(context).uiExecutor()); 125 addTimeoutCheck(); 126 } 127 onAnswerCallback(boolean answerVideoAsAudio)128 private void onAnswerCallback(boolean answerVideoAsAudio) { 129 130 if (answerScreen.isVideoUpgradeRequest()) { 131 if (answerVideoAsAudio) { 132 Logger.get(context) 133 .logCallImpression( 134 DialerImpression.Type.VIDEO_CALL_REQUEST_ACCEPTED_AS_AUDIO, 135 call.getUniqueCallId(), 136 call.getTimeAddedMs()); 137 call.getVideoTech().acceptVideoRequestAsAudio(); 138 } else { 139 Logger.get(context) 140 .logCallImpression( 141 DialerImpression.Type.VIDEO_CALL_REQUEST_ACCEPTED, 142 call.getUniqueCallId(), 143 call.getTimeAddedMs()); 144 call.getVideoTech().acceptVideoRequest(context); 145 } 146 } else { 147 if (answerVideoAsAudio) { 148 call.answer(VideoProfile.STATE_AUDIO_ONLY); 149 } else { 150 call.answer(); 151 } 152 } 153 } 154 155 @Override onReject()156 public void onReject() { 157 if (answerScreen.isVideoUpgradeRequest()) { 158 Logger.get(context) 159 .logCallImpression( 160 DialerImpression.Type.VIDEO_CALL_REQUEST_DECLINED, 161 call.getUniqueCallId(), 162 call.getTimeAddedMs()); 163 call.getVideoTech().declineVideoRequest(); 164 } else { 165 call.reject(false /* rejectWithMessage */, null); 166 } 167 addTimeoutCheck(); 168 } 169 170 @Override onSpeakEasyCall()171 public void onSpeakEasyCall() { 172 LogUtil.enterBlock("AnswerScreenPresenter.onSpeakEasyCall"); 173 DialerCall incomingCall = CallList.getInstance().getIncomingCall(); 174 if (incomingCall == null) { 175 LogUtil.i("AnswerScreenPresenter.onSpeakEasyCall", "incomingCall == null"); 176 return; 177 } 178 incomingCall.setIsSpeakEasyCall(true); 179 } 180 181 @Override onAnswerAndReleaseCall()182 public void onAnswerAndReleaseCall() { 183 LogUtil.enterBlock("AnswerScreenPresenter.onAnswerAndReleaseCall"); 184 DialerCall activeCall = CallList.getInstance().getActiveCall(); 185 if (activeCall == null) { 186 LogUtil.i("AnswerScreenPresenter.onAnswerAndReleaseCall", "activeCall == null"); 187 onAnswer(false); 188 } else { 189 activeCall.setReleasedByAnsweringSecondCall(true); 190 activeCall.addListener(new AnswerOnDisconnected(activeCall)); 191 activeCall.disconnect(); 192 } 193 addTimeoutCheck(); 194 } 195 196 @Override onAnswerAndReleaseButtonDisabled()197 public void onAnswerAndReleaseButtonDisabled() { 198 DialerCall activeCall = CallList.getInstance().getActiveCall(); 199 if (activeCall != null) { 200 activeCall.increaseSecondCallWithoutAnswerAndReleasedButtonTimes(); 201 } 202 } 203 204 @Override onAnswerAndReleaseButtonEnabled()205 public void onAnswerAndReleaseButtonEnabled() { 206 DialerCall activeCall = CallList.getInstance().getActiveCall(); 207 if (activeCall != null) { 208 activeCall.increaseAnswerAndReleaseButtonDisplayedTimes(); 209 } 210 } 211 212 @Override onCannedTextResponsesLoaded(DialerCall call)213 public void onCannedTextResponsesLoaded(DialerCall call) { 214 if (isSmsResponseAllowed(call)) { 215 answerScreen.setTextResponses(call.getCannedSmsResponses()); 216 } 217 } 218 219 @Override updateWindowBackgroundColor(@loatRangefrom = -1f, to = 1.0f) float progress)220 public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) { 221 InCallActivity activity = (InCallActivity) answerScreen.getAnswerScreenFragment().getActivity(); 222 if (activity != null) { 223 activity.updateWindowBackgroundColor(progress); 224 } 225 } 226 227 private class AnswerOnDisconnected implements DialerCallListener { 228 229 private final DialerCall disconnectingCall; 230 AnswerOnDisconnected(DialerCall disconnectingCall)231 AnswerOnDisconnected(DialerCall disconnectingCall) { 232 this.disconnectingCall = disconnectingCall; 233 } 234 235 @Override onDialerCallDisconnect()236 public void onDialerCallDisconnect() { 237 LogUtil.i( 238 "AnswerScreenPresenter.AnswerOnDisconnected", "call disconnected, answering new call"); 239 call.answer(); 240 disconnectingCall.removeListener(this); 241 } 242 243 @Override onDialerCallUpdate()244 public void onDialerCallUpdate() {} 245 246 @Override onDialerCallChildNumberChange()247 public void onDialerCallChildNumberChange() {} 248 249 @Override onDialerCallLastForwardedNumberChange()250 public void onDialerCallLastForwardedNumberChange() {} 251 252 @Override onDialerCallUpgradeToVideo()253 public void onDialerCallUpgradeToVideo() {} 254 255 @Override onDialerCallSessionModificationStateChange()256 public void onDialerCallSessionModificationStateChange() {} 257 258 @Override onWiFiToLteHandover()259 public void onWiFiToLteHandover() {} 260 261 @Override onHandoverToWifiFailure()262 public void onHandoverToWifiFailure() {} 263 264 @Override onInternationalCallOnWifi()265 public void onInternationalCallOnWifi() {} 266 267 @Override onEnrichedCallSessionUpdate()268 public void onEnrichedCallSessionUpdate() {} 269 } 270 isSmsResponseAllowed(DialerCall call)271 private boolean isSmsResponseAllowed(DialerCall call) { 272 return UserManagerCompat.isUserUnlocked(context) 273 && call.can(android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT); 274 } 275 addTimeoutCheck()276 private void addTimeoutCheck() { 277 actionPerformedTimeMillis = SystemClock.elapsedRealtime(); 278 if (answerScreen.getAnswerScreenFragment().isVisible()) { 279 ThreadUtil.postDelayedOnUiThread( 280 () -> { 281 if (!answerScreen.getAnswerScreenFragment().isVisible()) { 282 LogUtil.d( 283 "AnswerScreenPresenter.addTimeoutCheck", 284 "accept/reject call timed out, do nothing"); 285 return; 286 } 287 LogUtil.i("AnswerScreenPresenter.addTimeoutCheck", "accept/reject call timed out"); 288 // Force re-evaluate which fragment to show. 289 InCallPresenter.getInstance().refreshUi(); 290 }, 291 ACCEPT_REJECT_CALL_TIME_OUT_IN_MILLIS); 292 } 293 } 294 } 295