blob: 6300dac75ce3b22ea3c2678b61c06a5f26555dc1 [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
wangqibc28ea72018-04-02 16:23:00 -0700897 public void onUpgradeToRtt(DialerCall call, int rttRequestId) {
898 if (inCallActivity != null) {
899 inCallActivity.showDialogForRttRequest(call, rttRequestId);
900 }
901 }
902
903 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700904 public void onSessionModificationStateChange(DialerCall call) {
905 int newState = call.getVideoTech().getSessionModificationState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800906 LogUtil.i("InCallPresenter.onSessionModificationStateChange", "state: %d", newState);
linyuh183cb712017-12-27 17:02:37 -0800907 if (proximitySensor == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800908 LogUtil.i("InCallPresenter.onSessionModificationStateChange", "proximitySensor is null");
909 return;
910 }
linyuh183cb712017-12-27 17:02:37 -0800911 proximitySensor.setIsAttemptingVideoCall(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700912 call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest());
linyuh183cb712017-12-27 17:02:37 -0800913 if (inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800914 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -0800915 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800916 }
917 }
918
919 /**
920 * Called when a call becomes disconnected. Called everytime an existing call changes from being
921 * connected (incoming/outgoing/active) to disconnected.
922 */
923 @Override
924 public void onDisconnect(DialerCall call) {
linyuh7b86f562017-11-16 11:24:09 -0800925 showDialogOrToastForDisconnectedCall(call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800926
927 // We need to do the run the same code as onCallListChange.
linyuh183cb712017-12-27 17:02:37 -0800928 onCallListChange(callList);
Eric Erfanianccca3152017-02-22 16:32:36 -0800929
930 if (isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -0800931 inCallActivity.dismissKeyguard(false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800932 }
933
934 if (call.isEmergencyCall()) {
linyuh183cb712017-12-27 17:02:37 -0800935 FilteredNumbersUtil.recordLastEmergencyCallTime(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800936 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800937
linyuh183cb712017-12-27 17:02:37 -0800938 if (!callList.hasLiveCall()
Eric Erfanianfc37b022017-03-21 10:11:17 -0700939 && !call.getLogState().isIncoming
Eric Erfanian10b34a52017-05-04 08:23:17 -0700940 && !isSecretCode(call.getNumber())
wangqi9982f0d2017-10-11 17:46:07 -0700941 && !call.isVoiceMailNumber()) {
linyuh183cb712017-12-27 17:02:37 -0800942 PostCall.onCallDisconnected(context, call.getNumber(), call.getConnectTimeMillis());
Eric Erfanianccca3152017-02-22 16:32:36 -0800943 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800944 }
945
Eric Erfanian10b34a52017-05-04 08:23:17 -0700946 private boolean isSecretCode(@Nullable String number) {
947 return number != null
948 && (number.length() <= 8 || number.startsWith("*#*#") || number.endsWith("#*#*"));
949 }
950
Eric Erfanianccca3152017-02-22 16:32:36 -0800951 /** Given the call list, return the state in which the in-call screen should be. */
952 public InCallState getPotentialStateFromCallList(CallList callList) {
953
954 InCallState newState = InCallState.NO_CALLS;
955
956 if (callList == null) {
957 return newState;
958 }
959 if (callList.getIncomingCall() != null) {
960 newState = InCallState.INCOMING;
961 } else if (callList.getWaitingForAccountCall() != null) {
962 newState = InCallState.WAITING_FOR_ACCOUNT;
963 } else if (callList.getPendingOutgoingCall() != null) {
964 newState = InCallState.PENDING_OUTGOING;
965 } else if (callList.getOutgoingCall() != null) {
966 newState = InCallState.OUTGOING;
967 } else if (callList.getActiveCall() != null
968 || callList.getBackgroundCall() != null
969 || callList.getDisconnectedCall() != null
970 || callList.getDisconnectingCall() != null) {
971 newState = InCallState.INCALL;
972 }
973
974 if (newState == InCallState.NO_CALLS) {
linyuh183cb712017-12-27 17:02:37 -0800975 if (boundAndWaitingForOutgoingCall) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700976 return InCallState.PENDING_OUTGOING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800977 }
978 }
979
980 return newState;
981 }
982
983 public boolean isBoundAndWaitingForOutgoingCall() {
linyuh183cb712017-12-27 17:02:37 -0800984 return boundAndWaitingForOutgoingCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800985 }
986
987 public void setBoundAndWaitingForOutgoingCall(boolean isBound, PhoneAccountHandle handle) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700988 LogUtil.i(
989 "InCallPresenter.setBoundAndWaitingForOutgoingCall",
990 "setBoundAndWaitingForOutgoingCall: " + isBound);
linyuh183cb712017-12-27 17:02:37 -0800991 boundAndWaitingForOutgoingCall = isBound;
992 themeColorManager.setPendingPhoneAccountHandle(handle);
993 if (isBound && inCallState == InCallState.NO_CALLS) {
994 inCallState = InCallState.PENDING_OUTGOING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800995 }
996 }
997
998 public void onShrinkAnimationComplete() {
linyuh183cb712017-12-27 17:02:37 -0800999 if (awaitingCallListUpdate) {
1000 onCallListChange(callList);
Eric Erfanianccca3152017-02-22 16:32:36 -08001001 }
1002 }
1003
1004 public void addIncomingCallListener(IncomingCallListener listener) {
1005 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -08001006 incomingCallListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001007 }
1008
1009 public void removeIncomingCallListener(IncomingCallListener listener) {
1010 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001011 incomingCallListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001012 }
1013 }
1014
1015 public void addListener(InCallStateListener listener) {
1016 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -08001017 listeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001018 }
1019
1020 public void removeListener(InCallStateListener listener) {
1021 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001022 listeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001023 }
1024 }
1025
1026 public void addDetailsListener(InCallDetailsListener listener) {
1027 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -08001028 detailsListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001029 }
1030
1031 public void removeDetailsListener(InCallDetailsListener listener) {
1032 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001033 detailsListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001034 }
1035 }
1036
1037 public void addCanAddCallListener(CanAddCallListener listener) {
1038 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -08001039 canAddCallListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001040 }
1041
1042 public void removeCanAddCallListener(CanAddCallListener listener) {
1043 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001044 canAddCallListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001045 }
1046 }
1047
1048 public void addOrientationListener(InCallOrientationListener listener) {
1049 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -08001050 orientationListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001051 }
1052
1053 public void removeOrientationListener(InCallOrientationListener listener) {
1054 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001055 orientationListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001056 }
1057 }
1058
1059 public void addInCallEventListener(InCallEventListener listener) {
1060 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -08001061 inCallEventListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001062 }
1063
1064 public void removeInCallEventListener(InCallEventListener listener) {
1065 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -08001066 inCallEventListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001067 }
1068 }
1069
1070 public ProximitySensor getProximitySensor() {
linyuh183cb712017-12-27 17:02:37 -08001071 return proximitySensor;
Eric Erfanianccca3152017-02-22 16:32:36 -08001072 }
1073
1074 public PseudoScreenState getPseudoScreenState() {
linyuh183cb712017-12-27 17:02:37 -08001075 return pseudoScreenState;
Eric Erfanianccca3152017-02-22 16:32:36 -08001076 }
1077
1078 /** Returns true if the incall app is the foreground application. */
1079 public boolean isShowingInCallUi() {
1080 if (!isActivityStarted()) {
1081 return false;
1082 }
linyuh183cb712017-12-27 17:02:37 -08001083 if (manageConferenceActivity != null && manageConferenceActivity.isVisible()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001084 return true;
1085 }
linyuh183cb712017-12-27 17:02:37 -08001086 return inCallActivity.isVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -08001087 }
1088
1089 /**
1090 * Returns true if the activity has been created and is running. Returns true as long as activity
1091 * is not destroyed or finishing. This ensures that we return true even if the activity is paused
1092 * (not in foreground).
1093 */
1094 public boolean isActivityStarted() {
linyuh183cb712017-12-27 17:02:37 -08001095 return (inCallActivity != null
1096 && !inCallActivity.isDestroyed()
1097 && !inCallActivity.isFinishing());
Eric Erfanianccca3152017-02-22 16:32:36 -08001098 }
1099
1100 /**
1101 * Determines if the In-Call app is currently changing configuration.
1102 *
1103 * @return {@code true} if the In-Call app is changing configuration.
1104 */
1105 public boolean isChangingConfigurations() {
linyuh183cb712017-12-27 17:02:37 -08001106 return isChangingConfigurations;
Eric Erfanianccca3152017-02-22 16:32:36 -08001107 }
1108
1109 /**
1110 * Tracks whether the In-Call app is currently in the process of changing configuration (i.e.
1111 * screen orientation).
1112 */
1113 /*package*/
1114 void updateIsChangingConfigurations() {
linyuh183cb712017-12-27 17:02:37 -08001115 isChangingConfigurations = false;
1116 if (inCallActivity != null) {
1117 isChangingConfigurations = inCallActivity.isChangingConfigurations();
Eric Erfanianccca3152017-02-22 16:32:36 -08001118 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001119 LogUtil.v(
1120 "InCallPresenter.updateIsChangingConfigurations",
linyuh183cb712017-12-27 17:02:37 -08001121 "updateIsChangingConfigurations = " + isChangingConfigurations);
Eric Erfanianccca3152017-02-22 16:32:36 -08001122 }
1123
yueg10f6e822018-01-17 15:32:18 -08001124 /** Called when the activity goes in/out of the foreground. */
1125 public void onUiShowing(boolean showing) {
linyuh183cb712017-12-27 17:02:37 -08001126 if (proximitySensor != null) {
1127 proximitySensor.onInCallShowing(showing);
Eric Erfanianccca3152017-02-22 16:32:36 -08001128 }
1129
yueg092b21c2017-11-15 16:20:07 -08001130 if (!showing) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001131 updateIsChangingConfigurations();
1132 }
1133
linyuh183cb712017-12-27 17:02:37 -08001134 for (InCallUiListener listener : inCallUiListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001135 listener.onUiShowing(showing);
1136 }
1137
linyuh183cb712017-12-27 17:02:37 -08001138 if (inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001139 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -08001140 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -08001141 }
1142 }
1143
Eric Erfanian2ca43182017-08-31 06:57:16 -07001144 public void refreshUi() {
linyuh183cb712017-12-27 17:02:37 -08001145 if (inCallActivity != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001146 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -08001147 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001148 }
1149 }
1150
Eric Erfanianccca3152017-02-22 16:32:36 -08001151 public void addInCallUiListener(InCallUiListener listener) {
linyuh183cb712017-12-27 17:02:37 -08001152 inCallUiListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001153 }
1154
1155 public boolean removeInCallUiListener(InCallUiListener listener) {
linyuh183cb712017-12-27 17:02:37 -08001156 return inCallUiListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001157 }
1158
1159 /*package*/
1160 void onActivityStarted() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001161 LogUtil.d("InCallPresenter.onActivityStarted", "onActivityStarted");
Eric Erfanianccca3152017-02-22 16:32:36 -08001162 notifyVideoPauseController(true);
Eric Erfanian2ca43182017-08-31 06:57:16 -07001163 applyScreenTimeout();
Eric Erfanianccca3152017-02-22 16:32:36 -08001164 }
1165
1166 /*package*/
1167 void onActivityStopped() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001168 LogUtil.d("InCallPresenter.onActivityStopped", "onActivityStopped");
Eric Erfanianccca3152017-02-22 16:32:36 -08001169 notifyVideoPauseController(false);
1170 }
1171
1172 private void notifyVideoPauseController(boolean showing) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001173 LogUtil.d(
1174 "InCallPresenter.notifyVideoPauseController",
linyuh183cb712017-12-27 17:02:37 -08001175 "mIsChangingConfigurations=" + isChangingConfigurations);
1176 if (!isChangingConfigurations) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001177 VideoPauseController.getInstance().onUiShowing(showing);
1178 }
1179 }
1180
1181 /** Brings the app into the foreground if possible. */
1182 public void bringToForeground(boolean showDialpad) {
1183 // Before we bring the incall UI to the foreground, we check to see if:
1184 // 1. It is not currently in the foreground
1185 // 2. We are in a state where we want to show the incall ui (i.e. there are calls to
1186 // be displayed)
1187 // If the activity hadn't actually been started previously, yet there are still calls
1188 // present (e.g. a call was accepted by a bluetooth or wired headset), we want to
1189 // bring it up the UI regardless.
linyuh183cb712017-12-27 17:02:37 -08001190 if (!isShowingInCallUi() && inCallState != InCallState.NO_CALLS) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001191 showInCall(showDialpad, false /* newOutgoingCall */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001192 }
1193 }
1194
1195 public void onPostDialCharWait(String callId, String chars) {
1196 if (isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -08001197 inCallActivity.showDialogForPostCharWait(callId, chars);
Eric Erfanianccca3152017-02-22 16:32:36 -08001198 }
1199 }
1200
1201 /**
1202 * Handles the green CALL key while in-call.
1203 *
1204 * @return true if we consumed the event.
1205 */
1206 public boolean handleCallKey() {
1207 LogUtil.v("InCallPresenter.handleCallKey", null);
1208
1209 // The green CALL button means either "Answer", "Unhold", or
1210 // "Swap calls", or can be a no-op, depending on the current state
1211 // of the Phone.
1212
1213 /** INCOMING CALL */
linyuh183cb712017-12-27 17:02:37 -08001214 final CallList calls = callList;
Eric Erfanianccca3152017-02-22 16:32:36 -08001215 final DialerCall incomingCall = calls.getIncomingCall();
1216 LogUtil.v("InCallPresenter.handleCallKey", "incomingCall: " + incomingCall);
1217
1218 // (1) Attempt to answer a call
1219 if (incomingCall != null) {
1220 incomingCall.answer(VideoProfile.STATE_AUDIO_ONLY);
1221 return true;
1222 }
1223
1224 /** STATE_ACTIVE CALL */
1225 final DialerCall activeCall = calls.getActiveCall();
1226 if (activeCall != null) {
1227 // TODO: This logic is repeated from CallButtonPresenter.java. We should
1228 // consolidate this logic.
1229 final boolean canMerge =
1230 activeCall.can(android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
1231 final boolean canSwap =
1232 activeCall.can(android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE);
1233
Eric Erfanian2ca43182017-08-31 06:57:16 -07001234 LogUtil.v(
1235 "InCallPresenter.handleCallKey",
1236 "activeCall: " + activeCall + ", canMerge: " + canMerge + ", canSwap: " + canSwap);
Eric Erfanianccca3152017-02-22 16:32:36 -08001237
1238 // (2) Attempt actions on conference calls
1239 if (canMerge) {
1240 TelecomAdapter.getInstance().merge(activeCall.getId());
1241 return true;
1242 } else if (canSwap) {
1243 TelecomAdapter.getInstance().swap(activeCall.getId());
1244 return true;
1245 }
1246 }
1247
1248 /** BACKGROUND CALL */
1249 final DialerCall heldCall = calls.getBackgroundCall();
1250 if (heldCall != null) {
1251 // We have a hold call so presumeable it will always support HOLD...but
1252 // there is no harm in double checking.
1253 final boolean canHold = heldCall.can(android.telecom.Call.Details.CAPABILITY_HOLD);
1254
Eric Erfanian2ca43182017-08-31 06:57:16 -07001255 LogUtil.v("InCallPresenter.handleCallKey", "heldCall: " + heldCall + ", canHold: " + canHold);
Eric Erfanianccca3152017-02-22 16:32:36 -08001256
1257 // (4) unhold call
1258 if (heldCall.getState() == DialerCall.State.ONHOLD && canHold) {
1259 heldCall.unhold();
1260 return true;
1261 }
1262 }
1263
1264 // Always consume hard keys
1265 return true;
1266 }
1267
Eric Erfanianccca3152017-02-22 16:32:36 -08001268 /** Clears the previous fullscreen state. */
1269 public void clearFullscreen() {
linyuh183cb712017-12-27 17:02:37 -08001270 isFullScreen = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001271 }
1272
1273 /**
1274 * Changes the fullscreen mode of the in-call UI.
1275 *
1276 * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
1277 * otherwise.
1278 */
1279 public void setFullScreen(boolean isFullScreen) {
1280 setFullScreen(isFullScreen, false /* force */);
1281 }
1282
1283 /**
1284 * Changes the fullscreen mode of the in-call UI.
1285 *
1286 * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
1287 * otherwise.
1288 * @param force {@code true} if fullscreen mode should be set regardless of its current state.
1289 */
1290 public void setFullScreen(boolean isFullScreen, boolean force) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001291 LogUtil.i("InCallPresenter.setFullScreen", "setFullScreen = " + isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001292
1293 // As a safeguard, ensure we cannot enter fullscreen if the dialpad is shown.
1294 if (isDialpadVisible()) {
1295 isFullScreen = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001296 LogUtil.v(
1297 "InCallPresenter.setFullScreen",
1298 "setFullScreen overridden as dialpad is shown = " + isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001299 }
1300
linyuh183cb712017-12-27 17:02:37 -08001301 if (this.isFullScreen == isFullScreen && !force) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001302 LogUtil.v("InCallPresenter.setFullScreen", "setFullScreen ignored as already in that state.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001303 return;
1304 }
linyuh183cb712017-12-27 17:02:37 -08001305 this.isFullScreen = isFullScreen;
1306 notifyFullscreenModeChange(this.isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001307 }
1308
1309 /**
1310 * @return {@code true} if the in-call ui is currently in fullscreen mode, {@code false}
1311 * otherwise.
1312 */
1313 public boolean isFullscreen() {
linyuh183cb712017-12-27 17:02:37 -08001314 return isFullScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -08001315 }
1316
1317 /**
1318 * Called by the {@link VideoCallPresenter} to inform of a change in full screen video status.
1319 *
1320 * @param isFullscreenMode {@code True} if entering full screen mode.
1321 */
1322 public void notifyFullscreenModeChange(boolean isFullscreenMode) {
linyuh183cb712017-12-27 17:02:37 -08001323 for (InCallEventListener listener : inCallEventListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001324 listener.onFullscreenModeChanged(isFullscreenMode);
1325 }
1326 }
1327
linyuh7b86f562017-11-16 11:24:09 -08001328 /** Instruct the in-call activity to show an error dialog or toast for a disconnected call. */
1329 private void showDialogOrToastForDisconnectedCall(DialerCall call) {
1330 if (!isActivityStarted() || call.getState() != DialerCall.State.DISCONNECTED) {
1331 return;
Eric Erfanianccca3152017-02-22 16:32:36 -08001332 }
linyuh7b86f562017-11-16 11:24:09 -08001333
1334 // For newly disconnected calls, we may want to show a dialog on specific error conditions
1335 if (call.getAccountHandle() == null && !call.isConferenceCall()) {
1336 setDisconnectCauseForMissingAccounts(call);
1337 }
1338
linyuh183cb712017-12-27 17:02:37 -08001339 inCallActivity.showDialogOrToastForDisconnectedCall(
1340 new DisconnectMessage(inCallActivity, call));
Eric Erfanianccca3152017-02-22 16:32:36 -08001341 }
1342
1343 /**
1344 * When the state of in-call changes, this is the first method to get called. It determines if the
1345 * UI needs to be started or finished depending on the new state and does it.
1346 */
1347 private InCallState startOrFinishUi(InCallState newState) {
wangqicf61ca02017-08-31 15:32:55 -07001348 Trace.beginSection("InCallPresenter.startOrFinishUi");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001349 LogUtil.d(
linyuh183cb712017-12-27 17:02:37 -08001350 "InCallPresenter.startOrFinishUi", "startOrFinishUi: " + inCallState + " -> " + newState);
Eric Erfanianccca3152017-02-22 16:32:36 -08001351
1352 // TODO: Consider a proper state machine implementation
1353
1354 // If the state isn't changing we have already done any starting/stopping of activities in
1355 // a previous pass...so lets cut out early
linyuh183cb712017-12-27 17:02:37 -08001356 if (newState == inCallState) {
wangqicf61ca02017-08-31 15:32:55 -07001357 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001358 return newState;
1359 }
1360
1361 // A new Incoming call means that the user needs to be notified of the the call (since
1362 // it wasn't them who initiated it). We do this through full screen notifications and
1363 // happens indirectly through {@link StatusBarNotifier}.
1364 //
1365 // The process for incoming calls is as follows:
1366 //
1367 // 1) CallList - Announces existence of new INCOMING call
1368 // 2) InCallPresenter - Gets announcement and calculates that the new InCallState
1369 // - should be set to INCOMING.
1370 // 3) InCallPresenter - This method is called to see if we need to start or finish
1371 // the app given the new state.
1372 // 4) StatusBarNotifier - Listens to InCallState changes. InCallPresenter calls
1373 // StatusBarNotifier explicitly to issue a FullScreen Notification
1374 // that will either start the InCallActivity or show the user a
1375 // top-level notification dialog if the user is in an immersive app.
1376 // That notification can also start the InCallActivity.
1377 // 5) InCallActivity - Main activity starts up and at the end of its onCreate will
1378 // call InCallPresenter::setActivity() to let the presenter
1379 // know that start-up is complete.
1380 //
1381 // [ AND NOW YOU'RE IN THE CALL. voila! ]
Eric Erfanianccca3152017-02-22 16:32:36 -08001382
1383 // A dialog to show on top of the InCallUI to select a PhoneAccount
1384 final boolean showAccountPicker = (InCallState.WAITING_FOR_ACCOUNT == newState);
1385
1386 // A new outgoing call indicates that the user just now dialed a number and when that
1387 // happens we need to display the screen immediately or show an account picker dialog if
1388 // no default is set. However, if the main InCallUI is already visible, we do not want to
1389 // re-initiate the start-up animation, so we do not need to do anything here.
1390 //
1391 // It is also possible to go into an intermediate state where the call has been initiated
1392 // but Telecom has not yet returned with the details of the call (handle, gateway, etc.).
1393 // This pending outgoing state can also launch the call screen.
1394 //
1395 // This is different from the incoming call sequence because we do not need to shock the
1396 // user with a top-level notification. Just show the call UI normally.
1397 boolean callCardFragmentVisible =
linyuh183cb712017-12-27 17:02:37 -08001398 inCallActivity != null && inCallActivity.getCallCardFragmentVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -08001399 final boolean mainUiNotVisible = !isShowingInCallUi() || !callCardFragmentVisible;
1400 boolean showCallUi = InCallState.OUTGOING == newState && mainUiNotVisible;
1401
1402 // Direct transition from PENDING_OUTGOING -> INCALL means that there was an error in the
1403 // outgoing call process, so the UI should be brought up to show an error dialog.
1404 showCallUi |=
linyuh183cb712017-12-27 17:02:37 -08001405 (InCallState.PENDING_OUTGOING == inCallState
Eric Erfanianccca3152017-02-22 16:32:36 -08001406 && InCallState.INCALL == newState
1407 && !isShowingInCallUi());
1408
1409 // Another exception - InCallActivity is in charge of disconnecting a call with no
1410 // valid accounts set. Bring the UI up if this is true for the current pending outgoing
1411 // call so that:
1412 // 1) The call can be disconnected correctly
1413 // 2) The UI comes up and correctly displays the error dialog.
1414 // TODO: Remove these special case conditions by making InCallPresenter a true state
1415 // machine. Telecom should also be the component responsible for disconnecting a call
1416 // with no valid accounts.
1417 showCallUi |=
1418 InCallState.PENDING_OUTGOING == newState
1419 && mainUiNotVisible
linyuh183cb712017-12-27 17:02:37 -08001420 && isCallWithNoValidAccounts(callList.getPendingOutgoingCall());
Eric Erfanianccca3152017-02-22 16:32:36 -08001421
1422 // The only time that we have an instance of mInCallActivity and it isn't started is
1423 // when it is being destroyed. In that case, lets avoid bringing up another instance of
1424 // the activity. When it is finally destroyed, we double check if we should bring it back
1425 // up so we aren't going to lose anything by avoiding a second startup here.
linyuh183cb712017-12-27 17:02:37 -08001426 boolean activityIsFinishing = inCallActivity != null && !isActivityStarted();
Eric Erfanianccca3152017-02-22 16:32:36 -08001427 if (activityIsFinishing) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001428 LogUtil.i(
1429 "InCallPresenter.startOrFinishUi",
linyuh183cb712017-12-27 17:02:37 -08001430 "Undo the state change: " + newState + " -> " + inCallState);
wangqicf61ca02017-08-31 15:32:55 -07001431 Trace.endSection();
linyuh183cb712017-12-27 17:02:37 -08001432 return inCallState;
Eric Erfanianccca3152017-02-22 16:32:36 -08001433 }
1434
1435 // We're about the bring up the in-call UI for outgoing and incoming call. If we still have
1436 // dialogs up, we need to clear them out before showing in-call screen. This is necessary
1437 // to fix the bug that dialog will show up when data reaches limit even after makeing new
1438 // outgoing call after user ignore it by pressing home button.
1439 if ((newState == InCallState.INCOMING || newState == InCallState.PENDING_OUTGOING)
1440 && !showCallUi
1441 && isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -08001442 inCallActivity.dismissPendingDialogs();
Eric Erfanianccca3152017-02-22 16:32:36 -08001443 }
1444
1445 if (showCallUi || showAccountPicker) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001446 LogUtil.i("InCallPresenter.startOrFinishUi", "Start in call UI");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001447 showInCall(false /* showDialpad */, !showAccountPicker /* newOutgoingCall */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001448 } else if (newState == InCallState.NO_CALLS) {
1449 // The new state is the no calls state. Tear everything down.
1450 attemptFinishActivity();
1451 attemptCleanup();
1452 }
1453
wangqicf61ca02017-08-31 15:32:55 -07001454 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001455 return newState;
1456 }
1457
1458 /**
1459 * Sets the DisconnectCause for a call that was disconnected because it was missing a PhoneAccount
1460 * or PhoneAccounts to select from.
1461 */
1462 private void setDisconnectCauseForMissingAccounts(DialerCall call) {
1463
1464 Bundle extras = call.getIntentExtras();
1465 // Initialize the extras bundle to avoid NPE
1466 if (extras == null) {
1467 extras = new Bundle();
1468 }
1469
1470 final List<PhoneAccountHandle> phoneAccountHandles =
1471 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
1472
1473 if (phoneAccountHandles == null || phoneAccountHandles.isEmpty()) {
1474 String scheme = call.getHandle().getScheme();
1475 final String errorMsg =
1476 PhoneAccount.SCHEME_TEL.equals(scheme)
linyuh183cb712017-12-27 17:02:37 -08001477 ? context.getString(R.string.callFailed_simError)
1478 : context.getString(R.string.incall_error_supp_service_unknown);
Eric Erfanianccca3152017-02-22 16:32:36 -08001479 DisconnectCause disconnectCause =
1480 new DisconnectCause(DisconnectCause.ERROR, null, errorMsg, errorMsg);
1481 call.setDisconnectCause(disconnectCause);
1482 }
1483 }
1484
Eric Erfanianccca3152017-02-22 16:32:36 -08001485 /**
1486 * @return {@code true} if the InCallPresenter is ready to be torn down, {@code false} otherwise.
1487 * Calling classes should use this as an indication whether to interact with the
1488 * InCallPresenter or not.
1489 */
1490 public boolean isReadyForTearDown() {
linyuh183cb712017-12-27 17:02:37 -08001491 return inCallActivity == null && !serviceConnected && inCallState == InCallState.NO_CALLS;
Eric Erfanianccca3152017-02-22 16:32:36 -08001492 }
1493
1494 /**
1495 * Checks to see if both the UI is gone and the service is disconnected. If so, tear it all down.
1496 */
1497 private void attemptCleanup() {
1498 if (isReadyForTearDown()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001499 LogUtil.i("InCallPresenter.attemptCleanup", "Cleaning up");
Eric Erfanianccca3152017-02-22 16:32:36 -08001500
1501 cleanupSurfaces();
1502
linyuh183cb712017-12-27 17:02:37 -08001503 isChangingConfigurations = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001504
1505 // blow away stale contact info so that we get fresh data on
1506 // the next set of calls
linyuh183cb712017-12-27 17:02:37 -08001507 if (contactInfoCache != null) {
1508 contactInfoCache.clearCache();
Eric Erfanianccca3152017-02-22 16:32:36 -08001509 }
linyuh183cb712017-12-27 17:02:37 -08001510 contactInfoCache = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001511
linyuh183cb712017-12-27 17:02:37 -08001512 if (proximitySensor != null) {
1513 removeListener(proximitySensor);
1514 proximitySensor.tearDown();
Eric Erfanianccca3152017-02-22 16:32:36 -08001515 }
linyuh183cb712017-12-27 17:02:37 -08001516 proximitySensor = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001517
linyuh183cb712017-12-27 17:02:37 -08001518 if (statusBarNotifier != null) {
1519 removeListener(statusBarNotifier);
1520 EnrichedCallComponent.get(context)
Eric Erfaniand8046e52017-04-06 09:41:50 -07001521 .getEnrichedCallManager()
linyuh183cb712017-12-27 17:02:37 -08001522 .unregisterStateChangedListener(statusBarNotifier);
Eric Erfanianccca3152017-02-22 16:32:36 -08001523 }
Eric Erfaniand8046e52017-04-06 09:41:50 -07001524
linyuh183cb712017-12-27 17:02:37 -08001525 if (externalCallNotifier != null && externalCallList != null) {
1526 externalCallList.removeExternalCallListener(externalCallNotifier);
Eric Erfanianccca3152017-02-22 16:32:36 -08001527 }
linyuh183cb712017-12-27 17:02:37 -08001528 statusBarNotifier = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001529
linyuh183cb712017-12-27 17:02:37 -08001530 if (callList != null) {
1531 callList.removeListener(this);
1532 callList.removeListener(spamCallListListener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001533 }
linyuh183cb712017-12-27 17:02:37 -08001534 callList = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001535
linyuh183cb712017-12-27 17:02:37 -08001536 context = null;
1537 inCallActivity = null;
1538 manageConferenceActivity = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001539
linyuh183cb712017-12-27 17:02:37 -08001540 listeners.clear();
1541 incomingCallListeners.clear();
1542 detailsListeners.clear();
1543 canAddCallListeners.clear();
1544 orientationListeners.clear();
1545 inCallEventListeners.clear();
1546 inCallUiListeners.clear();
1547 if (!inCallUiLocks.isEmpty()) {
1548 LogUtil.e("InCallPresenter.attemptCleanup", "held in call locks: " + inCallUiLocks);
1549 inCallUiLocks.clear();
twyen8efb4952017-10-06 16:35:54 -07001550 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001551 LogUtil.d("InCallPresenter.attemptCleanup", "finished");
Eric Erfanianccca3152017-02-22 16:32:36 -08001552 }
1553 }
1554
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001555 public void showInCall(boolean showDialpad, boolean newOutgoingCall) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001556 LogUtil.i("InCallPresenter.showInCall", "Showing InCallActivity");
linyuh183cb712017-12-27 17:02:37 -08001557 context.startActivity(
1558 InCallActivity.getIntent(context, showDialpad, newOutgoingCall, false /* forFullScreen */));
Eric Erfanianccca3152017-02-22 16:32:36 -08001559 }
1560
1561 public void onServiceBind() {
linyuh183cb712017-12-27 17:02:37 -08001562 serviceBound = true;
Eric Erfanianccca3152017-02-22 16:32:36 -08001563 }
1564
1565 public void onServiceUnbind() {
1566 InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(false, null);
linyuh183cb712017-12-27 17:02:37 -08001567 serviceBound = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001568 }
1569
1570 public boolean isServiceBound() {
linyuh183cb712017-12-27 17:02:37 -08001571 return serviceBound;
Eric Erfanianccca3152017-02-22 16:32:36 -08001572 }
1573
1574 public void maybeStartRevealAnimation(Intent intent) {
linyuh183cb712017-12-27 17:02:37 -08001575 if (intent == null || inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001576 return;
1577 }
1578 final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
1579 if (extras == null) {
1580 // Incoming call, just show the in-call UI directly.
1581 return;
1582 }
1583
1584 if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) {
1585 // Account selection dialog will show up so don't show the animation.
1586 return;
1587 }
1588
1589 final PhoneAccountHandle accountHandle =
1590 intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
1591 final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT);
Eric Erfanianccca3152017-02-22 16:32:36 -08001592
1593 InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(true, accountHandle);
1594
1595 final Intent activityIntent =
linyuh183cb712017-12-27 17:02:37 -08001596 InCallActivity.getIntent(context, false, true, false /* forFullScreen */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001597 activityIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint);
linyuh183cb712017-12-27 17:02:37 -08001598 context.startActivity(activityIntent);
Eric Erfanianccca3152017-02-22 16:32:36 -08001599 }
1600
1601 /**
1602 * Retrieves the current in-call camera manager instance, creating if necessary.
1603 *
1604 * @return The {@link InCallCameraManager}.
1605 */
1606 public InCallCameraManager getInCallCameraManager() {
1607 synchronized (this) {
linyuh183cb712017-12-27 17:02:37 -08001608 if (inCallCameraManager == null) {
1609 inCallCameraManager = new InCallCameraManager(context);
Eric Erfanianccca3152017-02-22 16:32:36 -08001610 }
1611
linyuh183cb712017-12-27 17:02:37 -08001612 return inCallCameraManager;
Eric Erfanianccca3152017-02-22 16:32:36 -08001613 }
1614 }
1615
1616 /**
1617 * Notifies listeners of changes in orientation and notify calls of rotation angle change.
1618 *
1619 * @param orientation The screen orientation of the device (one of: {@link
1620 * InCallOrientationEventListener#SCREEN_ORIENTATION_0}, {@link
1621 * InCallOrientationEventListener#SCREEN_ORIENTATION_90}, {@link
1622 * InCallOrientationEventListener#SCREEN_ORIENTATION_180}, {@link
1623 * InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
1624 */
1625 public void onDeviceOrientationChange(@ScreenOrientation int orientation) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001626 LogUtil.d(
1627 "InCallPresenter.onDeviceOrientationChange",
1628 "onDeviceOrientationChange: orientation= " + orientation);
Eric Erfanianccca3152017-02-22 16:32:36 -08001629
linyuh183cb712017-12-27 17:02:37 -08001630 if (callList != null) {
1631 callList.notifyCallsOfDeviceRotation(orientation);
Eric Erfanianccca3152017-02-22 16:32:36 -08001632 } else {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001633 LogUtil.w("InCallPresenter.onDeviceOrientationChange", "CallList is null.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001634 }
1635
1636 // Notify listeners of device orientation changed.
linyuh183cb712017-12-27 17:02:37 -08001637 for (InCallOrientationListener listener : orientationListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001638 listener.onDeviceOrientationChanged(orientation);
1639 }
1640 }
1641
1642 /**
1643 * Configures the in-call UI activity so it can change orientations or not. Enables the
1644 * orientation event listener if allowOrientationChange is true, disables it if false.
1645 *
1646 * @param allowOrientationChange {@code true} if the in-call UI can change between portrait and
1647 * landscape. {@code false} if the in-call UI should be locked in portrait.
1648 */
1649 public void setInCallAllowsOrientationChange(boolean allowOrientationChange) {
linyuh183cb712017-12-27 17:02:37 -08001650 if (inCallActivity == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001651 LogUtil.e(
1652 "InCallPresenter.setInCallAllowsOrientationChange",
1653 "InCallActivity is null. Can't set requested orientation.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001654 return;
1655 }
linyuh183cb712017-12-27 17:02:37 -08001656 inCallActivity.setAllowOrientationChange(allowOrientationChange);
Eric Erfanianccca3152017-02-22 16:32:36 -08001657 }
1658
1659 public void enableScreenTimeout(boolean enable) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001660 LogUtil.v("InCallPresenter.enableScreenTimeout", "enableScreenTimeout: value=" + enable);
linyuh183cb712017-12-27 17:02:37 -08001661 screenTimeoutEnabled = enable;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001662 applyScreenTimeout();
1663 }
1664
1665 private void applyScreenTimeout() {
linyuh183cb712017-12-27 17:02:37 -08001666 if (inCallActivity == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001667 LogUtil.e("InCallPresenter.applyScreenTimeout", "InCallActivity is null.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001668 return;
1669 }
1670
linyuh183cb712017-12-27 17:02:37 -08001671 final Window window = inCallActivity.getWindow();
1672 if (screenTimeoutEnabled) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001673 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1674 } else {
1675 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1676 }
1677 }
1678
1679 /**
1680 * Hides or shows the conference manager fragment.
1681 *
1682 * @param show {@code true} if the conference manager should be shown, {@code false} if it should
1683 * be hidden.
1684 */
1685 public void showConferenceCallManager(boolean show) {
linyuh183cb712017-12-27 17:02:37 -08001686 if (inCallActivity != null) {
1687 inCallActivity.showConferenceFragment(show);
Eric Erfanianccca3152017-02-22 16:32:36 -08001688 }
linyuh183cb712017-12-27 17:02:37 -08001689 if (!show && manageConferenceActivity != null) {
1690 manageConferenceActivity.finish();
Eric Erfanianccca3152017-02-22 16:32:36 -08001691 }
1692 }
1693
1694 /**
1695 * Determines if the dialpad is visible.
1696 *
1697 * @return {@code true} if the dialpad is visible, {@code false} otherwise.
1698 */
1699 public boolean isDialpadVisible() {
linyuh183cb712017-12-27 17:02:37 -08001700 if (inCallActivity == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001701 return false;
1702 }
linyuh183cb712017-12-27 17:02:37 -08001703 return inCallActivity.isDialpadVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -08001704 }
1705
1706 public ThemeColorManager getThemeColorManager() {
linyuh183cb712017-12-27 17:02:37 -08001707 return themeColorManager;
Eric Erfanianccca3152017-02-22 16:32:36 -08001708 }
1709
wangqi8d662ca2017-10-26 11:27:19 -07001710 @VisibleForTesting
1711 public void setThemeColorManager(ThemeColorManager themeColorManager) {
linyuh183cb712017-12-27 17:02:37 -08001712 this.themeColorManager = themeColorManager;
wangqi8d662ca2017-10-26 11:27:19 -07001713 }
1714
Eric Erfanianccca3152017-02-22 16:32:36 -08001715 /** Called when the foreground call changes. */
1716 public void onForegroundCallChanged(DialerCall newForegroundCall) {
linyuh183cb712017-12-27 17:02:37 -08001717 themeColorManager.onForegroundCallChanged(context, newForegroundCall);
1718 if (inCallActivity != null) {
1719 inCallActivity.onForegroundCallChanged(newForegroundCall);
Eric Erfanianccca3152017-02-22 16:32:36 -08001720 }
1721 }
1722
1723 public InCallActivity getActivity() {
linyuh183cb712017-12-27 17:02:37 -08001724 return inCallActivity;
Eric Erfanianccca3152017-02-22 16:32:36 -08001725 }
1726
1727 /** Called when the UI begins, and starts the callstate callbacks if necessary. */
1728 public void setActivity(InCallActivity inCallActivity) {
1729 if (inCallActivity == null) {
1730 throw new IllegalArgumentException("registerActivity cannot be called with null");
1731 }
linyuh183cb712017-12-27 17:02:37 -08001732 if (this.inCallActivity != null && this.inCallActivity != inCallActivity) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001733 LogUtil.w(
1734 "InCallPresenter.setActivity", "Setting a second activity before destroying the first.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001735 }
1736 updateActivity(inCallActivity);
1737 }
1738
1739 ExternalCallNotifier getExternalCallNotifier() {
linyuh183cb712017-12-27 17:02:37 -08001740 return externalCallNotifier;
Eric Erfanianccca3152017-02-22 16:32:36 -08001741 }
1742
1743 VideoSurfaceTexture getLocalVideoSurfaceTexture() {
linyuh183cb712017-12-27 17:02:37 -08001744 if (localVideoSurfaceTexture == null) {
roldenburg12b50c62017-09-01 14:41:19 -07001745 boolean isPixel2017 = false;
linyuh183cb712017-12-27 17:02:37 -08001746 if (context != null) {
1747 isPixel2017 = context.getPackageManager().hasSystemFeature(PIXEL2017_SYSTEM_FEATURE);
roldenburg12b50c62017-09-01 14:41:19 -07001748 }
linyuh183cb712017-12-27 17:02:37 -08001749 localVideoSurfaceTexture = VideoSurfaceBindings.createLocalVideoSurfaceTexture(isPixel2017);
Eric Erfanianccca3152017-02-22 16:32:36 -08001750 }
linyuh183cb712017-12-27 17:02:37 -08001751 return localVideoSurfaceTexture;
Eric Erfanianccca3152017-02-22 16:32:36 -08001752 }
1753
1754 VideoSurfaceTexture getRemoteVideoSurfaceTexture() {
linyuh183cb712017-12-27 17:02:37 -08001755 if (remoteVideoSurfaceTexture == null) {
roldenburg12b50c62017-09-01 14:41:19 -07001756 boolean isPixel2017 = false;
linyuh183cb712017-12-27 17:02:37 -08001757 if (context != null) {
1758 isPixel2017 = context.getPackageManager().hasSystemFeature(PIXEL2017_SYSTEM_FEATURE);
roldenburg12b50c62017-09-01 14:41:19 -07001759 }
linyuh183cb712017-12-27 17:02:37 -08001760 remoteVideoSurfaceTexture = VideoSurfaceBindings.createRemoteVideoSurfaceTexture(isPixel2017);
Eric Erfanianccca3152017-02-22 16:32:36 -08001761 }
linyuh183cb712017-12-27 17:02:37 -08001762 return remoteVideoSurfaceTexture;
Eric Erfanianccca3152017-02-22 16:32:36 -08001763 }
1764
1765 void cleanupSurfaces() {
linyuh183cb712017-12-27 17:02:37 -08001766 if (remoteVideoSurfaceTexture != null) {
1767 remoteVideoSurfaceTexture.setDoneWithSurface();
1768 remoteVideoSurfaceTexture = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001769 }
linyuh183cb712017-12-27 17:02:37 -08001770 if (localVideoSurfaceTexture != null) {
1771 localVideoSurfaceTexture.setDoneWithSurface();
1772 localVideoSurfaceTexture = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001773 }
1774 }
1775
yueg77cb8e52017-10-27 16:42:51 -07001776 @Override
1777 public void onAudioStateChanged(CallAudioState audioState) {
linyuh183cb712017-12-27 17:02:37 -08001778 if (statusBarNotifier != null) {
1779 statusBarNotifier.updateNotification();
yueg77cb8e52017-10-27 16:42:51 -07001780 }
1781 }
1782
Eric Erfanianccca3152017-02-22 16:32:36 -08001783 /** All the main states of InCallActivity. */
1784 public enum InCallState {
1785 // InCall Screen is off and there are no calls
1786 NO_CALLS,
1787
1788 // Incoming-call screen is up
1789 INCOMING,
1790
1791 // In-call experience is showing
1792 INCALL,
1793
1794 // Waiting for user input before placing outgoing call
1795 WAITING_FOR_ACCOUNT,
1796
1797 // UI is starting up but no call has been initiated yet.
1798 // The UI is waiting for Telecom to respond.
1799 PENDING_OUTGOING,
1800
1801 // User is dialing out
1802 OUTGOING;
1803
1804 public boolean isIncoming() {
1805 return (this == INCOMING);
1806 }
1807
1808 public boolean isConnectingOrConnected() {
1809 return (this == INCOMING || this == OUTGOING || this == INCALL);
1810 }
1811 }
1812
1813 /** Interface implemented by classes that need to know about the InCall State. */
1814 public interface InCallStateListener {
1815
1816 // TODO: Enhance state to contain the call objects instead of passing CallList
1817 void onStateChange(InCallState oldState, InCallState newState, CallList callList);
1818 }
1819
1820 public interface IncomingCallListener {
1821
1822 void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call);
1823 }
1824
1825 public interface CanAddCallListener {
1826
1827 void onCanAddCallChanged(boolean canAddCall);
1828 }
1829
1830 public interface InCallDetailsListener {
1831
1832 void onDetailsChanged(DialerCall call, android.telecom.Call.Details details);
1833 }
1834
1835 public interface InCallOrientationListener {
1836
1837 void onDeviceOrientationChanged(@ScreenOrientation int orientation);
1838 }
1839
1840 /**
1841 * Interface implemented by classes that need to know about events which occur within the In-Call
1842 * UI. Used as a means of communicating between fragments that make up the UI.
1843 */
1844 public interface InCallEventListener {
1845
1846 void onFullscreenModeChanged(boolean isFullscreenMode);
1847 }
1848
1849 public interface InCallUiListener {
1850
1851 void onUiShowing(boolean showing);
1852 }
twyen8efb4952017-10-06 16:35:54 -07001853
1854 private class InCallUiLockImpl implements InCallUiLock {
1855 private final String tag;
1856
1857 private InCallUiLockImpl(String tag) {
1858 this.tag = tag;
1859 }
1860
1861 @MainThread
1862 @Override
1863 public void release() {
1864 Assert.isMainThread();
1865 releaseInCallUiLock(InCallUiLockImpl.this);
1866 }
1867
1868 @Override
1869 public String toString() {
1870 return "InCallUiLock[" + tag + "]";
1871 }
1872 }
1873
1874 @MainThread
1875 public InCallUiLock acquireInCallUiLock(String tag) {
1876 Assert.isMainThread();
1877 InCallUiLock lock = new InCallUiLockImpl(tag);
linyuh183cb712017-12-27 17:02:37 -08001878 inCallUiLocks.add(lock);
twyen8efb4952017-10-06 16:35:54 -07001879 return lock;
1880 }
1881
1882 @MainThread
1883 private void releaseInCallUiLock(InCallUiLock lock) {
1884 Assert.isMainThread();
1885 LogUtil.i("InCallPresenter.releaseInCallUiLock", "releasing %s", lock);
linyuh183cb712017-12-27 17:02:37 -08001886 inCallUiLocks.remove(lock);
1887 if (inCallUiLocks.isEmpty()) {
twyen8efb4952017-10-06 16:35:54 -07001888 LogUtil.i("InCallPresenter.releaseInCallUiLock", "all locks released");
linyuh183cb712017-12-27 17:02:37 -08001889 if (inCallState == InCallState.NO_CALLS) {
twyen8efb4952017-10-06 16:35:54 -07001890 LogUtil.i("InCallPresenter.releaseInCallUiLock", "no more calls, finishing UI");
1891 attemptFinishActivity();
1892 attemptCleanup();
1893 }
1894 }
1895 }
1896
1897 @MainThread
1898 public boolean isInCallUiLocked() {
1899 Assert.isMainThread();
linyuh183cb712017-12-27 17:02:37 -08001900 if (inCallUiLocks.isEmpty()) {
twyen8efb4952017-10-06 16:35:54 -07001901 return false;
1902 }
linyuh183cb712017-12-27 17:02:37 -08001903 for (InCallUiLock lock : inCallUiLocks) {
twyen8efb4952017-10-06 16:35:54 -07001904 LogUtil.i("InCallPresenter.isInCallUiLocked", "still locked by %s", lock);
1905 }
1906 return true;
1907 }
1908
linyuh183cb712017-12-27 17:02:37 -08001909 private final Set<InCallUiLock> inCallUiLocks = new ArraySet<>();
Eric Erfanianccca3152017-02-22 16:32:36 -08001910}