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