/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.incallui; import android.content.Context; import android.os.SystemClock; import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import android.support.v4.os.UserManagerCompat; import android.telecom.VideoProfile; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.incallui.answer.protocol.AnswerScreen; import com.android.incallui.answer.protocol.AnswerScreenDelegate; import com.android.incallui.answerproximitysensor.AnswerProximitySensor; import com.android.incallui.answerproximitysensor.PseudoScreenState; import com.android.incallui.call.CallList; import com.android.incallui.call.DialerCall; import com.android.incallui.call.DialerCallListener; import com.android.incallui.incalluilock.InCallUiLock; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; /** Manages changes for an incoming call screen. */ public class AnswerScreenPresenter implements AnswerScreenDelegate, DialerCall.CannedTextResponsesLoadedListener { private static final int ACCEPT_REJECT_CALL_TIME_OUT_IN_MILLIS = 5000; @NonNull private final Context context; @NonNull private final AnswerScreen answerScreen; @NonNull private final DialerCall call; private long actionPerformedTimeMillis; AnswerScreenPresenter( @NonNull Context context, @NonNull AnswerScreen answerScreen, @NonNull DialerCall call) { LogUtil.i("AnswerScreenPresenter.constructor", null); this.context = Assert.isNotNull(context); this.answerScreen = Assert.isNotNull(answerScreen); this.call = Assert.isNotNull(call); if (isSmsResponseAllowed(call)) { answerScreen.setTextResponses(call.getCannedSmsResponses()); } call.addCannedTextResponsesLoadedListener(this); PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState(); if (AnswerProximitySensor.shouldUse(context, call)) { new AnswerProximitySensor(context, call, pseudoScreenState); } else { pseudoScreenState.setOn(true); } } @Override public boolean isActionTimeout() { return actionPerformedTimeMillis != 0 && SystemClock.elapsedRealtime() - actionPerformedTimeMillis >= ACCEPT_REJECT_CALL_TIME_OUT_IN_MILLIS; } @Override public InCallUiLock acquireInCallUiLock(String tag) { return InCallPresenter.getInstance().acquireInCallUiLock(tag); } @Override public void onAnswerScreenUnready() { call.removeCannedTextResponsesLoadedListener(this); } @Override public void onRejectCallWithMessage(String message) { call.reject(true /* rejectWithMessage */, message); addTimeoutCheck(); } @Override public void onAnswer(boolean answerVideoAsAudio) { DialerCall incomingCall = CallList.getInstance().getIncomingCall(); InCallActivity inCallActivity = (InCallActivity) answerScreen.getAnswerScreenFragment().getActivity(); ListenableFuture answerPrecondition; if (incomingCall != null && inCallActivity != null) { answerPrecondition = inCallActivity.getSpeakEasyCallManager().onNewIncomingCall(incomingCall); } else { answerPrecondition = Futures.immediateFuture(null); } Futures.addCallback( answerPrecondition, new FutureCallback() { @Override public void onSuccess(Void result) { onAnswerCallback(answerVideoAsAudio); } @Override public void onFailure(Throwable t) { onAnswerCallback(answerVideoAsAudio); // TODO(erfanian): Enumerate all error states and specify recovery strategies. throw new RuntimeException("Failed to successfully complete pre call tasks.", t); } }, DialerExecutorComponent.get(context).uiExecutor()); addTimeoutCheck(); } private void onAnswerCallback(boolean answerVideoAsAudio) { if (answerScreen.isVideoUpgradeRequest()) { if (answerVideoAsAudio) { Logger.get(context) .logCallImpression( DialerImpression.Type.VIDEO_CALL_REQUEST_ACCEPTED_AS_AUDIO, call.getUniqueCallId(), call.getTimeAddedMs()); call.getVideoTech().acceptVideoRequestAsAudio(); } else { Logger.get(context) .logCallImpression( DialerImpression.Type.VIDEO_CALL_REQUEST_ACCEPTED, call.getUniqueCallId(), call.getTimeAddedMs()); call.getVideoTech().acceptVideoRequest(context); } } else { if (answerVideoAsAudio) { call.answer(VideoProfile.STATE_AUDIO_ONLY); } else { call.answer(); } } } @Override public void onReject() { if (answerScreen.isVideoUpgradeRequest()) { Logger.get(context) .logCallImpression( DialerImpression.Type.VIDEO_CALL_REQUEST_DECLINED, call.getUniqueCallId(), call.getTimeAddedMs()); call.getVideoTech().declineVideoRequest(); } else { call.reject(false /* rejectWithMessage */, null); } addTimeoutCheck(); } @Override public void onSpeakEasyCall() { LogUtil.enterBlock("AnswerScreenPresenter.onSpeakEasyCall"); DialerCall incomingCall = CallList.getInstance().getIncomingCall(); if (incomingCall == null) { LogUtil.i("AnswerScreenPresenter.onSpeakEasyCall", "incomingCall == null"); return; } incomingCall.setIsSpeakEasyCall(true); } @Override public void onAnswerAndReleaseCall() { LogUtil.enterBlock("AnswerScreenPresenter.onAnswerAndReleaseCall"); DialerCall activeCall = CallList.getInstance().getActiveCall(); if (activeCall == null) { LogUtil.i("AnswerScreenPresenter.onAnswerAndReleaseCall", "activeCall == null"); onAnswer(false); } else { activeCall.setReleasedByAnsweringSecondCall(true); activeCall.addListener(new AnswerOnDisconnected(activeCall)); activeCall.disconnect(); } addTimeoutCheck(); } @Override public void onAnswerAndReleaseButtonDisabled() { DialerCall activeCall = CallList.getInstance().getActiveCall(); if (activeCall != null) { activeCall.increaseSecondCallWithoutAnswerAndReleasedButtonTimes(); } } @Override public void onAnswerAndReleaseButtonEnabled() { DialerCall activeCall = CallList.getInstance().getActiveCall(); if (activeCall != null) { activeCall.increaseAnswerAndReleaseButtonDisplayedTimes(); } } @Override public void onCannedTextResponsesLoaded(DialerCall call) { if (isSmsResponseAllowed(call)) { answerScreen.setTextResponses(call.getCannedSmsResponses()); } } @Override public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) { InCallActivity activity = (InCallActivity) answerScreen.getAnswerScreenFragment().getActivity(); if (activity != null) { activity.updateWindowBackgroundColor(progress); } } private class AnswerOnDisconnected implements DialerCallListener { private final DialerCall disconnectingCall; AnswerOnDisconnected(DialerCall disconnectingCall) { this.disconnectingCall = disconnectingCall; } @Override public void onDialerCallDisconnect() { LogUtil.i( "AnswerScreenPresenter.AnswerOnDisconnected", "call disconnected, answering new call"); call.answer(); disconnectingCall.removeListener(this); } @Override public void onDialerCallUpdate() {} @Override public void onDialerCallChildNumberChange() {} @Override public void onDialerCallLastForwardedNumberChange() {} @Override public void onDialerCallUpgradeToVideo() {} @Override public void onDialerCallSessionModificationStateChange() {} @Override public void onWiFiToLteHandover() {} @Override public void onHandoverToWifiFailure() {} @Override public void onInternationalCallOnWifi() {} @Override public void onEnrichedCallSessionUpdate() {} } private boolean isSmsResponseAllowed(DialerCall call) { return UserManagerCompat.isUserUnlocked(context) && call.can(android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT); } private void addTimeoutCheck() { actionPerformedTimeMillis = SystemClock.elapsedRealtime(); if (answerScreen.getAnswerScreenFragment().isVisible()) { ThreadUtil.postDelayedOnUiThread( () -> { if (!answerScreen.getAnswerScreenFragment().isVisible()) { LogUtil.d( "AnswerScreenPresenter.addTimeoutCheck", "accept/reject call timed out, do nothing"); return; } LogUtil.i("AnswerScreenPresenter.addTimeoutCheck", "accept/reject call timed out"); // Force re-evaluate which fragment to show. InCallPresenter.getInstance().refreshUi(); }, ACCEPT_REJECT_CALL_TIME_OUT_IN_MILLIS); } } }