blob: 2e98a969de7e6cdb84306c50657c1f5843bbcf79 [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
774 // notify listeners of new state
linyuh183cb712017-12-27 17:02:37 -0800775 for (InCallStateListener listener : listeners) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700776 LogUtil.d(
777 "InCallPresenter.onCallListChange",
linyuh183cb712017-12-27 17:02:37 -0800778 "Notify " + listener + " of state " + inCallState.toString());
779 listener.onStateChange(oldState, inCallState, callList);
Eric Erfanianccca3152017-02-22 16:32:36 -0800780 }
781
782 if (isActivityStarted()) {
783 final boolean hasCall =
784 callList.getActiveOrBackgroundCall() != null || callList.getOutgoingCall() != null;
linyuh183cb712017-12-27 17:02:37 -0800785 inCallActivity.dismissKeyguard(hasCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800786 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700787 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800788 }
789
790 /** Called when there is a new incoming call. */
791 @Override
792 public void onIncomingCall(DialerCall call) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700793 Trace.beginSection("InCallPresenter.onIncomingCall");
Eric Erfanianccca3152017-02-22 16:32:36 -0800794 InCallState newState = startOrFinishUi(InCallState.INCOMING);
linyuh183cb712017-12-27 17:02:37 -0800795 InCallState oldState = inCallState;
Eric Erfanianccca3152017-02-22 16:32:36 -0800796
Eric Erfanian2ca43182017-08-31 06:57:16 -0700797 LogUtil.i(
798 "InCallPresenter.onIncomingCall", "Phone switching state: " + oldState + " -> " + newState);
linyuh183cb712017-12-27 17:02:37 -0800799 inCallState = newState;
Eric Erfanianccca3152017-02-22 16:32:36 -0800800
wangqicf61ca02017-08-31 15:32:55 -0700801 Trace.beginSection("listener.onIncomingCall");
linyuh183cb712017-12-27 17:02:37 -0800802 for (IncomingCallListener listener : incomingCallListeners) {
803 listener.onIncomingCall(oldState, inCallState, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800804 }
wangqicf61ca02017-08-31 15:32:55 -0700805 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800806
wangqicf61ca02017-08-31 15:32:55 -0700807 Trace.beginSection("onPrimaryCallStateChanged");
linyuh183cb712017-12-27 17:02:37 -0800808 if (inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800809 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -0800810 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800811 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700812 Trace.endSection();
wangqicf61ca02017-08-31 15:32:55 -0700813 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800814 }
815
816 @Override
817 public void onUpgradeToVideo(DialerCall call) {
Eric Erfanian90508232017-03-24 09:31:16 -0700818 if (VideoUtils.hasReceivedVideoUpgradeRequest(call.getVideoTech().getSessionModificationState())
linyuh183cb712017-12-27 17:02:37 -0800819 && inCallState == InCallPresenter.InCallState.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800820 LogUtil.i(
821 "InCallPresenter.onUpgradeToVideo",
822 "rejecting upgrade request due to existing incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700823 call.getVideoTech().declineVideoRequest();
Eric Erfanianccca3152017-02-22 16:32:36 -0800824 }
825
linyuh183cb712017-12-27 17:02:37 -0800826 if (inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800827 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -0800828 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800829 }
830 }
831
832 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700833 public void onSessionModificationStateChange(DialerCall call) {
834 int newState = call.getVideoTech().getSessionModificationState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800835 LogUtil.i("InCallPresenter.onSessionModificationStateChange", "state: %d", newState);
linyuh183cb712017-12-27 17:02:37 -0800836 if (proximitySensor == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800837 LogUtil.i("InCallPresenter.onSessionModificationStateChange", "proximitySensor is null");
838 return;
839 }
linyuh183cb712017-12-27 17:02:37 -0800840 proximitySensor.setIsAttemptingVideoCall(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700841 call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest());
linyuh183cb712017-12-27 17:02:37 -0800842 if (inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800843 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -0800844 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800845 }
846 }
847
848 /**
849 * Called when a call becomes disconnected. Called everytime an existing call changes from being
850 * connected (incoming/outgoing/active) to disconnected.
851 */
852 @Override
853 public void onDisconnect(DialerCall call) {
linyuh7b86f562017-11-16 11:24:09 -0800854 showDialogOrToastForDisconnectedCall(call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800855
856 // We need to do the run the same code as onCallListChange.
linyuh183cb712017-12-27 17:02:37 -0800857 onCallListChange(callList);
Eric Erfanianccca3152017-02-22 16:32:36 -0800858
859 if (isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -0800860 inCallActivity.dismissKeyguard(false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800861 }
862
863 if (call.isEmergencyCall()) {
linyuh183cb712017-12-27 17:02:37 -0800864 FilteredNumbersUtil.recordLastEmergencyCallTime(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800865 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800866
linyuh183cb712017-12-27 17:02:37 -0800867 if (!callList.hasLiveCall()
Eric Erfanianfc37b022017-03-21 10:11:17 -0700868 && !call.getLogState().isIncoming
Eric Erfanian10b34a52017-05-04 08:23:17 -0700869 && !isSecretCode(call.getNumber())
wangqi9982f0d2017-10-11 17:46:07 -0700870 && !call.isVoiceMailNumber()) {
linyuh183cb712017-12-27 17:02:37 -0800871 PostCall.onCallDisconnected(context, call.getNumber(), call.getConnectTimeMillis());
Eric Erfanianccca3152017-02-22 16:32:36 -0800872 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800873 }
874
Eric Erfanian10b34a52017-05-04 08:23:17 -0700875 private boolean isSecretCode(@Nullable String number) {
876 return number != null
877 && (number.length() <= 8 || number.startsWith("*#*#") || number.endsWith("#*#*"));
878 }
879
Eric Erfanianccca3152017-02-22 16:32:36 -0800880 /** Given the call list, return the state in which the in-call screen should be. */
881 public InCallState getPotentialStateFromCallList(CallList callList) {
882
883 InCallState newState = InCallState.NO_CALLS;
884
885 if (callList == null) {
886 return newState;
887 }
888 if (callList.getIncomingCall() != null) {
889 newState = InCallState.INCOMING;
890 } else if (callList.getWaitingForAccountCall() != null) {
891 newState = InCallState.WAITING_FOR_ACCOUNT;
892 } else if (callList.getPendingOutgoingCall() != null) {
893 newState = InCallState.PENDING_OUTGOING;
894 } else if (callList.getOutgoingCall() != null) {
895 newState = InCallState.OUTGOING;
896 } else if (callList.getActiveCall() != null
897 || callList.getBackgroundCall() != null
898 || callList.getDisconnectedCall() != null
899 || callList.getDisconnectingCall() != null) {
900 newState = InCallState.INCALL;
901 }
902
903 if (newState == InCallState.NO_CALLS) {
linyuh183cb712017-12-27 17:02:37 -0800904 if (boundAndWaitingForOutgoingCall) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700905 return InCallState.PENDING_OUTGOING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800906 }
907 }
908
909 return newState;
910 }
911
912 public boolean isBoundAndWaitingForOutgoingCall() {
linyuh183cb712017-12-27 17:02:37 -0800913 return boundAndWaitingForOutgoingCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800914 }
915
916 public void setBoundAndWaitingForOutgoingCall(boolean isBound, PhoneAccountHandle handle) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700917 LogUtil.i(
918 "InCallPresenter.setBoundAndWaitingForOutgoingCall",
919 "setBoundAndWaitingForOutgoingCall: " + isBound);
linyuh183cb712017-12-27 17:02:37 -0800920 boundAndWaitingForOutgoingCall = isBound;
921 themeColorManager.setPendingPhoneAccountHandle(handle);
922 if (isBound && inCallState == InCallState.NO_CALLS) {
923 inCallState = InCallState.PENDING_OUTGOING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800924 }
925 }
926
927 public void onShrinkAnimationComplete() {
linyuh183cb712017-12-27 17:02:37 -0800928 if (awaitingCallListUpdate) {
929 onCallListChange(callList);
Eric Erfanianccca3152017-02-22 16:32:36 -0800930 }
931 }
932
933 public void addIncomingCallListener(IncomingCallListener listener) {
934 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -0800935 incomingCallListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800936 }
937
938 public void removeIncomingCallListener(IncomingCallListener listener) {
939 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -0800940 incomingCallListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800941 }
942 }
943
944 public void addListener(InCallStateListener listener) {
945 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -0800946 listeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800947 }
948
949 public void removeListener(InCallStateListener listener) {
950 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -0800951 listeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800952 }
953 }
954
955 public void addDetailsListener(InCallDetailsListener listener) {
956 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -0800957 detailsListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800958 }
959
960 public void removeDetailsListener(InCallDetailsListener listener) {
961 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -0800962 detailsListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800963 }
964 }
965
966 public void addCanAddCallListener(CanAddCallListener listener) {
967 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -0800968 canAddCallListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800969 }
970
971 public void removeCanAddCallListener(CanAddCallListener listener) {
972 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -0800973 canAddCallListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800974 }
975 }
976
977 public void addOrientationListener(InCallOrientationListener listener) {
978 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -0800979 orientationListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800980 }
981
982 public void removeOrientationListener(InCallOrientationListener listener) {
983 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -0800984 orientationListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800985 }
986 }
987
988 public void addInCallEventListener(InCallEventListener listener) {
989 Objects.requireNonNull(listener);
linyuh183cb712017-12-27 17:02:37 -0800990 inCallEventListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800991 }
992
993 public void removeInCallEventListener(InCallEventListener listener) {
994 if (listener != null) {
linyuh183cb712017-12-27 17:02:37 -0800995 inCallEventListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -0800996 }
997 }
998
999 public ProximitySensor getProximitySensor() {
linyuh183cb712017-12-27 17:02:37 -08001000 return proximitySensor;
Eric Erfanianccca3152017-02-22 16:32:36 -08001001 }
1002
1003 public PseudoScreenState getPseudoScreenState() {
linyuh183cb712017-12-27 17:02:37 -08001004 return pseudoScreenState;
Eric Erfanianccca3152017-02-22 16:32:36 -08001005 }
1006
1007 /** Returns true if the incall app is the foreground application. */
1008 public boolean isShowingInCallUi() {
1009 if (!isActivityStarted()) {
1010 return false;
1011 }
linyuh183cb712017-12-27 17:02:37 -08001012 if (manageConferenceActivity != null && manageConferenceActivity.isVisible()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001013 return true;
1014 }
linyuh183cb712017-12-27 17:02:37 -08001015 return inCallActivity.isVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -08001016 }
1017
1018 /**
1019 * Returns true if the activity has been created and is running. Returns true as long as activity
1020 * is not destroyed or finishing. This ensures that we return true even if the activity is paused
1021 * (not in foreground).
1022 */
1023 public boolean isActivityStarted() {
linyuh183cb712017-12-27 17:02:37 -08001024 return (inCallActivity != null
1025 && !inCallActivity.isDestroyed()
1026 && !inCallActivity.isFinishing());
Eric Erfanianccca3152017-02-22 16:32:36 -08001027 }
1028
1029 /**
1030 * Determines if the In-Call app is currently changing configuration.
1031 *
1032 * @return {@code true} if the In-Call app is changing configuration.
1033 */
1034 public boolean isChangingConfigurations() {
linyuh183cb712017-12-27 17:02:37 -08001035 return isChangingConfigurations;
Eric Erfanianccca3152017-02-22 16:32:36 -08001036 }
1037
1038 /**
1039 * Tracks whether the In-Call app is currently in the process of changing configuration (i.e.
1040 * screen orientation).
1041 */
1042 /*package*/
1043 void updateIsChangingConfigurations() {
linyuh183cb712017-12-27 17:02:37 -08001044 isChangingConfigurations = false;
1045 if (inCallActivity != null) {
1046 isChangingConfigurations = inCallActivity.isChangingConfigurations();
Eric Erfanianccca3152017-02-22 16:32:36 -08001047 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001048 LogUtil.v(
1049 "InCallPresenter.updateIsChangingConfigurations",
linyuh183cb712017-12-27 17:02:37 -08001050 "updateIsChangingConfigurations = " + isChangingConfigurations);
Eric Erfanianccca3152017-02-22 16:32:36 -08001051 }
1052
yueg10f6e822018-01-17 15:32:18 -08001053 /** Called when the activity goes in/out of the foreground. */
1054 public void onUiShowing(boolean showing) {
linyuh183cb712017-12-27 17:02:37 -08001055 if (proximitySensor != null) {
1056 proximitySensor.onInCallShowing(showing);
Eric Erfanianccca3152017-02-22 16:32:36 -08001057 }
1058
yueg092b21c2017-11-15 16:20:07 -08001059 if (!showing) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001060 updateIsChangingConfigurations();
1061 }
1062
linyuh183cb712017-12-27 17:02:37 -08001063 for (InCallUiListener listener : inCallUiListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001064 listener.onUiShowing(showing);
1065 }
1066
linyuh183cb712017-12-27 17:02:37 -08001067 if (inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001068 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -08001069 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -08001070 }
1071 }
1072
Eric Erfanian2ca43182017-08-31 06:57:16 -07001073 public void refreshUi() {
linyuh183cb712017-12-27 17:02:37 -08001074 if (inCallActivity != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001075 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -08001076 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001077 }
1078 }
1079
Eric Erfanianccca3152017-02-22 16:32:36 -08001080 public void addInCallUiListener(InCallUiListener listener) {
linyuh183cb712017-12-27 17:02:37 -08001081 inCallUiListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001082 }
1083
1084 public boolean removeInCallUiListener(InCallUiListener listener) {
linyuh183cb712017-12-27 17:02:37 -08001085 return inCallUiListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001086 }
1087
1088 /*package*/
1089 void onActivityStarted() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001090 LogUtil.d("InCallPresenter.onActivityStarted", "onActivityStarted");
Eric Erfanianccca3152017-02-22 16:32:36 -08001091 notifyVideoPauseController(true);
Eric Erfanian2ca43182017-08-31 06:57:16 -07001092 applyScreenTimeout();
Eric Erfanianccca3152017-02-22 16:32:36 -08001093 }
1094
1095 /*package*/
1096 void onActivityStopped() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001097 LogUtil.d("InCallPresenter.onActivityStopped", "onActivityStopped");
Eric Erfanianccca3152017-02-22 16:32:36 -08001098 notifyVideoPauseController(false);
1099 }
1100
1101 private void notifyVideoPauseController(boolean showing) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001102 LogUtil.d(
1103 "InCallPresenter.notifyVideoPauseController",
linyuh183cb712017-12-27 17:02:37 -08001104 "mIsChangingConfigurations=" + isChangingConfigurations);
1105 if (!isChangingConfigurations) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001106 VideoPauseController.getInstance().onUiShowing(showing);
1107 }
1108 }
1109
1110 /** Brings the app into the foreground if possible. */
1111 public void bringToForeground(boolean showDialpad) {
1112 // Before we bring the incall UI to the foreground, we check to see if:
1113 // 1. It is not currently in the foreground
1114 // 2. We are in a state where we want to show the incall ui (i.e. there are calls to
1115 // be displayed)
1116 // If the activity hadn't actually been started previously, yet there are still calls
1117 // present (e.g. a call was accepted by a bluetooth or wired headset), we want to
1118 // bring it up the UI regardless.
linyuh183cb712017-12-27 17:02:37 -08001119 if (!isShowingInCallUi() && inCallState != InCallState.NO_CALLS) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001120 showInCall(showDialpad, false /* newOutgoingCall */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001121 }
1122 }
1123
1124 public void onPostDialCharWait(String callId, String chars) {
1125 if (isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -08001126 inCallActivity.showDialogForPostCharWait(callId, chars);
Eric Erfanianccca3152017-02-22 16:32:36 -08001127 }
1128 }
1129
1130 /**
1131 * Handles the green CALL key while in-call.
1132 *
1133 * @return true if we consumed the event.
1134 */
1135 public boolean handleCallKey() {
1136 LogUtil.v("InCallPresenter.handleCallKey", null);
1137
1138 // The green CALL button means either "Answer", "Unhold", or
1139 // "Swap calls", or can be a no-op, depending on the current state
1140 // of the Phone.
1141
1142 /** INCOMING CALL */
linyuh183cb712017-12-27 17:02:37 -08001143 final CallList calls = callList;
Eric Erfanianccca3152017-02-22 16:32:36 -08001144 final DialerCall incomingCall = calls.getIncomingCall();
1145 LogUtil.v("InCallPresenter.handleCallKey", "incomingCall: " + incomingCall);
1146
1147 // (1) Attempt to answer a call
1148 if (incomingCall != null) {
1149 incomingCall.answer(VideoProfile.STATE_AUDIO_ONLY);
1150 return true;
1151 }
1152
1153 /** STATE_ACTIVE CALL */
1154 final DialerCall activeCall = calls.getActiveCall();
1155 if (activeCall != null) {
1156 // TODO: This logic is repeated from CallButtonPresenter.java. We should
1157 // consolidate this logic.
1158 final boolean canMerge =
1159 activeCall.can(android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
1160 final boolean canSwap =
1161 activeCall.can(android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE);
1162
Eric Erfanian2ca43182017-08-31 06:57:16 -07001163 LogUtil.v(
1164 "InCallPresenter.handleCallKey",
1165 "activeCall: " + activeCall + ", canMerge: " + canMerge + ", canSwap: " + canSwap);
Eric Erfanianccca3152017-02-22 16:32:36 -08001166
1167 // (2) Attempt actions on conference calls
1168 if (canMerge) {
1169 TelecomAdapter.getInstance().merge(activeCall.getId());
1170 return true;
1171 } else if (canSwap) {
1172 TelecomAdapter.getInstance().swap(activeCall.getId());
1173 return true;
1174 }
1175 }
1176
1177 /** BACKGROUND CALL */
1178 final DialerCall heldCall = calls.getBackgroundCall();
1179 if (heldCall != null) {
1180 // We have a hold call so presumeable it will always support HOLD...but
1181 // there is no harm in double checking.
1182 final boolean canHold = heldCall.can(android.telecom.Call.Details.CAPABILITY_HOLD);
1183
Eric Erfanian2ca43182017-08-31 06:57:16 -07001184 LogUtil.v("InCallPresenter.handleCallKey", "heldCall: " + heldCall + ", canHold: " + canHold);
Eric Erfanianccca3152017-02-22 16:32:36 -08001185
1186 // (4) unhold call
1187 if (heldCall.getState() == DialerCall.State.ONHOLD && canHold) {
1188 heldCall.unhold();
1189 return true;
1190 }
1191 }
1192
1193 // Always consume hard keys
1194 return true;
1195 }
1196
Eric Erfanianccca3152017-02-22 16:32:36 -08001197 /** Clears the previous fullscreen state. */
1198 public void clearFullscreen() {
linyuh183cb712017-12-27 17:02:37 -08001199 isFullScreen = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001200 }
1201
1202 /**
1203 * Changes the fullscreen mode of the in-call UI.
1204 *
1205 * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
1206 * otherwise.
1207 */
1208 public void setFullScreen(boolean isFullScreen) {
1209 setFullScreen(isFullScreen, false /* force */);
1210 }
1211
1212 /**
1213 * Changes the fullscreen mode of the in-call UI.
1214 *
1215 * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
1216 * otherwise.
1217 * @param force {@code true} if fullscreen mode should be set regardless of its current state.
1218 */
1219 public void setFullScreen(boolean isFullScreen, boolean force) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001220 LogUtil.i("InCallPresenter.setFullScreen", "setFullScreen = " + isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001221
1222 // As a safeguard, ensure we cannot enter fullscreen if the dialpad is shown.
1223 if (isDialpadVisible()) {
1224 isFullScreen = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001225 LogUtil.v(
1226 "InCallPresenter.setFullScreen",
1227 "setFullScreen overridden as dialpad is shown = " + isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001228 }
1229
linyuh183cb712017-12-27 17:02:37 -08001230 if (this.isFullScreen == isFullScreen && !force) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001231 LogUtil.v("InCallPresenter.setFullScreen", "setFullScreen ignored as already in that state.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001232 return;
1233 }
linyuh183cb712017-12-27 17:02:37 -08001234 this.isFullScreen = isFullScreen;
1235 notifyFullscreenModeChange(this.isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001236 }
1237
1238 /**
1239 * @return {@code true} if the in-call ui is currently in fullscreen mode, {@code false}
1240 * otherwise.
1241 */
1242 public boolean isFullscreen() {
linyuh183cb712017-12-27 17:02:37 -08001243 return isFullScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -08001244 }
1245
1246 /**
1247 * Called by the {@link VideoCallPresenter} to inform of a change in full screen video status.
1248 *
1249 * @param isFullscreenMode {@code True} if entering full screen mode.
1250 */
1251 public void notifyFullscreenModeChange(boolean isFullscreenMode) {
linyuh183cb712017-12-27 17:02:37 -08001252 for (InCallEventListener listener : inCallEventListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001253 listener.onFullscreenModeChanged(isFullscreenMode);
1254 }
1255 }
1256
linyuh7b86f562017-11-16 11:24:09 -08001257 /** Instruct the in-call activity to show an error dialog or toast for a disconnected call. */
1258 private void showDialogOrToastForDisconnectedCall(DialerCall call) {
1259 if (!isActivityStarted() || call.getState() != DialerCall.State.DISCONNECTED) {
1260 return;
Eric Erfanianccca3152017-02-22 16:32:36 -08001261 }
linyuh7b86f562017-11-16 11:24:09 -08001262
1263 // For newly disconnected calls, we may want to show a dialog on specific error conditions
1264 if (call.getAccountHandle() == null && !call.isConferenceCall()) {
1265 setDisconnectCauseForMissingAccounts(call);
1266 }
1267
linyuh183cb712017-12-27 17:02:37 -08001268 inCallActivity.showDialogOrToastForDisconnectedCall(
1269 new DisconnectMessage(inCallActivity, call));
Eric Erfanianccca3152017-02-22 16:32:36 -08001270 }
1271
1272 /**
1273 * When the state of in-call changes, this is the first method to get called. It determines if the
1274 * UI needs to be started or finished depending on the new state and does it.
1275 */
1276 private InCallState startOrFinishUi(InCallState newState) {
wangqicf61ca02017-08-31 15:32:55 -07001277 Trace.beginSection("InCallPresenter.startOrFinishUi");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001278 LogUtil.d(
linyuh183cb712017-12-27 17:02:37 -08001279 "InCallPresenter.startOrFinishUi", "startOrFinishUi: " + inCallState + " -> " + newState);
Eric Erfanianccca3152017-02-22 16:32:36 -08001280
1281 // TODO: Consider a proper state machine implementation
1282
1283 // If the state isn't changing we have already done any starting/stopping of activities in
1284 // a previous pass...so lets cut out early
linyuh183cb712017-12-27 17:02:37 -08001285 if (newState == inCallState) {
wangqicf61ca02017-08-31 15:32:55 -07001286 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001287 return newState;
1288 }
1289
1290 // A new Incoming call means that the user needs to be notified of the the call (since
1291 // it wasn't them who initiated it). We do this through full screen notifications and
1292 // happens indirectly through {@link StatusBarNotifier}.
1293 //
1294 // The process for incoming calls is as follows:
1295 //
1296 // 1) CallList - Announces existence of new INCOMING call
1297 // 2) InCallPresenter - Gets announcement and calculates that the new InCallState
1298 // - should be set to INCOMING.
1299 // 3) InCallPresenter - This method is called to see if we need to start or finish
1300 // the app given the new state.
1301 // 4) StatusBarNotifier - Listens to InCallState changes. InCallPresenter calls
1302 // StatusBarNotifier explicitly to issue a FullScreen Notification
1303 // that will either start the InCallActivity or show the user a
1304 // top-level notification dialog if the user is in an immersive app.
1305 // That notification can also start the InCallActivity.
1306 // 5) InCallActivity - Main activity starts up and at the end of its onCreate will
1307 // call InCallPresenter::setActivity() to let the presenter
1308 // know that start-up is complete.
1309 //
1310 // [ AND NOW YOU'RE IN THE CALL. voila! ]
Eric Erfanianccca3152017-02-22 16:32:36 -08001311
1312 // A dialog to show on top of the InCallUI to select a PhoneAccount
1313 final boolean showAccountPicker = (InCallState.WAITING_FOR_ACCOUNT == newState);
1314
1315 // A new outgoing call indicates that the user just now dialed a number and when that
1316 // happens we need to display the screen immediately or show an account picker dialog if
1317 // no default is set. However, if the main InCallUI is already visible, we do not want to
1318 // re-initiate the start-up animation, so we do not need to do anything here.
1319 //
1320 // It is also possible to go into an intermediate state where the call has been initiated
1321 // but Telecom has not yet returned with the details of the call (handle, gateway, etc.).
1322 // This pending outgoing state can also launch the call screen.
1323 //
1324 // This is different from the incoming call sequence because we do not need to shock the
1325 // user with a top-level notification. Just show the call UI normally.
1326 boolean callCardFragmentVisible =
linyuh183cb712017-12-27 17:02:37 -08001327 inCallActivity != null && inCallActivity.getCallCardFragmentVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -08001328 final boolean mainUiNotVisible = !isShowingInCallUi() || !callCardFragmentVisible;
1329 boolean showCallUi = InCallState.OUTGOING == newState && mainUiNotVisible;
1330
1331 // Direct transition from PENDING_OUTGOING -> INCALL means that there was an error in the
1332 // outgoing call process, so the UI should be brought up to show an error dialog.
1333 showCallUi |=
linyuh183cb712017-12-27 17:02:37 -08001334 (InCallState.PENDING_OUTGOING == inCallState
Eric Erfanianccca3152017-02-22 16:32:36 -08001335 && InCallState.INCALL == newState
1336 && !isShowingInCallUi());
1337
1338 // Another exception - InCallActivity is in charge of disconnecting a call with no
1339 // valid accounts set. Bring the UI up if this is true for the current pending outgoing
1340 // call so that:
1341 // 1) The call can be disconnected correctly
1342 // 2) The UI comes up and correctly displays the error dialog.
1343 // TODO: Remove these special case conditions by making InCallPresenter a true state
1344 // machine. Telecom should also be the component responsible for disconnecting a call
1345 // with no valid accounts.
1346 showCallUi |=
1347 InCallState.PENDING_OUTGOING == newState
1348 && mainUiNotVisible
linyuh183cb712017-12-27 17:02:37 -08001349 && isCallWithNoValidAccounts(callList.getPendingOutgoingCall());
Eric Erfanianccca3152017-02-22 16:32:36 -08001350
1351 // The only time that we have an instance of mInCallActivity and it isn't started is
1352 // when it is being destroyed. In that case, lets avoid bringing up another instance of
1353 // the activity. When it is finally destroyed, we double check if we should bring it back
1354 // up so we aren't going to lose anything by avoiding a second startup here.
linyuh183cb712017-12-27 17:02:37 -08001355 boolean activityIsFinishing = inCallActivity != null && !isActivityStarted();
Eric Erfanianccca3152017-02-22 16:32:36 -08001356 if (activityIsFinishing) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001357 LogUtil.i(
1358 "InCallPresenter.startOrFinishUi",
linyuh183cb712017-12-27 17:02:37 -08001359 "Undo the state change: " + newState + " -> " + inCallState);
wangqicf61ca02017-08-31 15:32:55 -07001360 Trace.endSection();
linyuh183cb712017-12-27 17:02:37 -08001361 return inCallState;
Eric Erfanianccca3152017-02-22 16:32:36 -08001362 }
1363
1364 // We're about the bring up the in-call UI for outgoing and incoming call. If we still have
1365 // dialogs up, we need to clear them out before showing in-call screen. This is necessary
1366 // to fix the bug that dialog will show up when data reaches limit even after makeing new
1367 // outgoing call after user ignore it by pressing home button.
1368 if ((newState == InCallState.INCOMING || newState == InCallState.PENDING_OUTGOING)
1369 && !showCallUi
1370 && isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -08001371 inCallActivity.dismissPendingDialogs();
Eric Erfanianccca3152017-02-22 16:32:36 -08001372 }
1373
1374 if (showCallUi || showAccountPicker) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001375 LogUtil.i("InCallPresenter.startOrFinishUi", "Start in call UI");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001376 showInCall(false /* showDialpad */, !showAccountPicker /* newOutgoingCall */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001377 } else if (newState == InCallState.NO_CALLS) {
1378 // The new state is the no calls state. Tear everything down.
1379 attemptFinishActivity();
1380 attemptCleanup();
1381 }
1382
wangqicf61ca02017-08-31 15:32:55 -07001383 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001384 return newState;
1385 }
1386
1387 /**
1388 * Sets the DisconnectCause for a call that was disconnected because it was missing a PhoneAccount
1389 * or PhoneAccounts to select from.
1390 */
1391 private void setDisconnectCauseForMissingAccounts(DialerCall call) {
1392
1393 Bundle extras = call.getIntentExtras();
1394 // Initialize the extras bundle to avoid NPE
1395 if (extras == null) {
1396 extras = new Bundle();
1397 }
1398
1399 final List<PhoneAccountHandle> phoneAccountHandles =
1400 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
1401
1402 if (phoneAccountHandles == null || phoneAccountHandles.isEmpty()) {
1403 String scheme = call.getHandle().getScheme();
1404 final String errorMsg =
1405 PhoneAccount.SCHEME_TEL.equals(scheme)
linyuh183cb712017-12-27 17:02:37 -08001406 ? context.getString(R.string.callFailed_simError)
1407 : context.getString(R.string.incall_error_supp_service_unknown);
Eric Erfanianccca3152017-02-22 16:32:36 -08001408 DisconnectCause disconnectCause =
1409 new DisconnectCause(DisconnectCause.ERROR, null, errorMsg, errorMsg);
1410 call.setDisconnectCause(disconnectCause);
1411 }
1412 }
1413
Eric Erfanianccca3152017-02-22 16:32:36 -08001414 /**
1415 * @return {@code true} if the InCallPresenter is ready to be torn down, {@code false} otherwise.
1416 * Calling classes should use this as an indication whether to interact with the
1417 * InCallPresenter or not.
1418 */
1419 public boolean isReadyForTearDown() {
linyuh183cb712017-12-27 17:02:37 -08001420 return inCallActivity == null && !serviceConnected && inCallState == InCallState.NO_CALLS;
Eric Erfanianccca3152017-02-22 16:32:36 -08001421 }
1422
1423 /**
1424 * Checks to see if both the UI is gone and the service is disconnected. If so, tear it all down.
1425 */
1426 private void attemptCleanup() {
1427 if (isReadyForTearDown()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001428 LogUtil.i("InCallPresenter.attemptCleanup", "Cleaning up");
Eric Erfanianccca3152017-02-22 16:32:36 -08001429
1430 cleanupSurfaces();
1431
linyuh183cb712017-12-27 17:02:37 -08001432 isChangingConfigurations = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001433
1434 // blow away stale contact info so that we get fresh data on
1435 // the next set of calls
linyuh183cb712017-12-27 17:02:37 -08001436 if (contactInfoCache != null) {
1437 contactInfoCache.clearCache();
Eric Erfanianccca3152017-02-22 16:32:36 -08001438 }
linyuh183cb712017-12-27 17:02:37 -08001439 contactInfoCache = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001440
linyuh183cb712017-12-27 17:02:37 -08001441 if (proximitySensor != null) {
1442 removeListener(proximitySensor);
1443 proximitySensor.tearDown();
Eric Erfanianccca3152017-02-22 16:32:36 -08001444 }
linyuh183cb712017-12-27 17:02:37 -08001445 proximitySensor = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001446
linyuh183cb712017-12-27 17:02:37 -08001447 if (statusBarNotifier != null) {
1448 removeListener(statusBarNotifier);
1449 EnrichedCallComponent.get(context)
Eric Erfaniand8046e52017-04-06 09:41:50 -07001450 .getEnrichedCallManager()
linyuh183cb712017-12-27 17:02:37 -08001451 .unregisterStateChangedListener(statusBarNotifier);
Eric Erfanianccca3152017-02-22 16:32:36 -08001452 }
Eric Erfaniand8046e52017-04-06 09:41:50 -07001453
linyuh183cb712017-12-27 17:02:37 -08001454 if (externalCallNotifier != null && externalCallList != null) {
1455 externalCallList.removeExternalCallListener(externalCallNotifier);
Eric Erfanianccca3152017-02-22 16:32:36 -08001456 }
linyuh183cb712017-12-27 17:02:37 -08001457 statusBarNotifier = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001458
linyuh183cb712017-12-27 17:02:37 -08001459 if (callList != null) {
1460 callList.removeListener(this);
1461 callList.removeListener(spamCallListListener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001462 }
linyuh183cb712017-12-27 17:02:37 -08001463 callList = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001464
linyuh183cb712017-12-27 17:02:37 -08001465 context = null;
1466 inCallActivity = null;
1467 manageConferenceActivity = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001468
linyuh183cb712017-12-27 17:02:37 -08001469 listeners.clear();
1470 incomingCallListeners.clear();
1471 detailsListeners.clear();
1472 canAddCallListeners.clear();
1473 orientationListeners.clear();
1474 inCallEventListeners.clear();
1475 inCallUiListeners.clear();
1476 if (!inCallUiLocks.isEmpty()) {
1477 LogUtil.e("InCallPresenter.attemptCleanup", "held in call locks: " + inCallUiLocks);
1478 inCallUiLocks.clear();
twyen8efb4952017-10-06 16:35:54 -07001479 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001480 LogUtil.d("InCallPresenter.attemptCleanup", "finished");
Eric Erfanianccca3152017-02-22 16:32:36 -08001481 }
1482 }
1483
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001484 public void showInCall(boolean showDialpad, boolean newOutgoingCall) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001485 LogUtil.i("InCallPresenter.showInCall", "Showing InCallActivity");
linyuh183cb712017-12-27 17:02:37 -08001486 context.startActivity(
1487 InCallActivity.getIntent(context, showDialpad, newOutgoingCall, false /* forFullScreen */));
Eric Erfanianccca3152017-02-22 16:32:36 -08001488 }
1489
1490 public void onServiceBind() {
linyuh183cb712017-12-27 17:02:37 -08001491 serviceBound = true;
Eric Erfanianccca3152017-02-22 16:32:36 -08001492 }
1493
1494 public void onServiceUnbind() {
1495 InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(false, null);
linyuh183cb712017-12-27 17:02:37 -08001496 serviceBound = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001497 }
1498
1499 public boolean isServiceBound() {
linyuh183cb712017-12-27 17:02:37 -08001500 return serviceBound;
Eric Erfanianccca3152017-02-22 16:32:36 -08001501 }
1502
1503 public void maybeStartRevealAnimation(Intent intent) {
linyuh183cb712017-12-27 17:02:37 -08001504 if (intent == null || inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001505 return;
1506 }
1507 final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
1508 if (extras == null) {
1509 // Incoming call, just show the in-call UI directly.
1510 return;
1511 }
1512
1513 if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) {
1514 // Account selection dialog will show up so don't show the animation.
1515 return;
1516 }
1517
1518 final PhoneAccountHandle accountHandle =
1519 intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
1520 final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT);
Eric Erfanianccca3152017-02-22 16:32:36 -08001521
1522 InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(true, accountHandle);
1523
1524 final Intent activityIntent =
linyuh183cb712017-12-27 17:02:37 -08001525 InCallActivity.getIntent(context, false, true, false /* forFullScreen */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001526 activityIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint);
linyuh183cb712017-12-27 17:02:37 -08001527 context.startActivity(activityIntent);
Eric Erfanianccca3152017-02-22 16:32:36 -08001528 }
1529
1530 /**
1531 * Retrieves the current in-call camera manager instance, creating if necessary.
1532 *
1533 * @return The {@link InCallCameraManager}.
1534 */
1535 public InCallCameraManager getInCallCameraManager() {
1536 synchronized (this) {
linyuh183cb712017-12-27 17:02:37 -08001537 if (inCallCameraManager == null) {
1538 inCallCameraManager = new InCallCameraManager(context);
Eric Erfanianccca3152017-02-22 16:32:36 -08001539 }
1540
linyuh183cb712017-12-27 17:02:37 -08001541 return inCallCameraManager;
Eric Erfanianccca3152017-02-22 16:32:36 -08001542 }
1543 }
1544
1545 /**
1546 * Notifies listeners of changes in orientation and notify calls of rotation angle change.
1547 *
1548 * @param orientation The screen orientation of the device (one of: {@link
1549 * InCallOrientationEventListener#SCREEN_ORIENTATION_0}, {@link
1550 * InCallOrientationEventListener#SCREEN_ORIENTATION_90}, {@link
1551 * InCallOrientationEventListener#SCREEN_ORIENTATION_180}, {@link
1552 * InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
1553 */
1554 public void onDeviceOrientationChange(@ScreenOrientation int orientation) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001555 LogUtil.d(
1556 "InCallPresenter.onDeviceOrientationChange",
1557 "onDeviceOrientationChange: orientation= " + orientation);
Eric Erfanianccca3152017-02-22 16:32:36 -08001558
linyuh183cb712017-12-27 17:02:37 -08001559 if (callList != null) {
1560 callList.notifyCallsOfDeviceRotation(orientation);
Eric Erfanianccca3152017-02-22 16:32:36 -08001561 } else {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001562 LogUtil.w("InCallPresenter.onDeviceOrientationChange", "CallList is null.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001563 }
1564
1565 // Notify listeners of device orientation changed.
linyuh183cb712017-12-27 17:02:37 -08001566 for (InCallOrientationListener listener : orientationListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001567 listener.onDeviceOrientationChanged(orientation);
1568 }
1569 }
1570
1571 /**
1572 * Configures the in-call UI activity so it can change orientations or not. Enables the
1573 * orientation event listener if allowOrientationChange is true, disables it if false.
1574 *
1575 * @param allowOrientationChange {@code true} if the in-call UI can change between portrait and
1576 * landscape. {@code false} if the in-call UI should be locked in portrait.
1577 */
1578 public void setInCallAllowsOrientationChange(boolean allowOrientationChange) {
linyuh183cb712017-12-27 17:02:37 -08001579 if (inCallActivity == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001580 LogUtil.e(
1581 "InCallPresenter.setInCallAllowsOrientationChange",
1582 "InCallActivity is null. Can't set requested orientation.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001583 return;
1584 }
linyuh183cb712017-12-27 17:02:37 -08001585 inCallActivity.setAllowOrientationChange(allowOrientationChange);
Eric Erfanianccca3152017-02-22 16:32:36 -08001586 }
1587
1588 public void enableScreenTimeout(boolean enable) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001589 LogUtil.v("InCallPresenter.enableScreenTimeout", "enableScreenTimeout: value=" + enable);
linyuh183cb712017-12-27 17:02:37 -08001590 screenTimeoutEnabled = enable;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001591 applyScreenTimeout();
1592 }
1593
1594 private void applyScreenTimeout() {
linyuh183cb712017-12-27 17:02:37 -08001595 if (inCallActivity == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001596 LogUtil.e("InCallPresenter.applyScreenTimeout", "InCallActivity is null.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001597 return;
1598 }
1599
linyuh183cb712017-12-27 17:02:37 -08001600 final Window window = inCallActivity.getWindow();
1601 if (screenTimeoutEnabled) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001602 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1603 } else {
1604 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1605 }
1606 }
1607
1608 /**
1609 * Hides or shows the conference manager fragment.
1610 *
1611 * @param show {@code true} if the conference manager should be shown, {@code false} if it should
1612 * be hidden.
1613 */
1614 public void showConferenceCallManager(boolean show) {
linyuh183cb712017-12-27 17:02:37 -08001615 if (inCallActivity != null) {
1616 inCallActivity.showConferenceFragment(show);
Eric Erfanianccca3152017-02-22 16:32:36 -08001617 }
linyuh183cb712017-12-27 17:02:37 -08001618 if (!show && manageConferenceActivity != null) {
1619 manageConferenceActivity.finish();
Eric Erfanianccca3152017-02-22 16:32:36 -08001620 }
1621 }
1622
1623 /**
1624 * Determines if the dialpad is visible.
1625 *
1626 * @return {@code true} if the dialpad is visible, {@code false} otherwise.
1627 */
1628 public boolean isDialpadVisible() {
linyuh183cb712017-12-27 17:02:37 -08001629 if (inCallActivity == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001630 return false;
1631 }
linyuh183cb712017-12-27 17:02:37 -08001632 return inCallActivity.isDialpadVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -08001633 }
1634
1635 public ThemeColorManager getThemeColorManager() {
linyuh183cb712017-12-27 17:02:37 -08001636 return themeColorManager;
Eric Erfanianccca3152017-02-22 16:32:36 -08001637 }
1638
wangqi8d662ca2017-10-26 11:27:19 -07001639 @VisibleForTesting
1640 public void setThemeColorManager(ThemeColorManager themeColorManager) {
linyuh183cb712017-12-27 17:02:37 -08001641 this.themeColorManager = themeColorManager;
wangqi8d662ca2017-10-26 11:27:19 -07001642 }
1643
Eric Erfanianccca3152017-02-22 16:32:36 -08001644 /** Called when the foreground call changes. */
1645 public void onForegroundCallChanged(DialerCall newForegroundCall) {
linyuh183cb712017-12-27 17:02:37 -08001646 themeColorManager.onForegroundCallChanged(context, newForegroundCall);
1647 if (inCallActivity != null) {
1648 inCallActivity.onForegroundCallChanged(newForegroundCall);
Eric Erfanianccca3152017-02-22 16:32:36 -08001649 }
1650 }
1651
1652 public InCallActivity getActivity() {
linyuh183cb712017-12-27 17:02:37 -08001653 return inCallActivity;
Eric Erfanianccca3152017-02-22 16:32:36 -08001654 }
1655
1656 /** Called when the UI begins, and starts the callstate callbacks if necessary. */
1657 public void setActivity(InCallActivity inCallActivity) {
1658 if (inCallActivity == null) {
1659 throw new IllegalArgumentException("registerActivity cannot be called with null");
1660 }
linyuh183cb712017-12-27 17:02:37 -08001661 if (this.inCallActivity != null && this.inCallActivity != inCallActivity) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001662 LogUtil.w(
1663 "InCallPresenter.setActivity", "Setting a second activity before destroying the first.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001664 }
1665 updateActivity(inCallActivity);
1666 }
1667
1668 ExternalCallNotifier getExternalCallNotifier() {
linyuh183cb712017-12-27 17:02:37 -08001669 return externalCallNotifier;
Eric Erfanianccca3152017-02-22 16:32:36 -08001670 }
1671
1672 VideoSurfaceTexture getLocalVideoSurfaceTexture() {
linyuh183cb712017-12-27 17:02:37 -08001673 if (localVideoSurfaceTexture == null) {
roldenburg12b50c62017-09-01 14:41:19 -07001674 boolean isPixel2017 = false;
linyuh183cb712017-12-27 17:02:37 -08001675 if (context != null) {
1676 isPixel2017 = context.getPackageManager().hasSystemFeature(PIXEL2017_SYSTEM_FEATURE);
roldenburg12b50c62017-09-01 14:41:19 -07001677 }
linyuh183cb712017-12-27 17:02:37 -08001678 localVideoSurfaceTexture = VideoSurfaceBindings.createLocalVideoSurfaceTexture(isPixel2017);
Eric Erfanianccca3152017-02-22 16:32:36 -08001679 }
linyuh183cb712017-12-27 17:02:37 -08001680 return localVideoSurfaceTexture;
Eric Erfanianccca3152017-02-22 16:32:36 -08001681 }
1682
1683 VideoSurfaceTexture getRemoteVideoSurfaceTexture() {
linyuh183cb712017-12-27 17:02:37 -08001684 if (remoteVideoSurfaceTexture == null) {
roldenburg12b50c62017-09-01 14:41:19 -07001685 boolean isPixel2017 = false;
linyuh183cb712017-12-27 17:02:37 -08001686 if (context != null) {
1687 isPixel2017 = context.getPackageManager().hasSystemFeature(PIXEL2017_SYSTEM_FEATURE);
roldenburg12b50c62017-09-01 14:41:19 -07001688 }
linyuh183cb712017-12-27 17:02:37 -08001689 remoteVideoSurfaceTexture = VideoSurfaceBindings.createRemoteVideoSurfaceTexture(isPixel2017);
Eric Erfanianccca3152017-02-22 16:32:36 -08001690 }
linyuh183cb712017-12-27 17:02:37 -08001691 return remoteVideoSurfaceTexture;
Eric Erfanianccca3152017-02-22 16:32:36 -08001692 }
1693
1694 void cleanupSurfaces() {
linyuh183cb712017-12-27 17:02:37 -08001695 if (remoteVideoSurfaceTexture != null) {
1696 remoteVideoSurfaceTexture.setDoneWithSurface();
1697 remoteVideoSurfaceTexture = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001698 }
linyuh183cb712017-12-27 17:02:37 -08001699 if (localVideoSurfaceTexture != null) {
1700 localVideoSurfaceTexture.setDoneWithSurface();
1701 localVideoSurfaceTexture = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001702 }
1703 }
1704
yueg77cb8e52017-10-27 16:42:51 -07001705 @Override
1706 public void onAudioStateChanged(CallAudioState audioState) {
linyuh183cb712017-12-27 17:02:37 -08001707 if (statusBarNotifier != null) {
1708 statusBarNotifier.updateNotification();
yueg77cb8e52017-10-27 16:42:51 -07001709 }
1710 }
1711
Eric Erfanianccca3152017-02-22 16:32:36 -08001712 /** All the main states of InCallActivity. */
1713 public enum InCallState {
1714 // InCall Screen is off and there are no calls
1715 NO_CALLS,
1716
1717 // Incoming-call screen is up
1718 INCOMING,
1719
1720 // In-call experience is showing
1721 INCALL,
1722
1723 // Waiting for user input before placing outgoing call
1724 WAITING_FOR_ACCOUNT,
1725
1726 // UI is starting up but no call has been initiated yet.
1727 // The UI is waiting for Telecom to respond.
1728 PENDING_OUTGOING,
1729
1730 // User is dialing out
1731 OUTGOING;
1732
1733 public boolean isIncoming() {
1734 return (this == INCOMING);
1735 }
1736
1737 public boolean isConnectingOrConnected() {
1738 return (this == INCOMING || this == OUTGOING || this == INCALL);
1739 }
1740 }
1741
1742 /** Interface implemented by classes that need to know about the InCall State. */
1743 public interface InCallStateListener {
1744
1745 // TODO: Enhance state to contain the call objects instead of passing CallList
1746 void onStateChange(InCallState oldState, InCallState newState, CallList callList);
1747 }
1748
1749 public interface IncomingCallListener {
1750
1751 void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call);
1752 }
1753
1754 public interface CanAddCallListener {
1755
1756 void onCanAddCallChanged(boolean canAddCall);
1757 }
1758
1759 public interface InCallDetailsListener {
1760
1761 void onDetailsChanged(DialerCall call, android.telecom.Call.Details details);
1762 }
1763
1764 public interface InCallOrientationListener {
1765
1766 void onDeviceOrientationChanged(@ScreenOrientation int orientation);
1767 }
1768
1769 /**
1770 * Interface implemented by classes that need to know about events which occur within the In-Call
1771 * UI. Used as a means of communicating between fragments that make up the UI.
1772 */
1773 public interface InCallEventListener {
1774
1775 void onFullscreenModeChanged(boolean isFullscreenMode);
1776 }
1777
1778 public interface InCallUiListener {
1779
1780 void onUiShowing(boolean showing);
1781 }
twyen8efb4952017-10-06 16:35:54 -07001782
1783 private class InCallUiLockImpl implements InCallUiLock {
1784 private final String tag;
1785
1786 private InCallUiLockImpl(String tag) {
1787 this.tag = tag;
1788 }
1789
1790 @MainThread
1791 @Override
1792 public void release() {
1793 Assert.isMainThread();
1794 releaseInCallUiLock(InCallUiLockImpl.this);
1795 }
1796
1797 @Override
1798 public String toString() {
1799 return "InCallUiLock[" + tag + "]";
1800 }
1801 }
1802
1803 @MainThread
1804 public InCallUiLock acquireInCallUiLock(String tag) {
1805 Assert.isMainThread();
1806 InCallUiLock lock = new InCallUiLockImpl(tag);
linyuh183cb712017-12-27 17:02:37 -08001807 inCallUiLocks.add(lock);
twyen8efb4952017-10-06 16:35:54 -07001808 return lock;
1809 }
1810
1811 @MainThread
1812 private void releaseInCallUiLock(InCallUiLock lock) {
1813 Assert.isMainThread();
1814 LogUtil.i("InCallPresenter.releaseInCallUiLock", "releasing %s", lock);
linyuh183cb712017-12-27 17:02:37 -08001815 inCallUiLocks.remove(lock);
1816 if (inCallUiLocks.isEmpty()) {
twyen8efb4952017-10-06 16:35:54 -07001817 LogUtil.i("InCallPresenter.releaseInCallUiLock", "all locks released");
linyuh183cb712017-12-27 17:02:37 -08001818 if (inCallState == InCallState.NO_CALLS) {
twyen8efb4952017-10-06 16:35:54 -07001819 LogUtil.i("InCallPresenter.releaseInCallUiLock", "no more calls, finishing UI");
1820 attemptFinishActivity();
1821 attemptCleanup();
1822 }
1823 }
1824 }
1825
1826 @MainThread
1827 public boolean isInCallUiLocked() {
1828 Assert.isMainThread();
linyuh183cb712017-12-27 17:02:37 -08001829 if (inCallUiLocks.isEmpty()) {
twyen8efb4952017-10-06 16:35:54 -07001830 return false;
1831 }
linyuh183cb712017-12-27 17:02:37 -08001832 for (InCallUiLock lock : inCallUiLocks) {
twyen8efb4952017-10-06 16:35:54 -07001833 LogUtil.i("InCallPresenter.isInCallUiLocked", "still locked by %s", lock);
1834 }
1835 return true;
1836 }
1837
linyuh183cb712017-12-27 17:02:37 -08001838 private final Set<InCallUiLock> inCallUiLocks = new ArraySet<>();
Eric Erfanianccca3152017-02-22 16:32:36 -08001839}