1/*
2 * Copyright (C) 2013 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
17package com.android.incallui;
18
19import android.content.Context;
20
21import com.android.dialer.compat.UserManagerCompat;
22import com.android.dialer.util.TelecomUtil;
23import com.android.incallui.InCallPresenter.InCallState;
24
25import java.util.List;
26
27/**
28 * Presenter for the Incoming call widget. The {@link AnswerPresenter} handles the logic during
29 * incoming calls. It is also in charge of responding to incoming calls, so there needs to be
30 * an instance alive so that it can receive onIncomingCall callbacks.
31 *
32 * An instance of {@link AnswerPresenter} is created by InCallPresenter at startup, registers
33 * for callbacks via InCallPresenter, and shows/hides the {@link AnswerFragment} via IncallActivity.
34 *
35 */
36public class AnswerPresenter extends Presenter<AnswerPresenter.AnswerUi>
37        implements CallList.CallUpdateListener, InCallPresenter.InCallUiListener,
38                InCallPresenter.IncomingCallListener,
39                CallList.Listener {
40
41    private static final String TAG = AnswerPresenter.class.getSimpleName();
42
43    private String mCallId;
44    private Call mCall = null;
45    private boolean mHasTextMessages = false;
46
47    @Override
48    public void onUiShowing(boolean showing) {
49        if (showing) {
50            CallList.getInstance().addListener(this);
51            final CallList calls = CallList.getInstance();
52            Call call;
53            call = calls.getIncomingCall();
54            if (call != null) {
55                processIncomingCall(call);
56            }
57            call = calls.getVideoUpgradeRequestCall();
58            Log.d(this, "getVideoUpgradeRequestCall call =" + call);
59            if (call != null) {
60                showAnswerUi(true);
61                processVideoUpgradeRequestCall(call);
62            }
63        } else {
64            CallList.getInstance().removeListener(this);
65            // This is necessary because the activity can be destroyed while an incoming call exists.
66            // This happens when back button is pressed while incoming call is still being shown.
67            if (mCallId != null) {
68                CallList.getInstance().removeCallUpdateListener(mCallId, this);
69            }
70        }
71    }
72
73    @Override
74    public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
75        Log.d(this, "onIncomingCall: " + this);
76        Call modifyCall = CallList.getInstance().getVideoUpgradeRequestCall();
77        if (modifyCall != null) {
78            showAnswerUi(false);
79            Log.d(this, "declining upgrade request id: ");
80            CallList.getInstance().removeCallUpdateListener(mCallId, this);
81            InCallPresenter.getInstance().declineUpgradeRequest();
82        }
83        if (!call.getId().equals(mCallId)) {
84            // A new call is coming in.
85            processIncomingCall(call);
86        }
87    }
88
89    @Override
90    public void onIncomingCall(Call call) {
91    }
92
93    @Override
94    public void onCallListChange(CallList list) {
95    }
96
97    @Override
98    public void onDisconnect(Call call) {
99        // no-op
100    }
101
102    public void onSessionModificationStateChange(int sessionModificationState) {
103        boolean isUpgradePending = sessionModificationState ==
104                Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
105
106        if (!isUpgradePending) {
107            // Stop listening for updates.
108            CallList.getInstance().removeCallUpdateListener(mCallId, this);
109            showAnswerUi(false);
110        }
111    }
112
113    @Override
114    public void onLastForwardedNumberChange() {
115        // no-op
116    }
117
118    @Override
119    public void onChildNumberChange() {
120        // no-op
121    }
122
123    private boolean isVideoUpgradePending(Call call) {
124        return call.getSessionModificationState()
125                == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
126    }
127
128    @Override
129    public void onUpgradeToVideo(Call call) {
130        Log.d(this, "onUpgradeToVideo: " + this + " call=" + call);
131        showAnswerUi(true);
132        boolean isUpgradePending = isVideoUpgradePending(call);
133        InCallPresenter inCallPresenter = InCallPresenter.getInstance();
134        if (isUpgradePending
135                && inCallPresenter.getInCallState() == InCallPresenter.InCallState.INCOMING) {
136            Log.d(this, "declining upgrade request");
137            //If there is incoming call reject upgrade request
138            inCallPresenter.declineUpgradeRequest(getUi().getContext());
139        } else if (isUpgradePending) {
140            Log.d(this, "process upgrade request as no MT call");
141            processVideoUpgradeRequestCall(call);
142        }
143    }
144
145    private void processIncomingCall(Call call) {
146        mCallId = call.getId();
147        mCall = call;
148
149        // Listen for call updates for the current call.
150        CallList.getInstance().addCallUpdateListener(mCallId, this);
151
152        Log.d(TAG, "Showing incoming for call id: " + mCallId + " " + this);
153        if (showAnswerUi(true)) {
154            final List<String> textMsgs = CallList.getInstance().getTextResponses(call.getId());
155            configureAnswerTargetsForSms(call, textMsgs);
156        }
157    }
158
159    private boolean showAnswerUi(boolean show) {
160        final InCallActivity activity = InCallPresenter.getInstance().getActivity();
161        if (activity != null) {
162            activity.showAnswerFragment(show);
163            if (getUi() != null) {
164                getUi().onShowAnswerUi(show);
165            }
166            return true;
167        } else {
168            return false;
169        }
170    }
171
172    private void processVideoUpgradeRequestCall(Call call) {
173        Log.d(this, " processVideoUpgradeRequestCall call=" + call);
174        mCallId = call.getId();
175        mCall = call;
176
177        // Listen for call updates for the current call.
178        CallList.getInstance().addCallUpdateListener(mCallId, this);
179
180        final int currentVideoState = call.getVideoState();
181        final int modifyToVideoState = call.getRequestedVideoState();
182
183        if (currentVideoState == modifyToVideoState) {
184            Log.w(this, "processVideoUpgradeRequestCall: Video states are same. Return.");
185            return;
186        }
187
188        AnswerUi ui = getUi();
189
190        if (ui == null) {
191            Log.e(this, "Ui is null. Can't process upgrade request");
192            return;
193        }
194        showAnswerUi(true);
195        ui.showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_ACCEPT_REJECT_REQUEST,
196                modifyToVideoState);
197    }
198
199    private boolean isEnabled(int videoState, int mask) {
200        return (videoState & mask) == mask;
201    }
202
203    @Override
204    public void onCallChanged(Call call) {
205        Log.d(this, "onCallStateChange() " + call + " " + this);
206        if (call.getState() != Call.State.INCOMING) {
207            boolean isUpgradePending = isVideoUpgradePending(call);
208            if (!isUpgradePending) {
209                // Stop listening for updates.
210                CallList.getInstance().removeCallUpdateListener(mCallId, this);
211            }
212
213            final Call incall = CallList.getInstance().getIncomingCall();
214            if (incall != null || isUpgradePending) {
215                showAnswerUi(true);
216            } else {
217                showAnswerUi(false);
218            }
219
220            mHasTextMessages = false;
221        } else if (!mHasTextMessages) {
222            final List<String> textMsgs = CallList.getInstance().getTextResponses(call.getId());
223            if (textMsgs != null) {
224                configureAnswerTargetsForSms(call, textMsgs);
225            }
226        }
227    }
228
229    public void onAnswer(int videoState, Context context) {
230        if (mCallId == null) {
231            return;
232        }
233
234        if (mCall.getSessionModificationState()
235                == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
236            Log.d(this, "onAnswer (upgradeCall) mCallId=" + mCallId + " videoState=" + videoState);
237            InCallPresenter.getInstance().acceptUpgradeRequest(videoState, context);
238        } else {
239            Log.d(this, "onAnswer (answerCall) mCallId=" + mCallId + " videoState=" + videoState);
240            TelecomAdapter.getInstance().answerCall(mCall.getId(), videoState);
241        }
242    }
243
244    /**
245     * TODO: We are using reject and decline interchangeably. We should settle on
246     * reject since it seems to be more prevalent.
247     */
248    public void onDecline(Context context) {
249        Log.d(this, "onDecline " + mCallId);
250        if (mCall.getSessionModificationState()
251                == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
252            InCallPresenter.getInstance().declineUpgradeRequest(context);
253        } else {
254            TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null);
255        }
256    }
257
258    public void onText() {
259        if (getUi() != null) {
260            TelecomUtil.silenceRinger(getUi().getContext());
261            getUi().showMessageDialog();
262        }
263    }
264
265    public void rejectCallWithMessage(String message) {
266        Log.d(this, "sendTextToDefaultActivity()...");
267        TelecomAdapter.getInstance().rejectCall(mCall.getId(), true, message);
268
269        onDismissDialog();
270    }
271
272    public void onDismissDialog() {
273        InCallPresenter.getInstance().onDismissDialog();
274    }
275
276    private void configureAnswerTargetsForSms(Call call, List<String> textMsgs) {
277        if (getUi() == null) {
278            return;
279        }
280        mHasTextMessages = textMsgs != null;
281        boolean withSms = UserManagerCompat.isUserUnlocked(getUi().getContext())
282                && call.can(android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT)
283                && mHasTextMessages;
284
285        // Only present the user with the option to answer as a video call if the incoming call is
286        // a bi-directional video call.
287        if (VideoUtils.isBidirectionalVideoCall(call)) {
288            if (withSms) {
289                getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_WITH_SMS);
290                getUi().configureMessageDialog(textMsgs);
291            } else {
292                getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_WITHOUT_SMS);
293            }
294        } else {
295            if (withSms) {
296                getUi().showTargets(AnswerFragment.TARGET_SET_FOR_AUDIO_WITH_SMS);
297                getUi().configureMessageDialog(textMsgs);
298            } else {
299                getUi().showTargets(AnswerFragment.TARGET_SET_FOR_AUDIO_WITHOUT_SMS);
300            }
301        }
302    }
303
304    interface AnswerUi extends Ui {
305        public void onShowAnswerUi(boolean shown);
306        public void showTargets(int targetSet);
307        public void showTargets(int targetSet, int videoState);
308        public void showMessageDialog();
309        public void configureMessageDialog(List<String> textResponses);
310        public Context getContext();
311    }
312}
313