blob: e11b376c11b017304d099d90a45c3108e509ee0d [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
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;
20import android.content.Intent;
21import android.graphics.Point;
22import android.os.Bundle;
23import android.os.Handler;
Eric Erfanian2ca43182017-08-31 06:57:16 -070024import android.os.Trace;
twyen8efb4952017-10-06 16:35:54 -070025import android.support.annotation.MainThread;
Eric Erfanianccca3152017-02-22 16:32:36 -080026import android.support.annotation.NonNull;
27import android.support.annotation.Nullable;
28import android.support.annotation.VisibleForTesting;
Eric Erfanian83b20212017-05-31 08:53:10 -070029import android.support.v4.os.UserManagerCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080030import android.telecom.Call.Details;
yueg77cb8e52017-10-27 16:42:51 -070031import android.telecom.CallAudioState;
Eric Erfanianccca3152017-02-22 16:32:36 -080032import android.telecom.DisconnectCause;
33import android.telecom.PhoneAccount;
34import android.telecom.PhoneAccountHandle;
35import android.telecom.TelecomManager;
36import android.telecom.VideoProfile;
37import android.telephony.PhoneStateListener;
38import android.telephony.TelephonyManager;
twyen8efb4952017-10-06 16:35:54 -070039import android.util.ArraySet;
Eric Erfanianccca3152017-02-22 16:32:36 -080040import android.view.Window;
41import android.view.WindowManager;
Eric Erfanianccca3152017-02-22 16:32:36 -080042import com.android.contacts.common.compat.CallCompat;
43import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
44import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
Eric Erfanian83b20212017-05-31 08:53:10 -070045import com.android.dialer.blocking.FilteredNumberCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080046import com.android.dialer.blocking.FilteredNumbersUtil;
twyen8efb4952017-10-06 16:35:54 -070047import com.android.dialer.common.Assert;
Eric Erfanianccca3152017-02-22 16:32:36 -080048import com.android.dialer.common.LogUtil;
zachh6a4cebd2017-10-24 17:10:06 -070049import com.android.dialer.common.concurrent.DialerExecutorComponent;
Eric Erfaniand8046e52017-04-06 09:41:50 -070050import com.android.dialer.enrichedcall.EnrichedCallComponent;
Eric Erfanian10b34a52017-05-04 08:23:17 -070051import com.android.dialer.location.GeoUtil;
Eric Erfanian8369df02017-05-03 10:27:13 -070052import com.android.dialer.logging.InteractionEvent;
Eric Erfanianccca3152017-02-22 16:32:36 -080053import com.android.dialer.logging.Logger;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070054import com.android.dialer.postcall.PostCall;
twyena4745bd2017-12-12 18:40:11 -080055import com.android.dialer.telecom.TelecomCallUtil;
Eric Erfanianccca3152017-02-22 16:32:36 -080056import com.android.dialer.telecom.TelecomUtil;
57import com.android.dialer.util.TouchPointManager;
58import com.android.incallui.InCallOrientationEventListener.ScreenOrientation;
59import com.android.incallui.answerproximitysensor.PseudoScreenState;
yueg77cb8e52017-10-27 16:42:51 -070060import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080061import com.android.incallui.call.CallList;
62import com.android.incallui.call.DialerCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080063import com.android.incallui.call.ExternalCallList;
Eric Erfanianccca3152017-02-22 16:32:36 -080064import com.android.incallui.call.TelecomAdapter;
Eric Erfanian2ca43182017-08-31 06:57:16 -070065import com.android.incallui.disconnectdialog.DisconnectMessage;
twyen8efb4952017-10-06 16:35:54 -070066import com.android.incallui.incalluilock.InCallUiLock;
Eric Erfanianccca3152017-02-22 16:32:36 -080067import com.android.incallui.latencyreport.LatencyReport;
68import com.android.incallui.legacyblocking.BlockedNumberContentObserver;
69import com.android.incallui.spam.SpamCallListListener;
erfaniand05d8992018-03-20 19:42:26 -070070import com.android.incallui.speakeasy.SpeakEasyCallManager;
Eric Erfanianccca3152017-02-22 16:32:36 -080071import com.android.incallui.videosurface.bindings.VideoSurfaceBindings;
72import com.android.incallui.videosurface.protocol.VideoSurfaceTexture;
Eric Erfanian90508232017-03-24 09:31:16 -070073import com.android.incallui.videotech.utils.VideoUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080074import java.util.Collections;
75import java.util.List;
76import java.util.Objects;
77import java.util.Set;
78import java.util.concurrent.ConcurrentHashMap;
79import java.util.concurrent.CopyOnWriteArrayList;
80import java.util.concurrent.atomic.AtomicBoolean;
81
82/**
83 * Takes updates from the CallList and notifies the InCallActivity (UI) of the changes. Responsible
84 * for starting the activity for a new call and finishing the activity when all calls are
85 * disconnected. Creates and manages the in-call state and provides a listener pattern for the
86 * presenters that want to listen in on the in-call state changes. TODO: This class has become more
87 * of a state machine at this point. Consider renaming.
88 */
yueg77cb8e52017-10-27 16:42:51 -070089public class InCallPresenter implements CallList.Listener, AudioModeProvider.AudioModeListener {
Eric Erfanian2ca43182017-08-31 06:57:16 -070090 private static final String PIXEL2017_SYSTEM_FEATURE =
91 "com.google.android.feature.PIXEL_2017_EXPERIENCE";
Eric Erfanianccca3152017-02-22 16:32:36 -080092
93 private static final long BLOCK_QUERY_TIMEOUT_MS = 1000;
94
95 private static final Bundle EMPTY_EXTRAS = new Bundle();
96
linyuh183cb712017-12-27 17:02:37 -080097 private static InCallPresenter inCallPresenter;
Eric Erfanianccca3152017-02-22 16:32:36 -080098
99 /**
100 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is load factor before
101 * resizing, 1 means we only expect a single thread to access the map so make only a single shard
102 */
linyuh183cb712017-12-27 17:02:37 -0800103 private final Set<InCallStateListener> listeners =
Eric Erfanianccca3152017-02-22 16:32:36 -0800104 Collections.newSetFromMap(new ConcurrentHashMap<InCallStateListener, Boolean>(8, 0.9f, 1));
105
linyuh183cb712017-12-27 17:02:37 -0800106 private final List<IncomingCallListener> incomingCallListeners = new CopyOnWriteArrayList<>();
107 private final Set<InCallDetailsListener> detailsListeners =
Eric Erfanianccca3152017-02-22 16:32:36 -0800108 Collections.newSetFromMap(new ConcurrentHashMap<InCallDetailsListener, Boolean>(8, 0.9f, 1));
linyuh183cb712017-12-27 17:02:37 -0800109 private final Set<CanAddCallListener> canAddCallListeners =
Eric Erfanianccca3152017-02-22 16:32:36 -0800110 Collections.newSetFromMap(new ConcurrentHashMap<CanAddCallListener, Boolean>(8, 0.9f, 1));
linyuh183cb712017-12-27 17:02:37 -0800111 private final Set<InCallUiListener> inCallUiListeners =
Eric Erfanianccca3152017-02-22 16:32:36 -0800112 Collections.newSetFromMap(new ConcurrentHashMap<InCallUiListener, Boolean>(8, 0.9f, 1));
linyuh183cb712017-12-27 17:02:37 -0800113 private final Set<InCallOrientationListener> orientationListeners =
Eric Erfanianccca3152017-02-22 16:32:36 -0800114 Collections.newSetFromMap(
115 new ConcurrentHashMap<InCallOrientationListener, Boolean>(8, 0.9f, 1));
linyuh183cb712017-12-27 17:02:37 -0800116 private final Set<InCallEventListener> inCallEventListeners =
Eric Erfanianccca3152017-02-22 16:32:36 -0800117 Collections.newSetFromMap(new ConcurrentHashMap<InCallEventListener, Boolean>(8, 0.9f, 1));
118
linyuh183cb712017-12-27 17:02:37 -0800119 private StatusBarNotifier statusBarNotifier;
120 private ExternalCallNotifier externalCallNotifier;
121 private ContactInfoCache contactInfoCache;
122 private Context context;
123 private final OnCheckBlockedListener onCheckBlockedListener =
Eric Erfanianccca3152017-02-22 16:32:36 -0800124 new OnCheckBlockedListener() {
125 @Override
126 public void onCheckComplete(final Integer id) {
127 if (id != null && id != FilteredNumberAsyncQueryHandler.INVALID_ID) {
128 // Silence the ringer now to prevent ringing and vibration before the call is
129 // terminated when Telecom attempts to add it.
linyuh183cb712017-12-27 17:02:37 -0800130 TelecomUtil.silenceRinger(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800131 }
132 }
133 };
linyuh183cb712017-12-27 17:02:37 -0800134 private CallList callList;
135 private ExternalCallList externalCallList;
136 private InCallActivity inCallActivity;
137 private ManageConferenceActivity manageConferenceActivity;
138 private final android.telecom.Call.Callback callCallback =
Eric Erfanianccca3152017-02-22 16:32:36 -0800139 new android.telecom.Call.Callback() {
140 @Override
141 public void onPostDialWait(
142 android.telecom.Call telecomCall, String remainingPostDialSequence) {
linyuh183cb712017-12-27 17:02:37 -0800143 final DialerCall call = callList.getDialerCallFromTelecomCall(telecomCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800144 if (call == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700145 LogUtil.w(
146 "InCallPresenter.onPostDialWait",
147 "DialerCall not found in call list: " + telecomCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800148 return;
149 }
150 onPostDialCharWait(call.getId(), remainingPostDialSequence);
151 }
152
153 @Override
154 public void onDetailsChanged(
155 android.telecom.Call telecomCall, android.telecom.Call.Details details) {
linyuh183cb712017-12-27 17:02:37 -0800156 final DialerCall call = callList.getDialerCallFromTelecomCall(telecomCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800157 if (call == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700158 LogUtil.w(
159 "InCallPresenter.onDetailsChanged",
160 "DialerCall not found in call list: " + telecomCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800161 return;
162 }
163
164 if (details.hasProperty(Details.PROPERTY_IS_EXTERNAL_CALL)
linyuh183cb712017-12-27 17:02:37 -0800165 && !externalCallList.isCallTracked(telecomCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800166
167 // A regular call became an external call so swap call lists.
Eric Erfanian2ca43182017-08-31 06:57:16 -0700168 LogUtil.i("InCallPresenter.onDetailsChanged", "Call became external: " + telecomCall);
linyuh183cb712017-12-27 17:02:37 -0800169 callList.onInternalCallMadeExternal(context, telecomCall);
170 externalCallList.onCallAdded(telecomCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800171 return;
172 }
173
linyuh183cb712017-12-27 17:02:37 -0800174 for (InCallDetailsListener listener : detailsListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800175 listener.onDetailsChanged(call, details);
176 }
177 }
178
179 @Override
180 public void onConferenceableCallsChanged(
181 android.telecom.Call telecomCall, List<android.telecom.Call> conferenceableCalls) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700182 LogUtil.i(
183 "InCallPresenter.onConferenceableCallsChanged",
184 "onConferenceableCallsChanged: " + telecomCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800185 onDetailsChanged(telecomCall, telecomCall.getDetails());
186 }
187 };
linyuh183cb712017-12-27 17:02:37 -0800188 private InCallState inCallState = InCallState.NO_CALLS;
189 private ProximitySensor proximitySensor;
190 private final PseudoScreenState pseudoScreenState = new PseudoScreenState();
191 private boolean serviceConnected;
192 private InCallCameraManager inCallCameraManager;
193 private FilteredNumberAsyncQueryHandler filteredQueryHandler;
194 private CallList.Listener spamCallListListener;
Eric Erfanianccca3152017-02-22 16:32:36 -0800195 /** Whether or not we are currently bound and waiting for Telecom to send us a new call. */
linyuh183cb712017-12-27 17:02:37 -0800196 private boolean boundAndWaitingForOutgoingCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800197 /** Determines if the InCall UI is in fullscreen mode or not. */
linyuh183cb712017-12-27 17:02:37 -0800198 private boolean isFullScreen = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800199
linyuh183cb712017-12-27 17:02:37 -0800200 private boolean screenTimeoutEnabled = true;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700201
linyuh183cb712017-12-27 17:02:37 -0800202 private PhoneStateListener phoneStateListener =
Eric Erfanianccca3152017-02-22 16:32:36 -0800203 new PhoneStateListener() {
204 @Override
205 public void onCallStateChanged(int state, String incomingNumber) {
206 if (state == TelephonyManager.CALL_STATE_RINGING) {
linyuh183cb712017-12-27 17:02:37 -0800207 if (FilteredNumbersUtil.hasRecentEmergencyCall(context)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800208 return;
209 }
210 // Check if the number is blocked, to silence the ringer.
linyuh183cb712017-12-27 17:02:37 -0800211 String countryIso = GeoUtil.getCurrentCountryIso(context);
212 filteredQueryHandler.isBlockedNumber(
213 onCheckBlockedListener, incomingNumber, countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800214 }
215 }
216 };
twyena4745bd2017-12-12 18:40:11 -0800217
Eric Erfanianccca3152017-02-22 16:32:36 -0800218 /** Whether or not InCallService is bound to Telecom. */
linyuh183cb712017-12-27 17:02:37 -0800219 private boolean serviceBound = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800220
221 /**
222 * When configuration changes Android kills the current activity and starts a new one. The flag is
223 * used to check if full clean up is necessary (activity is stopped and new activity won't be
224 * started), or if a new activity will be started right after the current one is destroyed, and
225 * therefore no need in release all resources.
226 */
linyuh183cb712017-12-27 17:02:37 -0800227 private boolean isChangingConfigurations = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800228
linyuh183cb712017-12-27 17:02:37 -0800229 private boolean awaitingCallListUpdate = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800230
linyuh183cb712017-12-27 17:02:37 -0800231 private ExternalCallList.ExternalCallListener externalCallListener =
Eric Erfanianccca3152017-02-22 16:32:36 -0800232 new ExternalCallList.ExternalCallListener() {
233
234 @Override
235 public void onExternalCallPulled(android.telecom.Call call) {
236 // Note: keep this code in sync with InCallPresenter#onCallAdded
237 LatencyReport latencyReport = new LatencyReport(call);
238 latencyReport.onCallBlockingDone();
239 // Note: External calls do not require spam checking.
linyuh183cb712017-12-27 17:02:37 -0800240 callList.onCallAdded(context, call, latencyReport);
241 call.registerCallback(callCallback);
Eric Erfanianccca3152017-02-22 16:32:36 -0800242 }
243
244 @Override
245 public void onExternalCallAdded(android.telecom.Call call) {
246 // No-op
247 }
248
249 @Override
250 public void onExternalCallRemoved(android.telecom.Call call) {
251 // No-op
252 }
253
254 @Override
255 public void onExternalCallUpdated(android.telecom.Call call) {
256 // No-op
257 }
258 };
259
linyuh183cb712017-12-27 17:02:37 -0800260 private ThemeColorManager themeColorManager;
261 private VideoSurfaceTexture localVideoSurfaceTexture;
262 private VideoSurfaceTexture remoteVideoSurfaceTexture;
Eric Erfanianccca3152017-02-22 16:32:36 -0800263
yueg7f5acbe2018-01-10 13:50:29 -0800264 private MotorolaInCallUiNotifier motorolaInCallUiNotifier;
265
erfaniand05d8992018-03-20 19:42:26 -0700266 private SpeakEasyCallManager speakEasyCallManager;
267
Eric Erfanian10b34a52017-05-04 08:23:17 -0700268 /** Inaccessible constructor. Must use getRunningInstance() to get this singleton. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800269 @VisibleForTesting
270 InCallPresenter() {}
271
272 public static synchronized InCallPresenter getInstance() {
linyuh183cb712017-12-27 17:02:37 -0800273 if (inCallPresenter == null) {
wangqic8cf79e2017-10-17 09:21:00 -0700274 Trace.beginSection("InCallPresenter.Constructor");
linyuh183cb712017-12-27 17:02:37 -0800275 inCallPresenter = new InCallPresenter();
wangqic8cf79e2017-10-17 09:21:00 -0700276 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800277 }
linyuh183cb712017-12-27 17:02:37 -0800278 return inCallPresenter;
Eric Erfanianccca3152017-02-22 16:32:36 -0800279 }
280
Eric Erfanian10b34a52017-05-04 08:23:17 -0700281 @VisibleForTesting
282 public static synchronized void setInstanceForTesting(InCallPresenter inCallPresenter) {
linyuh183cb712017-12-27 17:02:37 -0800283 InCallPresenter.inCallPresenter = inCallPresenter;
Eric Erfanian10b34a52017-05-04 08:23:17 -0700284 }
285
Eric Erfanianccca3152017-02-22 16:32:36 -0800286 /**
287 * Determines whether or not a call has no valid phone accounts that can be used to make the call
288 * with. Emergency calls do not require a phone account.
289 *
290 * @param call to check accounts for.
291 * @return {@code true} if the call has no call capable phone accounts set, {@code false} if the
292 * call contains a phone account that could be used to initiate it with, or is an emergency
293 * call.
294 */
295 public static boolean isCallWithNoValidAccounts(DialerCall call) {
296 if (call != null && !call.isEmergencyCall()) {
297 Bundle extras = call.getIntentExtras();
298
299 if (extras == null) {
300 extras = EMPTY_EXTRAS;
301 }
302
303 final List<PhoneAccountHandle> phoneAccountHandles =
304 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
305
306 if ((call.getAccountHandle() == null
307 && (phoneAccountHandles == null || phoneAccountHandles.isEmpty()))) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700308 LogUtil.i(
309 "InCallPresenter.isCallWithNoValidAccounts", "No valid accounts for call " + call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800310 return true;
311 }
312 }
313 return false;
314 }
315
316 public InCallState getInCallState() {
linyuh183cb712017-12-27 17:02:37 -0800317 return inCallState;
Eric Erfanianccca3152017-02-22 16:32:36 -0800318 }
319
320 public CallList getCallList() {
linyuh183cb712017-12-27 17:02:37 -0800321 return callList;
Eric Erfanianccca3152017-02-22 16:32:36 -0800322 }
323
324 public void setUp(
325 @NonNull Context context,
326 CallList callList,
327 ExternalCallList externalCallList,
328 StatusBarNotifier statusBarNotifier,
329 ExternalCallNotifier externalCallNotifier,
330 ContactInfoCache contactInfoCache,
Eric Erfanian83b20212017-05-31 08:53:10 -0700331 ProximitySensor proximitySensor,
erfaniand05d8992018-03-20 19:42:26 -0700332 FilteredNumberAsyncQueryHandler filteredNumberQueryHandler,
333 @NonNull SpeakEasyCallManager speakEasyCallManager) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700334 Trace.beginSection("InCallPresenter.setUp");
linyuh183cb712017-12-27 17:02:37 -0800335 if (serviceConnected) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700336 LogUtil.i("InCallPresenter.setUp", "New service connection replacing existing one.");
linyuh183cb712017-12-27 17:02:37 -0800337 if (context != this.context || callList != this.callList) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800338 throw new IllegalStateException();
339 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700340 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800341 return;
342 }
343
344 Objects.requireNonNull(context);
linyuh183cb712017-12-27 17:02:37 -0800345 this.context = context;
Eric Erfanianccca3152017-02-22 16:32:36 -0800346
linyuh183cb712017-12-27 17:02:37 -0800347 this.contactInfoCache = contactInfoCache;
Eric Erfanianccca3152017-02-22 16:32:36 -0800348
linyuh183cb712017-12-27 17:02:37 -0800349 this.statusBarNotifier = statusBarNotifier;
350 this.externalCallNotifier = externalCallNotifier;
351 addListener(this.statusBarNotifier);
352 EnrichedCallComponent.get(this.context)
Eric Erfaniand8046e52017-04-06 09:41:50 -0700353 .getEnrichedCallManager()
linyuh183cb712017-12-27 17:02:37 -0800354 .registerStateChangedListener(this.statusBarNotifier);
Eric Erfanianccca3152017-02-22 16:32:36 -0800355
linyuh183cb712017-12-27 17:02:37 -0800356 this.proximitySensor = proximitySensor;
357 addListener(this.proximitySensor);
Eric Erfanianccca3152017-02-22 16:32:36 -0800358
linyuh183cb712017-12-27 17:02:37 -0800359 if (themeColorManager == null) {
360 themeColorManager =
361 new ThemeColorManager(new InCallUIMaterialColorMapUtils(this.context.getResources()));
wangqi8d662ca2017-10-26 11:27:19 -0700362 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800363
linyuh183cb712017-12-27 17:02:37 -0800364 this.callList = callList;
365 this.externalCallList = externalCallList;
366 externalCallList.addExternalCallListener(this.externalCallNotifier);
367 externalCallList.addExternalCallListener(externalCallListener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800368
369 // This only gets called by the service so this is okay.
linyuh183cb712017-12-27 17:02:37 -0800370 serviceConnected = true;
Eric Erfanianccca3152017-02-22 16:32:36 -0800371
372 // The final thing we do in this set up is add ourselves as a listener to CallList. This
373 // will kick off an update and the whole process can start.
linyuh183cb712017-12-27 17:02:37 -0800374 this.callList.addListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800375
376 // Create spam call list listener and add it to the list of listeners
linyuh183cb712017-12-27 17:02:37 -0800377 spamCallListListener =
zachh6a4cebd2017-10-24 17:10:06 -0700378 new SpamCallListListener(
379 context, DialerExecutorComponent.get(context).dialerExecutorFactory());
linyuh183cb712017-12-27 17:02:37 -0800380 this.callList.addListener(spamCallListListener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800381
382 VideoPauseController.getInstance().setUp(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800383
linyuh183cb712017-12-27 17:02:37 -0800384 filteredQueryHandler = filteredNumberQueryHandler;
erfaniand05d8992018-03-20 19:42:26 -0700385 this.speakEasyCallManager = speakEasyCallManager;
linyuh183cb712017-12-27 17:02:37 -0800386 this.context
Eric Erfanianccca3152017-02-22 16:32:36 -0800387 .getSystemService(TelephonyManager.class)
linyuh183cb712017-12-27 17:02:37 -0800388 .listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
Eric Erfanianccca3152017-02-22 16:32:36 -0800389
yueg77cb8e52017-10-27 16:42:51 -0700390 AudioModeProvider.getInstance().addListener(this);
391
yueg7f5acbe2018-01-10 13:50:29 -0800392 if (motorolaInCallUiNotifier == null) {
393 // Add listener to notify Telephony process when the incoming call screen is started or
394 // finished. This is for hiding USSD dialog because the incoming call screen should have
395 // higher precedence over this dialog.
396 motorolaInCallUiNotifier = new MotorolaInCallUiNotifier(context);
397 addInCallUiListener(motorolaInCallUiNotifier);
398 addListener(motorolaInCallUiNotifier);
399 }
400
Eric Erfanian2ca43182017-08-31 06:57:16 -0700401 LogUtil.d("InCallPresenter.setUp", "Finished InCallPresenter.setUp");
402 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800403 }
404
405 /**
406 * Called when the telephony service has disconnected from us. This will happen when there are no
407 * more active calls. However, we may still want to continue showing the UI for certain cases like
408 * showing "Call Ended". What we really want is to wait for the activity and the service to both
409 * disconnect before we tear things down. This method sets a serviceConnected boolean and calls a
410 * secondary method that performs the aforementioned logic.
411 */
412 public void tearDown() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700413 LogUtil.d("InCallPresenter.tearDown", "tearDown");
linyuh183cb712017-12-27 17:02:37 -0800414 callList.clearOnDisconnect();
Eric Erfanianccca3152017-02-22 16:32:36 -0800415
linyuh183cb712017-12-27 17:02:37 -0800416 serviceConnected = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800417
linyuh183cb712017-12-27 17:02:37 -0800418 context
Eric Erfanianccca3152017-02-22 16:32:36 -0800419 .getSystemService(TelephonyManager.class)
linyuh183cb712017-12-27 17:02:37 -0800420 .listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
Eric Erfanianccca3152017-02-22 16:32:36 -0800421
422 attemptCleanup();
423 VideoPauseController.getInstance().tearDown();
yueg77cb8e52017-10-27 16:42:51 -0700424 AudioModeProvider.getInstance().removeListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800425 }
426
427 private void attemptFinishActivity() {
linyuh183cb712017-12-27 17:02:37 -0800428 screenTimeoutEnabled = true;
429 final boolean doFinish = (inCallActivity != null && isActivityStarted());
Eric Erfanian2ca43182017-08-31 06:57:16 -0700430 LogUtil.i("InCallPresenter.attemptFinishActivity", "Hide in call UI: " + doFinish);
Eric Erfanianccca3152017-02-22 16:32:36 -0800431 if (doFinish) {
linyuh183cb712017-12-27 17:02:37 -0800432 inCallActivity.setExcludeFromRecents(true);
433 inCallActivity.finish();
Eric Erfanianccca3152017-02-22 16:32:36 -0800434 }
435 }
436
437 /**
438 * Called when the UI ends. Attempts to tear down everything if necessary. See {@link #tearDown()}
439 * for more insight on the tear-down process.
440 */
441 public void unsetActivity(InCallActivity inCallActivity) {
442 if (inCallActivity == null) {
443 throw new IllegalArgumentException("unregisterActivity cannot be called with null");
444 }
linyuh183cb712017-12-27 17:02:37 -0800445 if (this.inCallActivity == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700446 LogUtil.i(
447 "InCallPresenter.unsetActivity", "No InCallActivity currently set, no need to unset.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800448 return;
449 }
linyuh183cb712017-12-27 17:02:37 -0800450 if (this.inCallActivity != inCallActivity) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700451 LogUtil.w(
452 "InCallPresenter.unsetActivity",
Eric Erfanianccca3152017-02-22 16:32:36 -0800453 "Second instance of InCallActivity is trying to unregister when another"
454 + " instance is active. Ignoring.");
455 return;
456 }
457 updateActivity(null);
458 }
459
460 /**
461 * Updates the current instance of {@link InCallActivity} with the provided one. If a {@code null}
462 * activity is provided, it means that the activity was finished and we should attempt to cleanup.
463 */
464 private void updateActivity(InCallActivity inCallActivity) {
wangqi9982f0d2017-10-11 17:46:07 -0700465 Trace.beginSection("InCallPresenter.updateActivity");
Eric Erfanianccca3152017-02-22 16:32:36 -0800466 boolean updateListeners = false;
467 boolean doAttemptCleanup = false;
468
469 if (inCallActivity != null) {
linyuh183cb712017-12-27 17:02:37 -0800470 if (this.inCallActivity == null) {
471 context = inCallActivity.getApplicationContext();
Eric Erfanianccca3152017-02-22 16:32:36 -0800472 updateListeners = true;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700473 LogUtil.i("InCallPresenter.updateActivity", "UI Initialized");
Eric Erfanianccca3152017-02-22 16:32:36 -0800474 } else {
475 // since setActivity is called onStart(), it can be called multiple times.
476 // This is fine and ignorable, but we do not want to update the world every time
477 // this happens (like going to/from background) so we do not set updateListeners.
478 }
479
linyuh183cb712017-12-27 17:02:37 -0800480 this.inCallActivity = inCallActivity;
481 this.inCallActivity.setExcludeFromRecents(false);
erfaniand05d8992018-03-20 19:42:26 -0700482 this.inCallActivity.setSpeakEasyCallManager(this.speakEasyCallManager);
Eric Erfanianccca3152017-02-22 16:32:36 -0800483
484 // By the time the UI finally comes up, the call may already be disconnected.
485 // If that's the case, we may need to show an error dialog.
linyuh183cb712017-12-27 17:02:37 -0800486 if (callList != null && callList.getDisconnectedCall() != null) {
487 showDialogOrToastForDisconnectedCall(callList.getDisconnectedCall());
Eric Erfanianccca3152017-02-22 16:32:36 -0800488 }
489
490 // When the UI comes up, we need to first check the in-call state.
491 // If we are showing NO_CALLS, that means that a call probably connected and
492 // then immediately disconnected before the UI was able to come up.
493 // If we dont have any calls, start tearing down the UI instead.
494 // NOTE: This code relies on {@link #mInCallActivity} being set so we run it after
495 // it has been set.
linyuh183cb712017-12-27 17:02:37 -0800496 if (inCallState == InCallState.NO_CALLS) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700497 LogUtil.i("InCallPresenter.updateActivity", "UI Initialized, but no calls left. Shut down");
Eric Erfanianccca3152017-02-22 16:32:36 -0800498 attemptFinishActivity();
wangqi9982f0d2017-10-11 17:46:07 -0700499 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800500 return;
501 }
502 } else {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700503 LogUtil.i("InCallPresenter.updateActivity", "UI Destroyed");
Eric Erfanianccca3152017-02-22 16:32:36 -0800504 updateListeners = true;
linyuh183cb712017-12-27 17:02:37 -0800505 this.inCallActivity = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800506
507 // We attempt cleanup for the destroy case but only after we recalculate the state
508 // to see if we need to come back up or stay shut down. This is why we do the
509 // cleanup after the call to onCallListChange() instead of directly here.
510 doAttemptCleanup = true;
511 }
512
513 // Messages can come from the telephony layer while the activity is coming up
514 // and while the activity is going down. So in both cases we need to recalculate what
515 // state we should be in after they complete.
516 // Examples: (1) A new incoming call could come in and then get disconnected before
517 // the activity is created.
518 // (2) All calls could disconnect and then get a new incoming call before the
519 // activity is destroyed.
520 //
Eric Erfanian938468d2017-10-24 14:05:52 -0700521 // a bug - We previously had a check for mServiceConnected here as well, but there are
Eric Erfanianccca3152017-02-22 16:32:36 -0800522 // cases where we need to recalculate the current state even if the service in not
523 // connected. In particular the case where startOrFinish() is called while the app is
524 // already finish()ing. In that case, we skip updating the state with the knowledge that
525 // we will check again once the activity has finished. That means we have to recalculate the
526 // state here even if the service is disconnected since we may not have finished a state
527 // transition while finish()ing.
528 if (updateListeners) {
linyuh183cb712017-12-27 17:02:37 -0800529 onCallListChange(callList);
Eric Erfanianccca3152017-02-22 16:32:36 -0800530 }
531
532 if (doAttemptCleanup) {
533 attemptCleanup();
534 }
wangqi9982f0d2017-10-11 17:46:07 -0700535 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800536 }
537
538 public void setManageConferenceActivity(
539 @Nullable ManageConferenceActivity manageConferenceActivity) {
linyuh183cb712017-12-27 17:02:37 -0800540 this.manageConferenceActivity = manageConferenceActivity;
Eric Erfanianccca3152017-02-22 16:32:36 -0800541 }
542
543 public void onBringToForeground(boolean showDialpad) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700544 LogUtil.i("InCallPresenter.onBringToForeground", "Bringing UI to foreground.");
Eric Erfanianccca3152017-02-22 16:32:36 -0800545 bringToForeground(showDialpad);
546 }
547
548 public void onCallAdded(final android.telecom.Call call) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700549 Trace.beginSection("InCallPresenter.onCallAdded");
Eric Erfanianccca3152017-02-22 16:32:36 -0800550 LatencyReport latencyReport = new LatencyReport(call);
551 if (shouldAttemptBlocking(call)) {
552 maybeBlockCall(call, latencyReport);
553 } else {
554 if (call.getDetails().hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
linyuh183cb712017-12-27 17:02:37 -0800555 externalCallList.onCallAdded(call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800556 } else {
557 latencyReport.onCallBlockingDone();
linyuh183cb712017-12-27 17:02:37 -0800558 callList.onCallAdded(context, call, latencyReport);
Eric Erfanianccca3152017-02-22 16:32:36 -0800559 }
560 }
561
562 // Since a call has been added we are no longer waiting for Telecom to send us a call.
563 setBoundAndWaitingForOutgoingCall(false, null);
linyuh183cb712017-12-27 17:02:37 -0800564 call.registerCallback(callCallback);
zachh78e54ac2017-12-05 16:38:35 -0800565 // TODO(maxwelb): Return the future in recordPhoneLookupInfo and propagate.
linyuh183cb712017-12-27 17:02:37 -0800566 PhoneLookupHistoryRecorder.recordPhoneLookupInfo(context.getApplicationContext(), call);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700567 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800568 }
569
570 private boolean shouldAttemptBlocking(android.telecom.Call call) {
571 if (call.getState() != android.telecom.Call.STATE_RINGING) {
572 return false;
573 }
linyuh183cb712017-12-27 17:02:37 -0800574 if (!UserManagerCompat.isUserUnlocked(context)) {
Eric Erfanian83b20212017-05-31 08:53:10 -0700575 LogUtil.i(
576 "InCallPresenter.shouldAttemptBlocking",
577 "not attempting to block incoming call because user is locked");
578 return false;
579 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800580 if (TelecomCallUtil.isEmergencyCall(call)) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700581 LogUtil.i(
582 "InCallPresenter.shouldAttemptBlocking",
583 "Not attempting to block incoming emergency call");
Eric Erfanianccca3152017-02-22 16:32:36 -0800584 return false;
585 }
linyuh183cb712017-12-27 17:02:37 -0800586 if (FilteredNumbersUtil.hasRecentEmergencyCall(context)) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700587 LogUtil.i(
588 "InCallPresenter.shouldAttemptBlocking",
589 "Not attempting to block incoming call due to recent emergency call");
Eric Erfanianccca3152017-02-22 16:32:36 -0800590 return false;
591 }
592 if (call.getDetails().hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
593 return false;
594 }
linyuh183cb712017-12-27 17:02:37 -0800595 if (FilteredNumberCompat.useNewFiltering(context)) {
Eric Erfanian83b20212017-05-31 08:53:10 -0700596 LogUtil.i(
597 "InCallPresenter.shouldAttemptBlocking",
598 "not attempting to block incoming call because framework blocking is in use");
599 return false;
600 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800601 return true;
602 }
603
604 /**
605 * Checks whether a call should be blocked, and blocks it if so. Otherwise, it adds the call to
606 * the CallList so it can proceed as normal. There is a timeout, so if the function for checking
607 * whether a function is blocked does not return in a reasonable time, we proceed with adding the
608 * call anyways.
609 */
610 private void maybeBlockCall(final android.telecom.Call call, final LatencyReport latencyReport) {
linyuh183cb712017-12-27 17:02:37 -0800611 final String countryIso = GeoUtil.getCurrentCountryIso(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800612 final String number = TelecomCallUtil.getNumber(call);
613 final long timeAdded = System.currentTimeMillis();
614
615 // Though AtomicBoolean's can be scary, don't fear, as in this case it is only used on the
616 // main UI thread. It is needed so we can change its value within different scopes, since
617 // that cannot be done with a final boolean.
618 final AtomicBoolean hasTimedOut = new AtomicBoolean(false);
619
620 final Handler handler = new Handler();
621
622 // Proceed if the query is slow; the call may still be blocked after the query returns.
623 final Runnable runnable =
624 new Runnable() {
625 @Override
626 public void run() {
627 hasTimedOut.set(true);
628 latencyReport.onCallBlockingDone();
linyuh183cb712017-12-27 17:02:37 -0800629 callList.onCallAdded(context, call, latencyReport);
Eric Erfanianccca3152017-02-22 16:32:36 -0800630 }
631 };
632 handler.postDelayed(runnable, BLOCK_QUERY_TIMEOUT_MS);
633
634 OnCheckBlockedListener onCheckBlockedListener =
635 new OnCheckBlockedListener() {
636 @Override
637 public void onCheckComplete(final Integer id) {
638 if (isReadyForTearDown()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700639 LogUtil.i("InCallPresenter.onCheckComplete", "torn down, not adding call");
Eric Erfanianccca3152017-02-22 16:32:36 -0800640 return;
641 }
642 if (!hasTimedOut.get()) {
643 handler.removeCallbacks(runnable);
644 }
645 if (id == null) {
646 if (!hasTimedOut.get()) {
647 latencyReport.onCallBlockingDone();
linyuh183cb712017-12-27 17:02:37 -0800648 callList.onCallAdded(context, call, latencyReport);
Eric Erfanianccca3152017-02-22 16:32:36 -0800649 }
650 } else if (id == FilteredNumberAsyncQueryHandler.INVALID_ID) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700651 LogUtil.d(
652 "InCallPresenter.onCheckComplete", "invalid number, skipping block checking");
Eric Erfanianccca3152017-02-22 16:32:36 -0800653 if (!hasTimedOut.get()) {
654 handler.removeCallbacks(runnable);
655
656 latencyReport.onCallBlockingDone();
linyuh183cb712017-12-27 17:02:37 -0800657 callList.onCallAdded(context, call, latencyReport);
Eric Erfanianccca3152017-02-22 16:32:36 -0800658 }
659 } else {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700660 LogUtil.i(
661 "InCallPresenter.onCheckComplete", "Rejecting incoming call from blocked number");
Eric Erfanianccca3152017-02-22 16:32:36 -0800662 call.reject(false, null);
linyuh183cb712017-12-27 17:02:37 -0800663 Logger.get(context).logInteraction(InteractionEvent.Type.CALL_BLOCKED);
Eric Erfanianccca3152017-02-22 16:32:36 -0800664
665 /*
666 * If mContext is null, then the InCallPresenter was torn down before the
667 * block check had a chance to complete. The context is no longer valid, so
668 * don't attempt to remove the call log entry.
669 */
linyuh183cb712017-12-27 17:02:37 -0800670 if (context == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800671 return;
672 }
673 // Register observer to update the call log.
674 // BlockedNumberContentObserver will unregister after successful log or timeout.
675 BlockedNumberContentObserver contentObserver =
linyuh183cb712017-12-27 17:02:37 -0800676 new BlockedNumberContentObserver(context, new Handler(), number, timeAdded);
Eric Erfanianccca3152017-02-22 16:32:36 -0800677 contentObserver.register();
678 }
679 }
680 };
681
linyuh183cb712017-12-27 17:02:37 -0800682 filteredQueryHandler.isBlockedNumber(onCheckBlockedListener, number, countryIso);
Eric Erfanianccca3152017-02-22 16:32:36 -0800683 }
684
685 public void onCallRemoved(android.telecom.Call call) {
686 if (call.getDetails().hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
linyuh183cb712017-12-27 17:02:37 -0800687 externalCallList.onCallRemoved(call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800688 } else {
linyuh183cb712017-12-27 17:02:37 -0800689 callList.onCallRemoved(context, call);
690 call.unregisterCallback(callCallback);
Eric Erfanianccca3152017-02-22 16:32:36 -0800691 }
692 }
693
694 public void onCanAddCallChanged(boolean canAddCall) {
linyuh183cb712017-12-27 17:02:37 -0800695 for (CanAddCallListener listener : canAddCallListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800696 listener.onCanAddCallChanged(canAddCall);
697 }
698 }
699
700 @Override
701 public void onWiFiToLteHandover(DialerCall call) {
linyuh183cb712017-12-27 17:02:37 -0800702 if (inCallActivity != null) {
703 inCallActivity.showToastForWiFiToLteHandover(call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800704 }
705 }
706
707 @Override
708 public void onHandoverToWifiFailed(DialerCall call) {
linyuh183cb712017-12-27 17:02:37 -0800709 if (inCallActivity != null) {
710 inCallActivity.showDialogOrToastForWifiHandoverFailure(call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800711 }
712 }
713
Eric Erfanianc857f902017-05-15 14:05:33 -0700714 @Override
715 public void onInternationalCallOnWifi(@NonNull DialerCall call) {
716 LogUtil.enterBlock("InCallPresenter.onInternationalCallOnWifi");
linyuh183cb712017-12-27 17:02:37 -0800717 if (inCallActivity != null) {
718 inCallActivity.showDialogForInternationalCallOnWifi(call);
Eric Erfanianc857f902017-05-15 14:05:33 -0700719 }
720 }
721
Eric Erfanianccca3152017-02-22 16:32:36 -0800722 /**
723 * Called when there is a change to the call list. Sets the In-Call state for the entire in-call
724 * app based on the information it gets from CallList. Dispatches the in-call state to all
725 * listeners. Can trigger the creation or destruction of the UI based on the states that is
726 * calculates.
727 */
728 @Override
729 public void onCallListChange(CallList callList) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700730 Trace.beginSection("InCallPresenter.onCallListChange");
linyuh183cb712017-12-27 17:02:37 -0800731 if (inCallActivity != null && inCallActivity.isInCallScreenAnimating()) {
732 awaitingCallListUpdate = true;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700733 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800734 return;
735 }
736 if (callList == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700737 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800738 return;
739 }
740
linyuh183cb712017-12-27 17:02:37 -0800741 awaitingCallListUpdate = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800742
743 InCallState newState = getPotentialStateFromCallList(callList);
linyuh183cb712017-12-27 17:02:37 -0800744 InCallState oldState = inCallState;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700745 LogUtil.d(
746 "InCallPresenter.onCallListChange",
747 "onCallListChange oldState= " + oldState + " newState=" + newState);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700748
749 // If the user placed a call and was asked to choose the account, but then pressed "Home", the
750 // incall activity for that call will still exist (even if it's not visible). In the case of
751 // an incoming call in that situation, just disconnect that "waiting for account" call and
752 // dismiss the dialog. The same activity will be reused to handle the new incoming call. See
Eric Erfanian938468d2017-10-24 14:05:52 -0700753 // a bug for more details.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700754 DialerCall waitingForAccountCall;
755 if (newState == InCallState.INCOMING
756 && (waitingForAccountCall = callList.getWaitingForAccountCall()) != null) {
757 waitingForAccountCall.disconnect();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700758 // The InCallActivity might be destroyed or not started yet at this point.
759 if (isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -0800760 inCallActivity.dismissPendingDialogs();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700761 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700762 }
763
Eric Erfanianccca3152017-02-22 16:32:36 -0800764 newState = startOrFinishUi(newState);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700765 LogUtil.d(
766 "InCallPresenter.onCallListChange", "onCallListChange newState changed to " + newState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800767
768 // Set the new state before announcing it to the world
Eric Erfanian2ca43182017-08-31 06:57:16 -0700769 LogUtil.i(
770 "InCallPresenter.onCallListChange",
771 "Phone switching state: " + oldState + " -> " + newState);
linyuh183cb712017-12-27 17:02:37 -0800772 inCallState = newState;
Eric Erfanianccca3152017-02-22 16:32:36 -0800773
yuegb9103042018-03-30 12:12:25 -0700774 // Foreground call changed
775 DialerCall primary = null;
776 if (newState == InCallState.INCOMING) {
777 primary = callList.getIncomingCall();
778 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
779 primary = callList.getOutgoingCall();
780 if (primary == null) {
781 primary = callList.getPendingOutgoingCall();
782 }
783 } else if (newState == InCallState.INCALL) {
784 primary = getCallToDisplay(callList, null, false);
785 }
786 if (primary != null) {
787 onForegroundCallChanged(primary);
788 }
789
Eric Erfanianccca3152017-02-22 16:32:36 -0800790 // notify listeners of new state
linyuh183cb712017-12-27 17:02:37 -0800791 for (InCallStateListener listener : listeners) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700792 LogUtil.d(
793 "InCallPresenter.onCallListChange",
linyuh183cb712017-12-27 17:02:37 -0800794 "Notify " + listener + " of state " + inCallState.toString());
795 listener.onStateChange(oldState, inCallState, callList);
Eric Erfanianccca3152017-02-22 16:32:36 -0800796 }
797
798 if (isActivityStarted()) {
799 final boolean hasCall =
800 callList.getActiveOrBackgroundCall() != null || callList.getOutgoingCall() != null;
linyuh183cb712017-12-27 17:02:37 -0800801 inCallActivity.dismissKeyguard(hasCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800802 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700803 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800804 }
805
yuegb9103042018-03-30 12:12:25 -0700806 /**
807 * Get the highest priority call to display. Goes through the calls and chooses which to return
808 * based on priority of which type of call to display to the user. Callers can use the "ignore"
809 * feature to get the second best call by passing a previously found primary call as ignore.
810 *
811 * @param ignore A call to ignore if found.
812 */
813 static DialerCall getCallToDisplay(
814 CallList callList, DialerCall ignore, boolean skipDisconnected) {
815 // Active calls come second. An active call always gets precedent.
816 DialerCall retval = callList.getActiveCall();
817 if (retval != null && retval != ignore) {
818 return retval;
819 }
820
821 // Sometimes there is intemediate state that two calls are in active even one is about
822 // to be on hold.
823 retval = callList.getSecondActiveCall();
824 if (retval != null && retval != ignore) {
825 return retval;
826 }
827
828 // Disconnected calls get primary position if there are no active calls
829 // to let user know quickly what call has disconnected. Disconnected
830 // calls are very short lived.
831 if (!skipDisconnected) {
832 retval = callList.getDisconnectingCall();
833 if (retval != null && retval != ignore) {
834 return retval;
835 }
836 retval = callList.getDisconnectedCall();
837 if (retval != null && retval != ignore) {
838 return retval;
839 }
840 }
841
842 // Then we go to background call (calls on hold)
843 retval = callList.getBackgroundCall();
844 if (retval != null && retval != ignore) {
845 return retval;
846 }
847
848 // Lastly, we go to a second background call.
849 retval = callList.getSecondBackgroundCall();
850
851 return retval;
852 }
853
Eric Erfanianccca3152017-02-22 16:32:36 -0800854 /** Called when there is a new incoming call. */
855 @Override
856 public void onIncomingCall(DialerCall call) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700857 Trace.beginSection("InCallPresenter.onIncomingCall");
Eric Erfanianccca3152017-02-22 16:32:36 -0800858 InCallState newState = startOrFinishUi(InCallState.INCOMING);
linyuh183cb712017-12-27 17:02:37 -0800859 InCallState oldState = inCallState;
Eric Erfanianccca3152017-02-22 16:32:36 -0800860
Eric Erfanian2ca43182017-08-31 06:57:16 -0700861 LogUtil.i(
862 "InCallPresenter.onIncomingCall", "Phone switching state: " + oldState + " -> " + newState);
linyuh183cb712017-12-27 17:02:37 -0800863 inCallState = newState;
Eric Erfanianccca3152017-02-22 16:32:36 -0800864
wangqicf61ca02017-08-31 15:32:55 -0700865 Trace.beginSection("listener.onIncomingCall");
linyuh183cb712017-12-27 17:02:37 -0800866 for (IncomingCallListener listener : incomingCallListeners) {
867 listener.onIncomingCall(oldState, inCallState, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800868 }
wangqicf61ca02017-08-31 15:32:55 -0700869 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800870
wangqicf61ca02017-08-31 15:32:55 -0700871 Trace.beginSection("onPrimaryCallStateChanged");
linyuh183cb712017-12-27 17:02:37 -0800872 if (inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800873 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -0800874 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800875 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700876 Trace.endSection();
wangqicf61ca02017-08-31 15:32:55 -0700877 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800878 }
879
880 @Override
881 public void onUpgradeToVideo(DialerCall call) {
Eric Erfanian90508232017-03-24 09:31:16 -0700882 if (VideoUtils.hasReceivedVideoUpgradeRequest(call.getVideoTech().getSessionModificationState())
linyuh183cb712017-12-27 17:02:37 -0800883 && inCallState == InCallPresenter.InCallState.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800884 LogUtil.i(
885 "InCallPresenter.onUpgradeToVideo",
886 "rejecting upgrade request due to existing incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700887 call.getVideoTech().declineVideoRequest();
Eric Erfanianccca3152017-02-22 16:32:36 -0800888 }
889
linyuh183cb712017-12-27 17:02:37 -0800890 if (inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800891 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -0800892 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800893 }
894 }
895
896 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700897 public void onSessionModificationStateChange(DialerCall call) {
898 int newState = call.getVideoTech().getSessionModificationState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800899 LogUtil.i("InCallPresenter.onSessionModificationStateChange", "state: %d", newState);
linyuh183cb712017-12-27 17:02:37 -0800900 if (proximitySensor == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800901 LogUtil.i("InCallPresenter.onSessionModificationStateChange", "proximitySensor is null");
902 return;
903 }
linyuh183cb712017-12-27 17:02:37 -0800904 proximitySensor.setIsAttemptingVideoCall(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700905 call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest());
linyuh183cb712017-12-27 17:02:37 -0800906 if (inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800907 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -0800908 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800909 }
910 }
911
912 /**
913 * Called when a call becomes disconnected. Called everytime an existing call changes from being
914 * connected (incoming/outgoing/active) to disconnected.
915 */
916 @Override
917 public void onDisconnect(DialerCall call) {
linyuh7b86f562017-11-16 11:24:09 -0800918 showDialogOrToastForDisconnectedCall(call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800919
920 // We need to do the run the same code as onCallListChange.
linyuh183cb712017-12-27 17:02:37 -0800921 onCallListChange(callList);
Eric Erfanianccca3152017-02-22 16:32:36 -0800922
923 if (isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -0800924 inCallActivity.dismissKeyguard(false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800925 }
926
927 if (call.isEmergencyCall()) {
linyuh183cb712017-12-27 17:02:37 -0800928 FilteredNumbersUtil.recordLastEmergencyCallTime(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800929 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800930
linyuh183cb712017-12-27 17:02:37 -0800931 if (!callList.hasLiveCall()
Eric Erfanianfc37b022017-03-21 10:11:17 -0700932 && !call.getLogState().isIncoming
Eric Erfanian10b34a52017-05-04 08:23:17 -0700933 && !isSecretCode(call.getNumber())
wangqi9982f0d2017-10-11 17:46:07 -0700934 && !call.isVoiceMailNumber()) {
linyuh183cb712017-12-27 17:02:37 -0800935 PostCall.onCallDisconnected(context, call.getNumber(), call.getConnectTimeMillis());
Eric Erfanianccca3152017-02-22 16:32:36 -0800936 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800937 }
938
Eric Erfanian10b34a52017-05-04 08:23:17 -0700939 private boolean isSecretCode(@Nullable String number) {
940 return number != null
941 && (number.length() <= 8 || number.startsWith("*#*#") || number.endsWith("#*#*"));
942 }
943
Eric Erfanianccca3152017-02-22 16:32:36 -0800944 /** Given the call list, return the state in which the in-call screen should be. */
945 public InCallState getPotentialStateFromCallList(CallList callList) {
946
947 InCallState newState = InCallState.NO_CALLS;
948
949 if (callList == null) {
950 return newState;
951 }
952 if (callList.getIncomingCall() != null) {
953 newState = InCallState.INCOMING;
954 } else if (callList.getWaitingForAccountCall() != null) {
955 newState = InCallState.WAITING_FOR_ACCOUNT;
956 } else if (callList.getPendingOutgoingCall() != null) {
957 newState = InCallState.PENDING_OUTGOING;
958 } else if (callList.getOutgoingCall() != null) {
959 newState = InCallState.OUTGOING;
960 } else if (callList.getActiveCall() != null
961 || callList.getBackgroundCall() != null
962 || callList.getDisconnectedCall() != null
963 || callList.getDisconnectingCall() != null) {
964 newState = InCallState.INCALL;
965 }
966
967 if (newState == InCallState.NO_CALLS) {
linyuh183cb712017-12-27 17:02:37 -0800968 if (boundAndWaitingForOutgoingCall) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700969 return InCallState.PENDING_OUTGOING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800970 }
971 }
972
973 return newState;
974 }
975
976 public boolean isBoundAndWaitingForOutgoingCall() {
linyuh183cb712017-12-27 17:02:37 -0800977 return boundAndWaitingForOutgoingCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800978 }
979
980 public void setBoundAndWaitingForOutgoingCall(boolean isBound, PhoneAccountHandle handle) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700981 LogUtil.i(
982 "InCallPresenter.setBoundAndWaitingForOutgoingCall",
983 "setBoundAndWaitingForOutgoingCall: " + isBound);
linyuh183cb712017-12-27 17:02:37 -0800984 boundAndWaitingForOutgoingCall = isBound;
985 themeColorManager.setPendingPhoneAccountHandle(handle);
986 if (isBound && inCallState == InCallState.NO_CALLS) {
987 inCallState = InCallState.PENDING_OUTGOING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800988 }
989 }
990
991 public void onShrinkAnimationComplete() {
linyuh183cb712017-12-27 17:02:37 -0800992 if (awaitingCallListUpdate) {
993 onCallListChange(callList);
Eric Erfanianccca3152017-02-22 16:32:36 -0800994 }
995 }
996
997 public void addIncomingCallListener(IncomingCallListener listener) {
998 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -0800999 incomingCallListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001000 }
1001
1002 public void removeIncomingCallListener(IncomingCallListener listener) {
1003 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001004 incomingCallListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001005 }
1006 }
1007
1008 public void addListener(InCallStateListener listener) {
1009 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -08001010 listeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001011 }
1012
1013 public void removeListener(InCallStateListener listener) {
1014 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001015 listeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001016 }
1017 }
1018
1019 public void addDetailsListener(InCallDetailsListener listener) {
1020 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -08001021 detailsListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001022 }
1023
1024 public void removeDetailsListener(InCallDetailsListener listener) {
1025 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001026 detailsListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001027 }
1028 }
1029
1030 public void addCanAddCallListener(CanAddCallListener listener) {
1031 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -08001032 canAddCallListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001033 }
1034
1035 public void removeCanAddCallListener(CanAddCallListener listener) {
1036 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001037 canAddCallListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001038 }
1039 }
1040
1041 public void addOrientationListener(InCallOrientationListener listener) {
1042 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -08001043 orientationListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001044 }
1045
1046 public void removeOrientationListener(InCallOrientationListener listener) {
1047 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001048 orientationListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001049 }
1050 }
1051
1052 public void addInCallEventListener(InCallEventListener listener) {
1053 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -08001054 inCallEventListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001055 }
1056
1057 public void removeInCallEventListener(InCallEventListener listener) {
1058 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001059 inCallEventListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001060 }
1061 }
1062
1063 public ProximitySensor getProximitySensor() {
linyuh183cb712017-12-27 17:02:37 -08001064 return proximitySensor;
Eric Erfanianccca3152017-02-22 16:32:36 -08001065 }
1066
1067 public PseudoScreenState getPseudoScreenState() {
linyuh183cb712017-12-27 17:02:37 -08001068 return pseudoScreenState;
Eric Erfanianccca3152017-02-22 16:32:36 -08001069 }
1070
1071 /** Returns true if the incall app is the foreground application. */
1072 public boolean isShowingInCallUi() {
1073 if (!isActivityStarted()) {
1074 return false;
1075 }
linyuh183cb712017-12-27 17:02:37 -08001076 if (manageConferenceActivity != null && manageConferenceActivity.isVisible()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001077 return true;
1078 }
linyuh183cb712017-12-27 17:02:37 -08001079 return inCallActivity.isVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -08001080 }
1081
1082 /**
1083 * Returns true if the activity has been created and is running. Returns true as long as activity
1084 * is not destroyed or finishing. This ensures that we return true even if the activity is paused
1085 * (not in foreground).
1086 */
1087 public boolean isActivityStarted() {
linyuh183cb712017-12-27 17:02:37 -08001088 return (inCallActivity != null
1089 && !inCallActivity.isDestroyed()
1090 && !inCallActivity.isFinishing());
Eric Erfanianccca3152017-02-22 16:32:36 -08001091 }
1092
1093 /**
1094 * Determines if the In-Call app is currently changing configuration.
1095 *
1096 * @return {@code true} if the In-Call app is changing configuration.
1097 */
1098 public boolean isChangingConfigurations() {
linyuh183cb712017-12-27 17:02:37 -08001099 return isChangingConfigurations;
Eric Erfanianccca3152017-02-22 16:32:36 -08001100 }
1101
1102 /**
1103 * Tracks whether the In-Call app is currently in the process of changing configuration (i.e.
1104 * screen orientation).
1105 */
1106 /*package*/
1107 void updateIsChangingConfigurations() {
linyuh183cb712017-12-27 17:02:37 -08001108 isChangingConfigurations = false;
1109 if (inCallActivity != null) {
1110 isChangingConfigurations = inCallActivity.isChangingConfigurations();
Eric Erfanianccca3152017-02-22 16:32:36 -08001111 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001112 LogUtil.v(
1113 "InCallPresenter.updateIsChangingConfigurations",
linyuh183cb712017-12-27 17:02:37 -08001114 "updateIsChangingConfigurations = " + isChangingConfigurations);
Eric Erfanianccca3152017-02-22 16:32:36 -08001115 }
1116
yueg10f6e822018-01-17 15:32:18 -08001117 /** Called when the activity goes in/out of the foreground. */
1118 public void onUiShowing(boolean showing) {
linyuh183cb712017-12-27 17:02:37 -08001119 if (proximitySensor != null) {
1120 proximitySensor.onInCallShowing(showing);
Eric Erfanianccca3152017-02-22 16:32:36 -08001121 }
1122
yueg092b21c2017-11-15 16:20:07 -08001123 if (!showing) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001124 updateIsChangingConfigurations();
1125 }
1126
linyuh183cb712017-12-27 17:02:37 -08001127 for (InCallUiListener listener : inCallUiListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001128 listener.onUiShowing(showing);
1129 }
1130
linyuh183cb712017-12-27 17:02:37 -08001131 if (inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001132 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -08001133 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -08001134 }
1135 }
1136
Eric Erfanian2ca43182017-08-31 06:57:16 -07001137 public void refreshUi() {
linyuh183cb712017-12-27 17:02:37 -08001138 if (inCallActivity != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001139 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -08001140 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001141 }
1142 }
1143
Eric Erfanianccca3152017-02-22 16:32:36 -08001144 public void addInCallUiListener(InCallUiListener listener) {
linyuh183cb712017-12-27 17:02:37 -08001145 inCallUiListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001146 }
1147
1148 public boolean removeInCallUiListener(InCallUiListener listener) {
linyuh183cb712017-12-27 17:02:37 -08001149 return inCallUiListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001150 }
1151
1152 /*package*/
1153 void onActivityStarted() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001154 LogUtil.d("InCallPresenter.onActivityStarted", "onActivityStarted");
Eric Erfanianccca3152017-02-22 16:32:36 -08001155 notifyVideoPauseController(true);
Eric Erfanian2ca43182017-08-31 06:57:16 -07001156 applyScreenTimeout();
Eric Erfanianccca3152017-02-22 16:32:36 -08001157 }
1158
1159 /*package*/
1160 void onActivityStopped() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001161 LogUtil.d("InCallPresenter.onActivityStopped", "onActivityStopped");
Eric Erfanianccca3152017-02-22 16:32:36 -08001162 notifyVideoPauseController(false);
1163 }
1164
1165 private void notifyVideoPauseController(boolean showing) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001166 LogUtil.d(
1167 "InCallPresenter.notifyVideoPauseController",
linyuh183cb712017-12-27 17:02:37 -08001168 "mIsChangingConfigurations=" + isChangingConfigurations);
1169 if (!isChangingConfigurations) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001170 VideoPauseController.getInstance().onUiShowing(showing);
1171 }
1172 }
1173
1174 /** Brings the app into the foreground if possible. */
1175 public void bringToForeground(boolean showDialpad) {
1176 // Before we bring the incall UI to the foreground, we check to see if:
1177 // 1. It is not currently in the foreground
1178 // 2. We are in a state where we want to show the incall ui (i.e. there are calls to
1179 // be displayed)
1180 // If the activity hadn't actually been started previously, yet there are still calls
1181 // present (e.g. a call was accepted by a bluetooth or wired headset), we want to
1182 // bring it up the UI regardless.
linyuh183cb712017-12-27 17:02:37 -08001183 if (!isShowingInCallUi() && inCallState != InCallState.NO_CALLS) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001184 showInCall(showDialpad, false /* newOutgoingCall */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001185 }
1186 }
1187
1188 public void onPostDialCharWait(String callId, String chars) {
1189 if (isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -08001190 inCallActivity.showDialogForPostCharWait(callId, chars);
Eric Erfanianccca3152017-02-22 16:32:36 -08001191 }
1192 }
1193
1194 /**
1195 * Handles the green CALL key while in-call.
1196 *
1197 * @return true if we consumed the event.
1198 */
1199 public boolean handleCallKey() {
1200 LogUtil.v("InCallPresenter.handleCallKey", null);
1201
1202 // The green CALL button means either "Answer", "Unhold", or
1203 // "Swap calls", or can be a no-op, depending on the current state
1204 // of the Phone.
1205
1206 /** INCOMING CALL */
linyuh183cb712017-12-27 17:02:37 -08001207 final CallList calls = callList;
Eric Erfanianccca3152017-02-22 16:32:36 -08001208 final DialerCall incomingCall = calls.getIncomingCall();
1209 LogUtil.v("InCallPresenter.handleCallKey", "incomingCall: " + incomingCall);
1210
1211 // (1) Attempt to answer a call
1212 if (incomingCall != null) {
1213 incomingCall.answer(VideoProfile.STATE_AUDIO_ONLY);
1214 return true;
1215 }
1216
1217 /** STATE_ACTIVE CALL */
1218 final DialerCall activeCall = calls.getActiveCall();
1219 if (activeCall != null) {
1220 // TODO: This logic is repeated from CallButtonPresenter.java. We should
1221 // consolidate this logic.
1222 final boolean canMerge =
1223 activeCall.can(android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
1224 final boolean canSwap =
1225 activeCall.can(android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE);
1226
Eric Erfanian2ca43182017-08-31 06:57:16 -07001227 LogUtil.v(
1228 "InCallPresenter.handleCallKey",
1229 "activeCall: " + activeCall + ", canMerge: " + canMerge + ", canSwap: " + canSwap);
Eric Erfanianccca3152017-02-22 16:32:36 -08001230
1231 // (2) Attempt actions on conference calls
1232 if (canMerge) {
1233 TelecomAdapter.getInstance().merge(activeCall.getId());
1234 return true;
1235 } else if (canSwap) {
1236 TelecomAdapter.getInstance().swap(activeCall.getId());
1237 return true;
1238 }
1239 }
1240
1241 /** BACKGROUND CALL */
1242 final DialerCall heldCall = calls.getBackgroundCall();
1243 if (heldCall != null) {
1244 // We have a hold call so presumeable it will always support HOLD...but
1245 // there is no harm in double checking.
1246 final boolean canHold = heldCall.can(android.telecom.Call.Details.CAPABILITY_HOLD);
1247
Eric Erfanian2ca43182017-08-31 06:57:16 -07001248 LogUtil.v("InCallPresenter.handleCallKey", "heldCall: " + heldCall + ", canHold: " + canHold);
Eric Erfanianccca3152017-02-22 16:32:36 -08001249
1250 // (4) unhold call
1251 if (heldCall.getState() == DialerCall.State.ONHOLD && canHold) {
1252 heldCall.unhold();
1253 return true;
1254 }
1255 }
1256
1257 // Always consume hard keys
1258 return true;
1259 }
1260
Eric Erfanianccca3152017-02-22 16:32:36 -08001261 /** Clears the previous fullscreen state. */
1262 public void clearFullscreen() {
linyuh183cb712017-12-27 17:02:37 -08001263 isFullScreen = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001264 }
1265
1266 /**
1267 * Changes the fullscreen mode of the in-call UI.
1268 *
1269 * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
1270 * otherwise.
1271 */
1272 public void setFullScreen(boolean isFullScreen) {
1273 setFullScreen(isFullScreen, false /* force */);
1274 }
1275
1276 /**
1277 * Changes the fullscreen mode of the in-call UI.
1278 *
1279 * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
1280 * otherwise.
1281 * @param force {@code true} if fullscreen mode should be set regardless of its current state.
1282 */
1283 public void setFullScreen(boolean isFullScreen, boolean force) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001284 LogUtil.i("InCallPresenter.setFullScreen", "setFullScreen = " + isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001285
1286 // As a safeguard, ensure we cannot enter fullscreen if the dialpad is shown.
1287 if (isDialpadVisible()) {
1288 isFullScreen = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001289 LogUtil.v(
1290 "InCallPresenter.setFullScreen",
1291 "setFullScreen overridden as dialpad is shown = " + isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001292 }
1293
linyuh183cb712017-12-27 17:02:37 -08001294 if (this.isFullScreen == isFullScreen && !force) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001295 LogUtil.v("InCallPresenter.setFullScreen", "setFullScreen ignored as already in that state.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001296 return;
1297 }
linyuh183cb712017-12-27 17:02:37 -08001298 this.isFullScreen = isFullScreen;
1299 notifyFullscreenModeChange(this.isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001300 }
1301
1302 /**
1303 * @return {@code true} if the in-call ui is currently in fullscreen mode, {@code false}
1304 * otherwise.
1305 */
1306 public boolean isFullscreen() {
linyuh183cb712017-12-27 17:02:37 -08001307 return isFullScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -08001308 }
1309
1310 /**
1311 * Called by the {@link VideoCallPresenter} to inform of a change in full screen video status.
1312 *
1313 * @param isFullscreenMode {@code True} if entering full screen mode.
1314 */
1315 public void notifyFullscreenModeChange(boolean isFullscreenMode) {
linyuh183cb712017-12-27 17:02:37 -08001316 for (InCallEventListener listener : inCallEventListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001317 listener.onFullscreenModeChanged(isFullscreenMode);
1318 }
1319 }
1320
linyuh7b86f562017-11-16 11:24:09 -08001321 /** Instruct the in-call activity to show an error dialog or toast for a disconnected call. */
1322 private void showDialogOrToastForDisconnectedCall(DialerCall call) {
1323 if (!isActivityStarted() || call.getState() != DialerCall.State.DISCONNECTED) {
1324 return;
Eric Erfanianccca3152017-02-22 16:32:36 -08001325 }
linyuh7b86f562017-11-16 11:24:09 -08001326
1327 // For newly disconnected calls, we may want to show a dialog on specific error conditions
1328 if (call.getAccountHandle() == null && !call.isConferenceCall()) {
1329 setDisconnectCauseForMissingAccounts(call);
1330 }
1331
linyuh183cb712017-12-27 17:02:37 -08001332 inCallActivity.showDialogOrToastForDisconnectedCall(
1333 new DisconnectMessage(inCallActivity, call));
Eric Erfanianccca3152017-02-22 16:32:36 -08001334 }
1335
1336 /**
1337 * When the state of in-call changes, this is the first method to get called. It determines if the
1338 * UI needs to be started or finished depending on the new state and does it.
1339 */
1340 private InCallState startOrFinishUi(InCallState newState) {
wangqicf61ca02017-08-31 15:32:55 -07001341 Trace.beginSection("InCallPresenter.startOrFinishUi");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001342 LogUtil.d(
linyuh183cb712017-12-27 17:02:37 -08001343 "InCallPresenter.startOrFinishUi", "startOrFinishUi: " + inCallState + " -> " + newState);
Eric Erfanianccca3152017-02-22 16:32:36 -08001344
1345 // TODO: Consider a proper state machine implementation
1346
1347 // If the state isn't changing we have already done any starting/stopping of activities in
1348 // a previous pass...so lets cut out early
linyuh183cb712017-12-27 17:02:37 -08001349 if (newState == inCallState) {
wangqicf61ca02017-08-31 15:32:55 -07001350 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001351 return newState;
1352 }
1353
1354 // A new Incoming call means that the user needs to be notified of the the call (since
1355 // it wasn't them who initiated it). We do this through full screen notifications and
1356 // happens indirectly through {@link StatusBarNotifier}.
1357 //
1358 // The process for incoming calls is as follows:
1359 //
1360 // 1) CallList - Announces existence of new INCOMING call
1361 // 2) InCallPresenter - Gets announcement and calculates that the new InCallState
1362 // - should be set to INCOMING.
1363 // 3) InCallPresenter - This method is called to see if we need to start or finish
1364 // the app given the new state.
1365 // 4) StatusBarNotifier - Listens to InCallState changes. InCallPresenter calls
1366 // StatusBarNotifier explicitly to issue a FullScreen Notification
1367 // that will either start the InCallActivity or show the user a
1368 // top-level notification dialog if the user is in an immersive app.
1369 // That notification can also start the InCallActivity.
1370 // 5) InCallActivity - Main activity starts up and at the end of its onCreate will
1371 // call InCallPresenter::setActivity() to let the presenter
1372 // know that start-up is complete.
1373 //
1374 // [ AND NOW YOU'RE IN THE CALL. voila! ]
Eric Erfanianccca3152017-02-22 16:32:36 -08001375
1376 // A dialog to show on top of the InCallUI to select a PhoneAccount
1377 final boolean showAccountPicker = (InCallState.WAITING_FOR_ACCOUNT == newState);
1378
1379 // A new outgoing call indicates that the user just now dialed a number and when that
1380 // happens we need to display the screen immediately or show an account picker dialog if
1381 // no default is set. However, if the main InCallUI is already visible, we do not want to
1382 // re-initiate the start-up animation, so we do not need to do anything here.
1383 //
1384 // It is also possible to go into an intermediate state where the call has been initiated
1385 // but Telecom has not yet returned with the details of the call (handle, gateway, etc.).
1386 // This pending outgoing state can also launch the call screen.
1387 //
1388 // This is different from the incoming call sequence because we do not need to shock the
1389 // user with a top-level notification. Just show the call UI normally.
1390 boolean callCardFragmentVisible =
linyuh183cb712017-12-27 17:02:37 -08001391 inCallActivity != null && inCallActivity.getCallCardFragmentVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -08001392 final boolean mainUiNotVisible = !isShowingInCallUi() || !callCardFragmentVisible;
1393 boolean showCallUi = InCallState.OUTGOING == newState && mainUiNotVisible;
1394
1395 // Direct transition from PENDING_OUTGOING -> INCALL means that there was an error in the
1396 // outgoing call process, so the UI should be brought up to show an error dialog.
1397 showCallUi |=
linyuh183cb712017-12-27 17:02:37 -08001398 (InCallState.PENDING_OUTGOING == inCallState
Eric Erfanianccca3152017-02-22 16:32:36 -08001399 && InCallState.INCALL == newState
1400 && !isShowingInCallUi());
1401
1402 // Another exception - InCallActivity is in charge of disconnecting a call with no
1403 // valid accounts set. Bring the UI up if this is true for the current pending outgoing
1404 // call so that:
1405 // 1) The call can be disconnected correctly
1406 // 2) The UI comes up and correctly displays the error dialog.
1407 // TODO: Remove these special case conditions by making InCallPresenter a true state
1408 // machine. Telecom should also be the component responsible for disconnecting a call
1409 // with no valid accounts.
1410 showCallUi |=
1411 InCallState.PENDING_OUTGOING == newState
1412 && mainUiNotVisible
linyuh183cb712017-12-27 17:02:37 -08001413 && isCallWithNoValidAccounts(callList.getPendingOutgoingCall());
Eric Erfanianccca3152017-02-22 16:32:36 -08001414
1415 // The only time that we have an instance of mInCallActivity and it isn't started is
1416 // when it is being destroyed. In that case, lets avoid bringing up another instance of
1417 // the activity. When it is finally destroyed, we double check if we should bring it back
1418 // up so we aren't going to lose anything by avoiding a second startup here.
linyuh183cb712017-12-27 17:02:37 -08001419 boolean activityIsFinishing = inCallActivity != null && !isActivityStarted();
Eric Erfanianccca3152017-02-22 16:32:36 -08001420 if (activityIsFinishing) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001421 LogUtil.i(
1422 "InCallPresenter.startOrFinishUi",
linyuh183cb712017-12-27 17:02:37 -08001423 "Undo the state change: " + newState + " -> " + inCallState);
wangqicf61ca02017-08-31 15:32:55 -07001424 Trace.endSection();
linyuh183cb712017-12-27 17:02:37 -08001425 return inCallState;
Eric Erfanianccca3152017-02-22 16:32:36 -08001426 }
1427
1428 // We're about the bring up the in-call UI for outgoing and incoming call. If we still have
1429 // dialogs up, we need to clear them out before showing in-call screen. This is necessary
1430 // to fix the bug that dialog will show up when data reaches limit even after makeing new
1431 // outgoing call after user ignore it by pressing home button.
1432 if ((newState == InCallState.INCOMING || newState == InCallState.PENDING_OUTGOING)
1433 && !showCallUi
1434 && isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -08001435 inCallActivity.dismissPendingDialogs();
Eric Erfanianccca3152017-02-22 16:32:36 -08001436 }
1437
1438 if (showCallUi || showAccountPicker) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001439 LogUtil.i("InCallPresenter.startOrFinishUi", "Start in call UI");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001440 showInCall(false /* showDialpad */, !showAccountPicker /* newOutgoingCall */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001441 } else if (newState == InCallState.NO_CALLS) {
1442 // The new state is the no calls state. Tear everything down.
1443 attemptFinishActivity();
1444 attemptCleanup();
1445 }
1446
wangqicf61ca02017-08-31 15:32:55 -07001447 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001448 return newState;
1449 }
1450
1451 /**
1452 * Sets the DisconnectCause for a call that was disconnected because it was missing a PhoneAccount
1453 * or PhoneAccounts to select from.
1454 */
1455 private void setDisconnectCauseForMissingAccounts(DialerCall call) {
1456
1457 Bundle extras = call.getIntentExtras();
1458 // Initialize the extras bundle to avoid NPE
1459 if (extras == null) {
1460 extras = new Bundle();
1461 }
1462
1463 final List<PhoneAccountHandle> phoneAccountHandles =
1464 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
1465
1466 if (phoneAccountHandles == null || phoneAccountHandles.isEmpty()) {
1467 String scheme = call.getHandle().getScheme();
1468 final String errorMsg =
1469 PhoneAccount.SCHEME_TEL.equals(scheme)
linyuh183cb712017-12-27 17:02:37 -08001470 ? context.getString(R.string.callFailed_simError)
1471 : context.getString(R.string.incall_error_supp_service_unknown);
Eric Erfanianccca3152017-02-22 16:32:36 -08001472 DisconnectCause disconnectCause =
1473 new DisconnectCause(DisconnectCause.ERROR, null, errorMsg, errorMsg);
1474 call.setDisconnectCause(disconnectCause);
1475 }
1476 }
1477
Eric Erfanianccca3152017-02-22 16:32:36 -08001478 /**
1479 * @return {@code true} if the InCallPresenter is ready to be torn down, {@code false} otherwise.
1480 * Calling classes should use this as an indication whether to interact with the
1481 * InCallPresenter or not.
1482 */
1483 public boolean isReadyForTearDown() {
linyuh183cb712017-12-27 17:02:37 -08001484 return inCallActivity == null && !serviceConnected && inCallState == InCallState.NO_CALLS;
Eric Erfanianccca3152017-02-22 16:32:36 -08001485 }
1486
1487 /**
1488 * Checks to see if both the UI is gone and the service is disconnected. If so, tear it all down.
1489 */
1490 private void attemptCleanup() {
1491 if (isReadyForTearDown()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001492 LogUtil.i("InCallPresenter.attemptCleanup", "Cleaning up");
Eric Erfanianccca3152017-02-22 16:32:36 -08001493
1494 cleanupSurfaces();
1495
linyuh183cb712017-12-27 17:02:37 -08001496 isChangingConfigurations = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001497
1498 // blow away stale contact info so that we get fresh data on
1499 // the next set of calls
linyuh183cb712017-12-27 17:02:37 -08001500 if (contactInfoCache != null) {
1501 contactInfoCache.clearCache();
Eric Erfanianccca3152017-02-22 16:32:36 -08001502 }
linyuh183cb712017-12-27 17:02:37 -08001503 contactInfoCache = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001504
linyuh183cb712017-12-27 17:02:37 -08001505 if (proximitySensor != null) {
1506 removeListener(proximitySensor);
1507 proximitySensor.tearDown();
Eric Erfanianccca3152017-02-22 16:32:36 -08001508 }
linyuh183cb712017-12-27 17:02:37 -08001509 proximitySensor = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001510
linyuh183cb712017-12-27 17:02:37 -08001511 if (statusBarNotifier != null) {
1512 removeListener(statusBarNotifier);
1513 EnrichedCallComponent.get(context)
Eric Erfaniand8046e52017-04-06 09:41:50 -07001514 .getEnrichedCallManager()
linyuh183cb712017-12-27 17:02:37 -08001515 .unregisterStateChangedListener(statusBarNotifier);
Eric Erfanianccca3152017-02-22 16:32:36 -08001516 }
Eric Erfaniand8046e52017-04-06 09:41:50 -07001517
linyuh183cb712017-12-27 17:02:37 -08001518 if (externalCallNotifier != null && externalCallList != null) {
1519 externalCallList.removeExternalCallListener(externalCallNotifier);
Eric Erfanianccca3152017-02-22 16:32:36 -08001520 }
linyuh183cb712017-12-27 17:02:37 -08001521 statusBarNotifier = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001522
linyuh183cb712017-12-27 17:02:37 -08001523 if (callList != null) {
1524 callList.removeListener(this);
1525 callList.removeListener(spamCallListListener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001526 }
linyuh183cb712017-12-27 17:02:37 -08001527 callList = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001528
linyuh183cb712017-12-27 17:02:37 -08001529 context = null;
1530 inCallActivity = null;
1531 manageConferenceActivity = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001532
linyuh183cb712017-12-27 17:02:37 -08001533 listeners.clear();
1534 incomingCallListeners.clear();
1535 detailsListeners.clear();
1536 canAddCallListeners.clear();
1537 orientationListeners.clear();
1538 inCallEventListeners.clear();
1539 inCallUiListeners.clear();
1540 if (!inCallUiLocks.isEmpty()) {
1541 LogUtil.e("InCallPresenter.attemptCleanup", "held in call locks: " + inCallUiLocks);
1542 inCallUiLocks.clear();
twyen8efb4952017-10-06 16:35:54 -07001543 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001544 LogUtil.d("InCallPresenter.attemptCleanup", "finished");
Eric Erfanianccca3152017-02-22 16:32:36 -08001545 }
1546 }
1547
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001548 public void showInCall(boolean showDialpad, boolean newOutgoingCall) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001549 LogUtil.i("InCallPresenter.showInCall", "Showing InCallActivity");
linyuh183cb712017-12-27 17:02:37 -08001550 context.startActivity(
1551 InCallActivity.getIntent(context, showDialpad, newOutgoingCall, false /* forFullScreen */));
Eric Erfanianccca3152017-02-22 16:32:36 -08001552 }
1553
1554 public void onServiceBind() {
linyuh183cb712017-12-27 17:02:37 -08001555 serviceBound = true;
Eric Erfanianccca3152017-02-22 16:32:36 -08001556 }
1557
1558 public void onServiceUnbind() {
1559 InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(false, null);
linyuh183cb712017-12-27 17:02:37 -08001560 serviceBound = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001561 }
1562
1563 public boolean isServiceBound() {
linyuh183cb712017-12-27 17:02:37 -08001564 return serviceBound;
Eric Erfanianccca3152017-02-22 16:32:36 -08001565 }
1566
1567 public void maybeStartRevealAnimation(Intent intent) {
linyuh183cb712017-12-27 17:02:37 -08001568 if (intent == null || inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001569 return;
1570 }
1571 final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
1572 if (extras == null) {
1573 // Incoming call, just show the in-call UI directly.
1574 return;
1575 }
1576
1577 if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) {
1578 // Account selection dialog will show up so don't show the animation.
1579 return;
1580 }
1581
1582 final PhoneAccountHandle accountHandle =
1583 intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
1584 final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT);
Eric Erfanianccca3152017-02-22 16:32:36 -08001585
1586 InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(true, accountHandle);
1587
1588 final Intent activityIntent =
linyuh183cb712017-12-27 17:02:37 -08001589 InCallActivity.getIntent(context, false, true, false /* forFullScreen */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001590 activityIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint);
linyuh183cb712017-12-27 17:02:37 -08001591 context.startActivity(activityIntent);
Eric Erfanianccca3152017-02-22 16:32:36 -08001592 }
1593
1594 /**
1595 * Retrieves the current in-call camera manager instance, creating if necessary.
1596 *
1597 * @return The {@link InCallCameraManager}.
1598 */
1599 public InCallCameraManager getInCallCameraManager() {
1600 synchronized (this) {
linyuh183cb712017-12-27 17:02:37 -08001601 if (inCallCameraManager == null) {
1602 inCallCameraManager = new InCallCameraManager(context);
Eric Erfanianccca3152017-02-22 16:32:36 -08001603 }
1604
linyuh183cb712017-12-27 17:02:37 -08001605 return inCallCameraManager;
Eric Erfanianccca3152017-02-22 16:32:36 -08001606 }
1607 }
1608
1609 /**
1610 * Notifies listeners of changes in orientation and notify calls of rotation angle change.
1611 *
1612 * @param orientation The screen orientation of the device (one of: {@link
1613 * InCallOrientationEventListener#SCREEN_ORIENTATION_0}, {@link
1614 * InCallOrientationEventListener#SCREEN_ORIENTATION_90}, {@link
1615 * InCallOrientationEventListener#SCREEN_ORIENTATION_180}, {@link
1616 * InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
1617 */
1618 public void onDeviceOrientationChange(@ScreenOrientation int orientation) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001619 LogUtil.d(
1620 "InCallPresenter.onDeviceOrientationChange",
1621 "onDeviceOrientationChange: orientation= " + orientation);
Eric Erfanianccca3152017-02-22 16:32:36 -08001622
linyuh183cb712017-12-27 17:02:37 -08001623 if (callList != null) {
1624 callList.notifyCallsOfDeviceRotation(orientation);
Eric Erfanianccca3152017-02-22 16:32:36 -08001625 } else {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001626 LogUtil.w("InCallPresenter.onDeviceOrientationChange", "CallList is null.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001627 }
1628
1629 // Notify listeners of device orientation changed.
linyuh183cb712017-12-27 17:02:37 -08001630 for (InCallOrientationListener listener : orientationListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001631 listener.onDeviceOrientationChanged(orientation);
1632 }
1633 }
1634
1635 /**
1636 * Configures the in-call UI activity so it can change orientations or not. Enables the
1637 * orientation event listener if allowOrientationChange is true, disables it if false.
1638 *
1639 * @param allowOrientationChange {@code true} if the in-call UI can change between portrait and
1640 * landscape. {@code false} if the in-call UI should be locked in portrait.
1641 */
1642 public void setInCallAllowsOrientationChange(boolean allowOrientationChange) {
linyuh183cb712017-12-27 17:02:37 -08001643 if (inCallActivity == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001644 LogUtil.e(
1645 "InCallPresenter.setInCallAllowsOrientationChange",
1646 "InCallActivity is null. Can't set requested orientation.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001647 return;
1648 }
linyuh183cb712017-12-27 17:02:37 -08001649 inCallActivity.setAllowOrientationChange(allowOrientationChange);
Eric Erfanianccca3152017-02-22 16:32:36 -08001650 }
1651
1652 public void enableScreenTimeout(boolean enable) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001653 LogUtil.v("InCallPresenter.enableScreenTimeout", "enableScreenTimeout: value=" + enable);
linyuh183cb712017-12-27 17:02:37 -08001654 screenTimeoutEnabled = enable;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001655 applyScreenTimeout();
1656 }
1657
1658 private void applyScreenTimeout() {
linyuh183cb712017-12-27 17:02:37 -08001659 if (inCallActivity == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001660 LogUtil.e("InCallPresenter.applyScreenTimeout", "InCallActivity is null.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001661 return;
1662 }
1663
linyuh183cb712017-12-27 17:02:37 -08001664 final Window window = inCallActivity.getWindow();
1665 if (screenTimeoutEnabled) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001666 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1667 } else {
1668 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1669 }
1670 }
1671
1672 /**
1673 * Hides or shows the conference manager fragment.
1674 *
1675 * @param show {@code true} if the conference manager should be shown, {@code false} if it should
1676 * be hidden.
1677 */
1678 public void showConferenceCallManager(boolean show) {
linyuh183cb712017-12-27 17:02:37 -08001679 if (inCallActivity != null) {
1680 inCallActivity.showConferenceFragment(show);
Eric Erfanianccca3152017-02-22 16:32:36 -08001681 }
linyuh183cb712017-12-27 17:02:37 -08001682 if (!show && manageConferenceActivity != null) {
1683 manageConferenceActivity.finish();
Eric Erfanianccca3152017-02-22 16:32:36 -08001684 }
1685 }
1686
1687 /**
1688 * Determines if the dialpad is visible.
1689 *
1690 * @return {@code true} if the dialpad is visible, {@code false} otherwise.
1691 */
1692 public boolean isDialpadVisible() {
linyuh183cb712017-12-27 17:02:37 -08001693 if (inCallActivity == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001694 return false;
1695 }
linyuh183cb712017-12-27 17:02:37 -08001696 return inCallActivity.isDialpadVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -08001697 }
1698
1699 public ThemeColorManager getThemeColorManager() {
linyuh183cb712017-12-27 17:02:37 -08001700 return themeColorManager;
Eric Erfanianccca3152017-02-22 16:32:36 -08001701 }
1702
wangqi8d662ca2017-10-26 11:27:19 -07001703 @VisibleForTesting
1704 public void setThemeColorManager(ThemeColorManager themeColorManager) {
linyuh183cb712017-12-27 17:02:37 -08001705 this.themeColorManager = themeColorManager;
wangqi8d662ca2017-10-26 11:27:19 -07001706 }
1707
Eric Erfanianccca3152017-02-22 16:32:36 -08001708 /** Called when the foreground call changes. */
1709 public void onForegroundCallChanged(DialerCall newForegroundCall) {
linyuh183cb712017-12-27 17:02:37 -08001710 themeColorManager.onForegroundCallChanged(context, newForegroundCall);
1711 if (inCallActivity != null) {
1712 inCallActivity.onForegroundCallChanged(newForegroundCall);
Eric Erfanianccca3152017-02-22 16:32:36 -08001713 }
1714 }
1715
1716 public InCallActivity getActivity() {
linyuh183cb712017-12-27 17:02:37 -08001717 return inCallActivity;
Eric Erfanianccca3152017-02-22 16:32:36 -08001718 }
1719
1720 /** Called when the UI begins, and starts the callstate callbacks if necessary. */
1721 public void setActivity(InCallActivity inCallActivity) {
1722 if (inCallActivity == null) {
1723 throw new IllegalArgumentException("registerActivity cannot be called with null");
1724 }
linyuh183cb712017-12-27 17:02:37 -08001725 if (this.inCallActivity != null && this.inCallActivity != inCallActivity) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001726 LogUtil.w(
1727 "InCallPresenter.setActivity", "Setting a second activity before destroying the first.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001728 }
1729 updateActivity(inCallActivity);
1730 }
1731
1732 ExternalCallNotifier getExternalCallNotifier() {
linyuh183cb712017-12-27 17:02:37 -08001733 return externalCallNotifier;
Eric Erfanianccca3152017-02-22 16:32:36 -08001734 }
1735
1736 VideoSurfaceTexture getLocalVideoSurfaceTexture() {
linyuh183cb712017-12-27 17:02:37 -08001737 if (localVideoSurfaceTexture == null) {
roldenburg12b50c62017-09-01 14:41:19 -07001738 boolean isPixel2017 = false;
linyuh183cb712017-12-27 17:02:37 -08001739 if (context != null) {
1740 isPixel2017 = context.getPackageManager().hasSystemFeature(PIXEL2017_SYSTEM_FEATURE);
roldenburg12b50c62017-09-01 14:41:19 -07001741 }
linyuh183cb712017-12-27 17:02:37 -08001742 localVideoSurfaceTexture = VideoSurfaceBindings.createLocalVideoSurfaceTexture(isPixel2017);
Eric Erfanianccca3152017-02-22 16:32:36 -08001743 }
linyuh183cb712017-12-27 17:02:37 -08001744 return localVideoSurfaceTexture;
Eric Erfanianccca3152017-02-22 16:32:36 -08001745 }
1746
1747 VideoSurfaceTexture getRemoteVideoSurfaceTexture() {
linyuh183cb712017-12-27 17:02:37 -08001748 if (remoteVideoSurfaceTexture == null) {
roldenburg12b50c62017-09-01 14:41:19 -07001749 boolean isPixel2017 = false;
linyuh183cb712017-12-27 17:02:37 -08001750 if (context != null) {
1751 isPixel2017 = context.getPackageManager().hasSystemFeature(PIXEL2017_SYSTEM_FEATURE);
roldenburg12b50c62017-09-01 14:41:19 -07001752 }
linyuh183cb712017-12-27 17:02:37 -08001753 remoteVideoSurfaceTexture = VideoSurfaceBindings.createRemoteVideoSurfaceTexture(isPixel2017);
Eric Erfanianccca3152017-02-22 16:32:36 -08001754 }
linyuh183cb712017-12-27 17:02:37 -08001755 return remoteVideoSurfaceTexture;
Eric Erfanianccca3152017-02-22 16:32:36 -08001756 }
1757
1758 void cleanupSurfaces() {
linyuh183cb712017-12-27 17:02:37 -08001759 if (remoteVideoSurfaceTexture != null) {
1760 remoteVideoSurfaceTexture.setDoneWithSurface();
1761 remoteVideoSurfaceTexture = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001762 }
linyuh183cb712017-12-27 17:02:37 -08001763 if (localVideoSurfaceTexture != null) {
1764 localVideoSurfaceTexture.setDoneWithSurface();
1765 localVideoSurfaceTexture = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001766 }
1767 }
1768
yueg77cb8e52017-10-27 16:42:51 -07001769 @Override
1770 public void onAudioStateChanged(CallAudioState audioState) {
linyuh183cb712017-12-27 17:02:37 -08001771 if (statusBarNotifier != null) {
1772 statusBarNotifier.updateNotification();
yueg77cb8e52017-10-27 16:42:51 -07001773 }
1774 }
1775
Eric Erfanianccca3152017-02-22 16:32:36 -08001776 /** All the main states of InCallActivity. */
1777 public enum InCallState {
1778 // InCall Screen is off and there are no calls
1779 NO_CALLS,
1780
1781 // Incoming-call screen is up
1782 INCOMING,
1783
1784 // In-call experience is showing
1785 INCALL,
1786
1787 // Waiting for user input before placing outgoing call
1788 WAITING_FOR_ACCOUNT,
1789
1790 // UI is starting up but no call has been initiated yet.
1791 // The UI is waiting for Telecom to respond.
1792 PENDING_OUTGOING,
1793
1794 // User is dialing out
1795 OUTGOING;
1796
1797 public boolean isIncoming() {
1798 return (this == INCOMING);
1799 }
1800
1801 public boolean isConnectingOrConnected() {
1802 return (this == INCOMING || this == OUTGOING || this == INCALL);
1803 }
1804 }
1805
1806 /** Interface implemented by classes that need to know about the InCall State. */
1807 public interface InCallStateListener {
1808
1809 // TODO: Enhance state to contain the call objects instead of passing CallList
1810 void onStateChange(InCallState oldState, InCallState newState, CallList callList);
1811 }
1812
1813 public interface IncomingCallListener {
1814
1815 void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call);
1816 }
1817
1818 public interface CanAddCallListener {
1819
1820 void onCanAddCallChanged(boolean canAddCall);
1821 }
1822
1823 public interface InCallDetailsListener {
1824
1825 void onDetailsChanged(DialerCall call, android.telecom.Call.Details details);
1826 }
1827
1828 public interface InCallOrientationListener {
1829
1830 void onDeviceOrientationChanged(@ScreenOrientation int orientation);
1831 }
1832
1833 /**
1834 * Interface implemented by classes that need to know about events which occur within the In-Call
1835 * UI. Used as a means of communicating between fragments that make up the UI.
1836 */
1837 public interface InCallEventListener {
1838
1839 void onFullscreenModeChanged(boolean isFullscreenMode);
1840 }
1841
1842 public interface InCallUiListener {
1843
1844 void onUiShowing(boolean showing);
1845 }
twyen8efb4952017-10-06 16:35:54 -07001846
1847 private class InCallUiLockImpl implements InCallUiLock {
1848 private final String tag;
1849
1850 private InCallUiLockImpl(String tag) {
1851 this.tag = tag;
1852 }
1853
1854 @MainThread
1855 @Override
1856 public void release() {
1857 Assert.isMainThread();
1858 releaseInCallUiLock(InCallUiLockImpl.this);
1859 }
1860
1861 @Override
1862 public String toString() {
1863 return "InCallUiLock[" + tag + "]";
1864 }
1865 }
1866
1867 @MainThread
1868 public InCallUiLock acquireInCallUiLock(String tag) {
1869 Assert.isMainThread();
1870 InCallUiLock lock = new InCallUiLockImpl(tag);
linyuh183cb712017-12-27 17:02:37 -08001871 inCallUiLocks.add(lock);
twyen8efb4952017-10-06 16:35:54 -07001872 return lock;
1873 }
1874
1875 @MainThread
1876 private void releaseInCallUiLock(InCallUiLock lock) {
1877 Assert.isMainThread();
1878 LogUtil.i("InCallPresenter.releaseInCallUiLock", "releasing %s", lock);
linyuh183cb712017-12-27 17:02:37 -08001879 inCallUiLocks.remove(lock);
1880 if (inCallUiLocks.isEmpty()) {
twyen8efb4952017-10-06 16:35:54 -07001881 LogUtil.i("InCallPresenter.releaseInCallUiLock", "all locks released");
linyuh183cb712017-12-27 17:02:37 -08001882 if (inCallState == InCallState.NO_CALLS) {
twyen8efb4952017-10-06 16:35:54 -07001883 LogUtil.i("InCallPresenter.releaseInCallUiLock", "no more calls, finishing UI");
1884 attemptFinishActivity();
1885 attemptCleanup();
1886 }
1887 }
1888 }
1889
1890 @MainThread
1891 public boolean isInCallUiLocked() {
1892 Assert.isMainThread();
linyuh183cb712017-12-27 17:02:37 -08001893 if (inCallUiLocks.isEmpty()) {
twyen8efb4952017-10-06 16:35:54 -07001894 return false;
1895 }
linyuh183cb712017-12-27 17:02:37 -08001896 for (InCallUiLock lock : inCallUiLocks) {
twyen8efb4952017-10-06 16:35:54 -07001897 LogUtil.i("InCallPresenter.isInCallUiLocked", "still locked by %s", lock);
1898 }
1899 return true;
1900 }
1901
linyuh183cb712017-12-27 17:02:37 -08001902 private final Set<InCallUiLock> inCallUiLocks = new ArraySet<>();
Eric Erfanianccca3152017-02-22 16:32:36 -08001903}