blob: a4b90f1a6aab7f3583322dc0cfd695d139965ac0 [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 void updateNotification() {
Eric Erfanianccca3152017-02-22 16:32:36 -08001054 // We need to update the notification bar when we leave the UI because that
1055 // could trigger it to show again.
linyuh183cb712017-12-27 17:02:37 -08001056 if (statusBarNotifier != null) {
1057 statusBarNotifier.updateNotification();
Eric Erfanianccca3152017-02-22 16:32:36 -08001058 }
yueg10f6e822018-01-17 15:32:18 -08001059 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001060
yueg10f6e822018-01-17 15:32:18 -08001061 /** Called when the activity goes in/out of the foreground. */
1062 public void onUiShowing(boolean showing) {
linyuh183cb712017-12-27 17:02:37 -08001063 if (proximitySensor != null) {
1064 proximitySensor.onInCallShowing(showing);
Eric Erfanianccca3152017-02-22 16:32:36 -08001065 }
1066
yueg092b21c2017-11-15 16:20:07 -08001067 if (!showing) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001068 updateIsChangingConfigurations();
1069 }
1070
linyuh183cb712017-12-27 17:02:37 -08001071 for (InCallUiListener listener : inCallUiListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001072 listener.onUiShowing(showing);
1073 }
1074
linyuh183cb712017-12-27 17:02:37 -08001075 if (inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001076 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -08001077 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -08001078 }
1079 }
1080
Eric Erfanian2ca43182017-08-31 06:57:16 -07001081 public void refreshUi() {
linyuh183cb712017-12-27 17:02:37 -08001082 if (inCallActivity != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001083 // Re-evaluate which fragment is being shown.
linyuh183cb712017-12-27 17:02:37 -08001084 inCallActivity.onPrimaryCallStateChanged();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001085 }
1086 }
1087
Eric Erfanianccca3152017-02-22 16:32:36 -08001088 public void addInCallUiListener(InCallUiListener listener) {
linyuh183cb712017-12-27 17:02:37 -08001089 inCallUiListeners.add(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001090 }
1091
1092 public boolean removeInCallUiListener(InCallUiListener listener) {
linyuh183cb712017-12-27 17:02:37 -08001093 return inCallUiListeners.remove(listener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001094 }
1095
1096 /*package*/
1097 void onActivityStarted() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001098 LogUtil.d("InCallPresenter.onActivityStarted", "onActivityStarted");
Eric Erfanianccca3152017-02-22 16:32:36 -08001099 notifyVideoPauseController(true);
Eric Erfanian2ca43182017-08-31 06:57:16 -07001100 applyScreenTimeout();
Eric Erfanianccca3152017-02-22 16:32:36 -08001101 }
1102
1103 /*package*/
1104 void onActivityStopped() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001105 LogUtil.d("InCallPresenter.onActivityStopped", "onActivityStopped");
Eric Erfanianccca3152017-02-22 16:32:36 -08001106 notifyVideoPauseController(false);
1107 }
1108
1109 private void notifyVideoPauseController(boolean showing) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001110 LogUtil.d(
1111 "InCallPresenter.notifyVideoPauseController",
linyuh183cb712017-12-27 17:02:37 -08001112 "mIsChangingConfigurations=" + isChangingConfigurations);
1113 if (!isChangingConfigurations) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001114 VideoPauseController.getInstance().onUiShowing(showing);
1115 }
1116 }
1117
1118 /** Brings the app into the foreground if possible. */
1119 public void bringToForeground(boolean showDialpad) {
1120 // Before we bring the incall UI to the foreground, we check to see if:
1121 // 1. It is not currently in the foreground
1122 // 2. We are in a state where we want to show the incall ui (i.e. there are calls to
1123 // be displayed)
1124 // If the activity hadn't actually been started previously, yet there are still calls
1125 // present (e.g. a call was accepted by a bluetooth or wired headset), we want to
1126 // bring it up the UI regardless.
linyuh183cb712017-12-27 17:02:37 -08001127 if (!isShowingInCallUi() && inCallState != InCallState.NO_CALLS) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001128 showInCall(showDialpad, false /* newOutgoingCall */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001129 }
1130 }
1131
1132 public void onPostDialCharWait(String callId, String chars) {
1133 if (isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -08001134 inCallActivity.showDialogForPostCharWait(callId, chars);
Eric Erfanianccca3152017-02-22 16:32:36 -08001135 }
1136 }
1137
1138 /**
1139 * Handles the green CALL key while in-call.
1140 *
1141 * @return true if we consumed the event.
1142 */
1143 public boolean handleCallKey() {
1144 LogUtil.v("InCallPresenter.handleCallKey", null);
1145
1146 // The green CALL button means either "Answer", "Unhold", or
1147 // "Swap calls", or can be a no-op, depending on the current state
1148 // of the Phone.
1149
1150 /** INCOMING CALL */
linyuh183cb712017-12-27 17:02:37 -08001151 final CallList calls = callList;
Eric Erfanianccca3152017-02-22 16:32:36 -08001152 final DialerCall incomingCall = calls.getIncomingCall();
1153 LogUtil.v("InCallPresenter.handleCallKey", "incomingCall: " + incomingCall);
1154
1155 // (1) Attempt to answer a call
1156 if (incomingCall != null) {
1157 incomingCall.answer(VideoProfile.STATE_AUDIO_ONLY);
1158 return true;
1159 }
1160
1161 /** STATE_ACTIVE CALL */
1162 final DialerCall activeCall = calls.getActiveCall();
1163 if (activeCall != null) {
1164 // TODO: This logic is repeated from CallButtonPresenter.java. We should
1165 // consolidate this logic.
1166 final boolean canMerge =
1167 activeCall.can(android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
1168 final boolean canSwap =
1169 activeCall.can(android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE);
1170
Eric Erfanian2ca43182017-08-31 06:57:16 -07001171 LogUtil.v(
1172 "InCallPresenter.handleCallKey",
1173 "activeCall: " + activeCall + ", canMerge: " + canMerge + ", canSwap: " + canSwap);
Eric Erfanianccca3152017-02-22 16:32:36 -08001174
1175 // (2) Attempt actions on conference calls
1176 if (canMerge) {
1177 TelecomAdapter.getInstance().merge(activeCall.getId());
1178 return true;
1179 } else if (canSwap) {
1180 TelecomAdapter.getInstance().swap(activeCall.getId());
1181 return true;
1182 }
1183 }
1184
1185 /** BACKGROUND CALL */
1186 final DialerCall heldCall = calls.getBackgroundCall();
1187 if (heldCall != null) {
1188 // We have a hold call so presumeable it will always support HOLD...but
1189 // there is no harm in double checking.
1190 final boolean canHold = heldCall.can(android.telecom.Call.Details.CAPABILITY_HOLD);
1191
Eric Erfanian2ca43182017-08-31 06:57:16 -07001192 LogUtil.v("InCallPresenter.handleCallKey", "heldCall: " + heldCall + ", canHold: " + canHold);
Eric Erfanianccca3152017-02-22 16:32:36 -08001193
1194 // (4) unhold call
1195 if (heldCall.getState() == DialerCall.State.ONHOLD && canHold) {
1196 heldCall.unhold();
1197 return true;
1198 }
1199 }
1200
1201 // Always consume hard keys
1202 return true;
1203 }
1204
Eric Erfanianccca3152017-02-22 16:32:36 -08001205 /** Clears the previous fullscreen state. */
1206 public void clearFullscreen() {
linyuh183cb712017-12-27 17:02:37 -08001207 isFullScreen = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001208 }
1209
1210 /**
1211 * Changes the fullscreen mode of the in-call UI.
1212 *
1213 * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
1214 * otherwise.
1215 */
1216 public void setFullScreen(boolean isFullScreen) {
1217 setFullScreen(isFullScreen, false /* force */);
1218 }
1219
1220 /**
1221 * Changes the fullscreen mode of the in-call UI.
1222 *
1223 * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
1224 * otherwise.
1225 * @param force {@code true} if fullscreen mode should be set regardless of its current state.
1226 */
1227 public void setFullScreen(boolean isFullScreen, boolean force) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001228 LogUtil.i("InCallPresenter.setFullScreen", "setFullScreen = " + isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001229
1230 // As a safeguard, ensure we cannot enter fullscreen if the dialpad is shown.
1231 if (isDialpadVisible()) {
1232 isFullScreen = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001233 LogUtil.v(
1234 "InCallPresenter.setFullScreen",
1235 "setFullScreen overridden as dialpad is shown = " + isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001236 }
1237
linyuh183cb712017-12-27 17:02:37 -08001238 if (this.isFullScreen == isFullScreen && !force) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001239 LogUtil.v("InCallPresenter.setFullScreen", "setFullScreen ignored as already in that state.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001240 return;
1241 }
linyuh183cb712017-12-27 17:02:37 -08001242 this.isFullScreen = isFullScreen;
1243 notifyFullscreenModeChange(this.isFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -08001244 }
1245
1246 /**
1247 * @return {@code true} if the in-call ui is currently in fullscreen mode, {@code false}
1248 * otherwise.
1249 */
1250 public boolean isFullscreen() {
linyuh183cb712017-12-27 17:02:37 -08001251 return isFullScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -08001252 }
1253
1254 /**
1255 * Called by the {@link VideoCallPresenter} to inform of a change in full screen video status.
1256 *
1257 * @param isFullscreenMode {@code True} if entering full screen mode.
1258 */
1259 public void notifyFullscreenModeChange(boolean isFullscreenMode) {
linyuh183cb712017-12-27 17:02:37 -08001260 for (InCallEventListener listener : inCallEventListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001261 listener.onFullscreenModeChanged(isFullscreenMode);
1262 }
1263 }
1264
linyuh7b86f562017-11-16 11:24:09 -08001265 /** Instruct the in-call activity to show an error dialog or toast for a disconnected call. */
1266 private void showDialogOrToastForDisconnectedCall(DialerCall call) {
1267 if (!isActivityStarted() || call.getState() != DialerCall.State.DISCONNECTED) {
1268 return;
Eric Erfanianccca3152017-02-22 16:32:36 -08001269 }
linyuh7b86f562017-11-16 11:24:09 -08001270
1271 // For newly disconnected calls, we may want to show a dialog on specific error conditions
1272 if (call.getAccountHandle() == null && !call.isConferenceCall()) {
1273 setDisconnectCauseForMissingAccounts(call);
1274 }
1275
linyuh183cb712017-12-27 17:02:37 -08001276 inCallActivity.showDialogOrToastForDisconnectedCall(
1277 new DisconnectMessage(inCallActivity, call));
Eric Erfanianccca3152017-02-22 16:32:36 -08001278 }
1279
1280 /**
1281 * When the state of in-call changes, this is the first method to get called. It determines if the
1282 * UI needs to be started or finished depending on the new state and does it.
1283 */
1284 private InCallState startOrFinishUi(InCallState newState) {
wangqicf61ca02017-08-31 15:32:55 -07001285 Trace.beginSection("InCallPresenter.startOrFinishUi");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001286 LogUtil.d(
linyuh183cb712017-12-27 17:02:37 -08001287 "InCallPresenter.startOrFinishUi", "startOrFinishUi: " + inCallState + " -> " + newState);
Eric Erfanianccca3152017-02-22 16:32:36 -08001288
1289 // TODO: Consider a proper state machine implementation
1290
1291 // If the state isn't changing we have already done any starting/stopping of activities in
1292 // a previous pass...so lets cut out early
linyuh183cb712017-12-27 17:02:37 -08001293 if (newState == inCallState) {
wangqicf61ca02017-08-31 15:32:55 -07001294 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001295 return newState;
1296 }
1297
1298 // A new Incoming call means that the user needs to be notified of the the call (since
1299 // it wasn't them who initiated it). We do this through full screen notifications and
1300 // happens indirectly through {@link StatusBarNotifier}.
1301 //
1302 // The process for incoming calls is as follows:
1303 //
1304 // 1) CallList - Announces existence of new INCOMING call
1305 // 2) InCallPresenter - Gets announcement and calculates that the new InCallState
1306 // - should be set to INCOMING.
1307 // 3) InCallPresenter - This method is called to see if we need to start or finish
1308 // the app given the new state.
1309 // 4) StatusBarNotifier - Listens to InCallState changes. InCallPresenter calls
1310 // StatusBarNotifier explicitly to issue a FullScreen Notification
1311 // that will either start the InCallActivity or show the user a
1312 // top-level notification dialog if the user is in an immersive app.
1313 // That notification can also start the InCallActivity.
1314 // 5) InCallActivity - Main activity starts up and at the end of its onCreate will
1315 // call InCallPresenter::setActivity() to let the presenter
1316 // know that start-up is complete.
1317 //
1318 // [ AND NOW YOU'RE IN THE CALL. voila! ]
1319 //
1320 // Our app is started using a fullScreen notification. We need to do this whenever
1321 // we get an incoming call. Depending on the current context of the device, either a
1322 // incoming call HUN or the actual InCallActivity will be shown.
1323 final boolean startIncomingCallSequence = (InCallState.INCOMING == newState);
1324
1325 // A dialog to show on top of the InCallUI to select a PhoneAccount
1326 final boolean showAccountPicker = (InCallState.WAITING_FOR_ACCOUNT == newState);
1327
1328 // A new outgoing call indicates that the user just now dialed a number and when that
1329 // happens we need to display the screen immediately or show an account picker dialog if
1330 // no default is set. However, if the main InCallUI is already visible, we do not want to
1331 // re-initiate the start-up animation, so we do not need to do anything here.
1332 //
1333 // It is also possible to go into an intermediate state where the call has been initiated
1334 // but Telecom has not yet returned with the details of the call (handle, gateway, etc.).
1335 // This pending outgoing state can also launch the call screen.
1336 //
1337 // This is different from the incoming call sequence because we do not need to shock the
1338 // user with a top-level notification. Just show the call UI normally.
1339 boolean callCardFragmentVisible =
linyuh183cb712017-12-27 17:02:37 -08001340 inCallActivity != null && inCallActivity.getCallCardFragmentVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -08001341 final boolean mainUiNotVisible = !isShowingInCallUi() || !callCardFragmentVisible;
1342 boolean showCallUi = InCallState.OUTGOING == newState && mainUiNotVisible;
1343
1344 // Direct transition from PENDING_OUTGOING -> INCALL means that there was an error in the
1345 // outgoing call process, so the UI should be brought up to show an error dialog.
1346 showCallUi |=
linyuh183cb712017-12-27 17:02:37 -08001347 (InCallState.PENDING_OUTGOING == inCallState
Eric Erfanianccca3152017-02-22 16:32:36 -08001348 && InCallState.INCALL == newState
1349 && !isShowingInCallUi());
1350
1351 // Another exception - InCallActivity is in charge of disconnecting a call with no
1352 // valid accounts set. Bring the UI up if this is true for the current pending outgoing
1353 // call so that:
1354 // 1) The call can be disconnected correctly
1355 // 2) The UI comes up and correctly displays the error dialog.
1356 // TODO: Remove these special case conditions by making InCallPresenter a true state
1357 // machine. Telecom should also be the component responsible for disconnecting a call
1358 // with no valid accounts.
1359 showCallUi |=
1360 InCallState.PENDING_OUTGOING == newState
1361 && mainUiNotVisible
linyuh183cb712017-12-27 17:02:37 -08001362 && isCallWithNoValidAccounts(callList.getPendingOutgoingCall());
Eric Erfanianccca3152017-02-22 16:32:36 -08001363
1364 // The only time that we have an instance of mInCallActivity and it isn't started is
1365 // when it is being destroyed. In that case, lets avoid bringing up another instance of
1366 // the activity. When it is finally destroyed, we double check if we should bring it back
1367 // up so we aren't going to lose anything by avoiding a second startup here.
linyuh183cb712017-12-27 17:02:37 -08001368 boolean activityIsFinishing = inCallActivity != null && !isActivityStarted();
Eric Erfanianccca3152017-02-22 16:32:36 -08001369 if (activityIsFinishing) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001370 LogUtil.i(
1371 "InCallPresenter.startOrFinishUi",
linyuh183cb712017-12-27 17:02:37 -08001372 "Undo the state change: " + newState + " -> " + inCallState);
wangqicf61ca02017-08-31 15:32:55 -07001373 Trace.endSection();
linyuh183cb712017-12-27 17:02:37 -08001374 return inCallState;
Eric Erfanianccca3152017-02-22 16:32:36 -08001375 }
1376
1377 // We're about the bring up the in-call UI for outgoing and incoming call. If we still have
1378 // dialogs up, we need to clear them out before showing in-call screen. This is necessary
1379 // to fix the bug that dialog will show up when data reaches limit even after makeing new
1380 // outgoing call after user ignore it by pressing home button.
1381 if ((newState == InCallState.INCOMING || newState == InCallState.PENDING_OUTGOING)
1382 && !showCallUi
1383 && isActivityStarted()) {
linyuh183cb712017-12-27 17:02:37 -08001384 inCallActivity.dismissPendingDialogs();
Eric Erfanianccca3152017-02-22 16:32:36 -08001385 }
1386
1387 if (showCallUi || showAccountPicker) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001388 LogUtil.i("InCallPresenter.startOrFinishUi", "Start in call UI");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001389 showInCall(false /* showDialpad */, !showAccountPicker /* newOutgoingCall */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001390 } else if (startIncomingCallSequence) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001391 LogUtil.i("InCallPresenter.startOrFinishUi", "Start Full Screen in call UI");
Eric Erfanianccca3152017-02-22 16:32:36 -08001392
linyuh183cb712017-12-27 17:02:37 -08001393 statusBarNotifier.updateNotification();
Eric Erfanianccca3152017-02-22 16:32:36 -08001394 } else if (newState == InCallState.NO_CALLS) {
1395 // The new state is the no calls state. Tear everything down.
1396 attemptFinishActivity();
1397 attemptCleanup();
1398 }
1399
wangqicf61ca02017-08-31 15:32:55 -07001400 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001401 return newState;
1402 }
1403
1404 /**
1405 * Sets the DisconnectCause for a call that was disconnected because it was missing a PhoneAccount
1406 * or PhoneAccounts to select from.
1407 */
1408 private void setDisconnectCauseForMissingAccounts(DialerCall call) {
1409
1410 Bundle extras = call.getIntentExtras();
1411 // Initialize the extras bundle to avoid NPE
1412 if (extras == null) {
1413 extras = new Bundle();
1414 }
1415
1416 final List<PhoneAccountHandle> phoneAccountHandles =
1417 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
1418
1419 if (phoneAccountHandles == null || phoneAccountHandles.isEmpty()) {
1420 String scheme = call.getHandle().getScheme();
1421 final String errorMsg =
1422 PhoneAccount.SCHEME_TEL.equals(scheme)
linyuh183cb712017-12-27 17:02:37 -08001423 ? context.getString(R.string.callFailed_simError)
1424 : context.getString(R.string.incall_error_supp_service_unknown);
Eric Erfanianccca3152017-02-22 16:32:36 -08001425 DisconnectCause disconnectCause =
1426 new DisconnectCause(DisconnectCause.ERROR, null, errorMsg, errorMsg);
1427 call.setDisconnectCause(disconnectCause);
1428 }
1429 }
1430
Eric Erfanianccca3152017-02-22 16:32:36 -08001431 /**
1432 * @return {@code true} if the InCallPresenter is ready to be torn down, {@code false} otherwise.
1433 * Calling classes should use this as an indication whether to interact with the
1434 * InCallPresenter or not.
1435 */
1436 public boolean isReadyForTearDown() {
linyuh183cb712017-12-27 17:02:37 -08001437 return inCallActivity == null && !serviceConnected && inCallState == InCallState.NO_CALLS;
Eric Erfanianccca3152017-02-22 16:32:36 -08001438 }
1439
1440 /**
1441 * Checks to see if both the UI is gone and the service is disconnected. If so, tear it all down.
1442 */
1443 private void attemptCleanup() {
1444 if (isReadyForTearDown()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001445 LogUtil.i("InCallPresenter.attemptCleanup", "Cleaning up");
Eric Erfanianccca3152017-02-22 16:32:36 -08001446
1447 cleanupSurfaces();
1448
linyuh183cb712017-12-27 17:02:37 -08001449 isChangingConfigurations = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001450
1451 // blow away stale contact info so that we get fresh data on
1452 // the next set of calls
linyuh183cb712017-12-27 17:02:37 -08001453 if (contactInfoCache != null) {
1454 contactInfoCache.clearCache();
Eric Erfanianccca3152017-02-22 16:32:36 -08001455 }
linyuh183cb712017-12-27 17:02:37 -08001456 contactInfoCache = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001457
linyuh183cb712017-12-27 17:02:37 -08001458 if (proximitySensor != null) {
1459 removeListener(proximitySensor);
1460 proximitySensor.tearDown();
Eric Erfanianccca3152017-02-22 16:32:36 -08001461 }
linyuh183cb712017-12-27 17:02:37 -08001462 proximitySensor = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001463
linyuh183cb712017-12-27 17:02:37 -08001464 if (statusBarNotifier != null) {
1465 removeListener(statusBarNotifier);
1466 EnrichedCallComponent.get(context)
Eric Erfaniand8046e52017-04-06 09:41:50 -07001467 .getEnrichedCallManager()
linyuh183cb712017-12-27 17:02:37 -08001468 .unregisterStateChangedListener(statusBarNotifier);
Eric Erfanianccca3152017-02-22 16:32:36 -08001469 }
Eric Erfaniand8046e52017-04-06 09:41:50 -07001470
linyuh183cb712017-12-27 17:02:37 -08001471 if (externalCallNotifier != null && externalCallList != null) {
1472 externalCallList.removeExternalCallListener(externalCallNotifier);
Eric Erfanianccca3152017-02-22 16:32:36 -08001473 }
linyuh183cb712017-12-27 17:02:37 -08001474 statusBarNotifier = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001475
linyuh183cb712017-12-27 17:02:37 -08001476 if (callList != null) {
1477 callList.removeListener(this);
1478 callList.removeListener(spamCallListListener);
Eric Erfanianccca3152017-02-22 16:32:36 -08001479 }
linyuh183cb712017-12-27 17:02:37 -08001480 callList = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001481
linyuh183cb712017-12-27 17:02:37 -08001482 context = null;
1483 inCallActivity = null;
1484 manageConferenceActivity = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001485
linyuh183cb712017-12-27 17:02:37 -08001486 listeners.clear();
1487 incomingCallListeners.clear();
1488 detailsListeners.clear();
1489 canAddCallListeners.clear();
1490 orientationListeners.clear();
1491 inCallEventListeners.clear();
1492 inCallUiListeners.clear();
1493 if (!inCallUiLocks.isEmpty()) {
1494 LogUtil.e("InCallPresenter.attemptCleanup", "held in call locks: " + inCallUiLocks);
1495 inCallUiLocks.clear();
twyen8efb4952017-10-06 16:35:54 -07001496 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001497 LogUtil.d("InCallPresenter.attemptCleanup", "finished");
Eric Erfanianccca3152017-02-22 16:32:36 -08001498 }
1499 }
1500
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001501 public void showInCall(boolean showDialpad, boolean newOutgoingCall) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001502 LogUtil.i("InCallPresenter.showInCall", "Showing InCallActivity");
linyuh183cb712017-12-27 17:02:37 -08001503 context.startActivity(
1504 InCallActivity.getIntent(context, showDialpad, newOutgoingCall, false /* forFullScreen */));
Eric Erfanianccca3152017-02-22 16:32:36 -08001505 }
1506
1507 public void onServiceBind() {
linyuh183cb712017-12-27 17:02:37 -08001508 serviceBound = true;
Eric Erfanianccca3152017-02-22 16:32:36 -08001509 }
1510
1511 public void onServiceUnbind() {
1512 InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(false, null);
linyuh183cb712017-12-27 17:02:37 -08001513 serviceBound = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001514 }
1515
1516 public boolean isServiceBound() {
linyuh183cb712017-12-27 17:02:37 -08001517 return serviceBound;
Eric Erfanianccca3152017-02-22 16:32:36 -08001518 }
1519
1520 public void maybeStartRevealAnimation(Intent intent) {
linyuh183cb712017-12-27 17:02:37 -08001521 if (intent == null || inCallActivity != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001522 return;
1523 }
1524 final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
1525 if (extras == null) {
1526 // Incoming call, just show the in-call UI directly.
1527 return;
1528 }
1529
1530 if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) {
1531 // Account selection dialog will show up so don't show the animation.
1532 return;
1533 }
1534
1535 final PhoneAccountHandle accountHandle =
1536 intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
1537 final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT);
Eric Erfanianccca3152017-02-22 16:32:36 -08001538
1539 InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(true, accountHandle);
1540
1541 final Intent activityIntent =
linyuh183cb712017-12-27 17:02:37 -08001542 InCallActivity.getIntent(context, false, true, false /* forFullScreen */);
Eric Erfanianccca3152017-02-22 16:32:36 -08001543 activityIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint);
linyuh183cb712017-12-27 17:02:37 -08001544 context.startActivity(activityIntent);
Eric Erfanianccca3152017-02-22 16:32:36 -08001545 }
1546
1547 /**
1548 * Retrieves the current in-call camera manager instance, creating if necessary.
1549 *
1550 * @return The {@link InCallCameraManager}.
1551 */
1552 public InCallCameraManager getInCallCameraManager() {
1553 synchronized (this) {
linyuh183cb712017-12-27 17:02:37 -08001554 if (inCallCameraManager == null) {
1555 inCallCameraManager = new InCallCameraManager(context);
Eric Erfanianccca3152017-02-22 16:32:36 -08001556 }
1557
linyuh183cb712017-12-27 17:02:37 -08001558 return inCallCameraManager;
Eric Erfanianccca3152017-02-22 16:32:36 -08001559 }
1560 }
1561
1562 /**
1563 * Notifies listeners of changes in orientation and notify calls of rotation angle change.
1564 *
1565 * @param orientation The screen orientation of the device (one of: {@link
1566 * InCallOrientationEventListener#SCREEN_ORIENTATION_0}, {@link
1567 * InCallOrientationEventListener#SCREEN_ORIENTATION_90}, {@link
1568 * InCallOrientationEventListener#SCREEN_ORIENTATION_180}, {@link
1569 * InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
1570 */
1571 public void onDeviceOrientationChange(@ScreenOrientation int orientation) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001572 LogUtil.d(
1573 "InCallPresenter.onDeviceOrientationChange",
1574 "onDeviceOrientationChange: orientation= " + orientation);
Eric Erfanianccca3152017-02-22 16:32:36 -08001575
linyuh183cb712017-12-27 17:02:37 -08001576 if (callList != null) {
1577 callList.notifyCallsOfDeviceRotation(orientation);
Eric Erfanianccca3152017-02-22 16:32:36 -08001578 } else {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001579 LogUtil.w("InCallPresenter.onDeviceOrientationChange", "CallList is null.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001580 }
1581
1582 // Notify listeners of device orientation changed.
linyuh183cb712017-12-27 17:02:37 -08001583 for (InCallOrientationListener listener : orientationListeners) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001584 listener.onDeviceOrientationChanged(orientation);
1585 }
1586 }
1587
1588 /**
1589 * Configures the in-call UI activity so it can change orientations or not. Enables the
1590 * orientation event listener if allowOrientationChange is true, disables it if false.
1591 *
1592 * @param allowOrientationChange {@code true} if the in-call UI can change between portrait and
1593 * landscape. {@code false} if the in-call UI should be locked in portrait.
1594 */
1595 public void setInCallAllowsOrientationChange(boolean allowOrientationChange) {
linyuh183cb712017-12-27 17:02:37 -08001596 if (inCallActivity == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001597 LogUtil.e(
1598 "InCallPresenter.setInCallAllowsOrientationChange",
1599 "InCallActivity is null. Can't set requested orientation.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001600 return;
1601 }
linyuh183cb712017-12-27 17:02:37 -08001602 inCallActivity.setAllowOrientationChange(allowOrientationChange);
Eric Erfanianccca3152017-02-22 16:32:36 -08001603 }
1604
1605 public void enableScreenTimeout(boolean enable) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001606 LogUtil.v("InCallPresenter.enableScreenTimeout", "enableScreenTimeout: value=" + enable);
linyuh183cb712017-12-27 17:02:37 -08001607 screenTimeoutEnabled = enable;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001608 applyScreenTimeout();
1609 }
1610
1611 private void applyScreenTimeout() {
linyuh183cb712017-12-27 17:02:37 -08001612 if (inCallActivity == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001613 LogUtil.e("InCallPresenter.applyScreenTimeout", "InCallActivity is null.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001614 return;
1615 }
1616
linyuh183cb712017-12-27 17:02:37 -08001617 final Window window = inCallActivity.getWindow();
1618 if (screenTimeoutEnabled) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001619 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1620 } else {
1621 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1622 }
1623 }
1624
1625 /**
1626 * Hides or shows the conference manager fragment.
1627 *
1628 * @param show {@code true} if the conference manager should be shown, {@code false} if it should
1629 * be hidden.
1630 */
1631 public void showConferenceCallManager(boolean show) {
linyuh183cb712017-12-27 17:02:37 -08001632 if (inCallActivity != null) {
1633 inCallActivity.showConferenceFragment(show);
Eric Erfanianccca3152017-02-22 16:32:36 -08001634 }
linyuh183cb712017-12-27 17:02:37 -08001635 if (!show && manageConferenceActivity != null) {
1636 manageConferenceActivity.finish();
Eric Erfanianccca3152017-02-22 16:32:36 -08001637 }
1638 }
1639
1640 /**
1641 * Determines if the dialpad is visible.
1642 *
1643 * @return {@code true} if the dialpad is visible, {@code false} otherwise.
1644 */
1645 public boolean isDialpadVisible() {
linyuh183cb712017-12-27 17:02:37 -08001646 if (inCallActivity == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001647 return false;
1648 }
linyuh183cb712017-12-27 17:02:37 -08001649 return inCallActivity.isDialpadVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -08001650 }
1651
1652 public ThemeColorManager getThemeColorManager() {
linyuh183cb712017-12-27 17:02:37 -08001653 return themeColorManager;
Eric Erfanianccca3152017-02-22 16:32:36 -08001654 }
1655
wangqi8d662ca2017-10-26 11:27:19 -07001656 @VisibleForTesting
1657 public void setThemeColorManager(ThemeColorManager themeColorManager) {
linyuh183cb712017-12-27 17:02:37 -08001658 this.themeColorManager = themeColorManager;
wangqi8d662ca2017-10-26 11:27:19 -07001659 }
1660
Eric Erfanianccca3152017-02-22 16:32:36 -08001661 /** Called when the foreground call changes. */
1662 public void onForegroundCallChanged(DialerCall newForegroundCall) {
linyuh183cb712017-12-27 17:02:37 -08001663 themeColorManager.onForegroundCallChanged(context, newForegroundCall);
1664 if (inCallActivity != null) {
1665 inCallActivity.onForegroundCallChanged(newForegroundCall);
Eric Erfanianccca3152017-02-22 16:32:36 -08001666 }
1667 }
1668
1669 public InCallActivity getActivity() {
linyuh183cb712017-12-27 17:02:37 -08001670 return inCallActivity;
Eric Erfanianccca3152017-02-22 16:32:36 -08001671 }
1672
1673 /** Called when the UI begins, and starts the callstate callbacks if necessary. */
1674 public void setActivity(InCallActivity inCallActivity) {
1675 if (inCallActivity == null) {
1676 throw new IllegalArgumentException("registerActivity cannot be called with null");
1677 }
linyuh183cb712017-12-27 17:02:37 -08001678 if (this.inCallActivity != null && this.inCallActivity != inCallActivity) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001679 LogUtil.w(
1680 "InCallPresenter.setActivity", "Setting a second activity before destroying the first.");
Eric Erfanianccca3152017-02-22 16:32:36 -08001681 }
1682 updateActivity(inCallActivity);
1683 }
1684
1685 ExternalCallNotifier getExternalCallNotifier() {
linyuh183cb712017-12-27 17:02:37 -08001686 return externalCallNotifier;
Eric Erfanianccca3152017-02-22 16:32:36 -08001687 }
1688
1689 VideoSurfaceTexture getLocalVideoSurfaceTexture() {
linyuh183cb712017-12-27 17:02:37 -08001690 if (localVideoSurfaceTexture == null) {
roldenburg12b50c62017-09-01 14:41:19 -07001691 boolean isPixel2017 = false;
linyuh183cb712017-12-27 17:02:37 -08001692 if (context != null) {
1693 isPixel2017 = context.getPackageManager().hasSystemFeature(PIXEL2017_SYSTEM_FEATURE);
roldenburg12b50c62017-09-01 14:41:19 -07001694 }
linyuh183cb712017-12-27 17:02:37 -08001695 localVideoSurfaceTexture = VideoSurfaceBindings.createLocalVideoSurfaceTexture(isPixel2017);
Eric Erfanianccca3152017-02-22 16:32:36 -08001696 }
linyuh183cb712017-12-27 17:02:37 -08001697 return localVideoSurfaceTexture;
Eric Erfanianccca3152017-02-22 16:32:36 -08001698 }
1699
1700 VideoSurfaceTexture getRemoteVideoSurfaceTexture() {
linyuh183cb712017-12-27 17:02:37 -08001701 if (remoteVideoSurfaceTexture == null) {
roldenburg12b50c62017-09-01 14:41:19 -07001702 boolean isPixel2017 = false;
linyuh183cb712017-12-27 17:02:37 -08001703 if (context != null) {
1704 isPixel2017 = context.getPackageManager().hasSystemFeature(PIXEL2017_SYSTEM_FEATURE);
roldenburg12b50c62017-09-01 14:41:19 -07001705 }
linyuh183cb712017-12-27 17:02:37 -08001706 remoteVideoSurfaceTexture = VideoSurfaceBindings.createRemoteVideoSurfaceTexture(isPixel2017);
Eric Erfanianccca3152017-02-22 16:32:36 -08001707 }
linyuh183cb712017-12-27 17:02:37 -08001708 return remoteVideoSurfaceTexture;
Eric Erfanianccca3152017-02-22 16:32:36 -08001709 }
1710
1711 void cleanupSurfaces() {
linyuh183cb712017-12-27 17:02:37 -08001712 if (remoteVideoSurfaceTexture != null) {
1713 remoteVideoSurfaceTexture.setDoneWithSurface();
1714 remoteVideoSurfaceTexture = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001715 }
linyuh183cb712017-12-27 17:02:37 -08001716 if (localVideoSurfaceTexture != null) {
1717 localVideoSurfaceTexture.setDoneWithSurface();
1718 localVideoSurfaceTexture = null;
Eric Erfanianccca3152017-02-22 16:32:36 -08001719 }
1720 }
1721
yueg77cb8e52017-10-27 16:42:51 -07001722 @Override
1723 public void onAudioStateChanged(CallAudioState audioState) {
linyuh183cb712017-12-27 17:02:37 -08001724 if (statusBarNotifier != null) {
1725 statusBarNotifier.updateNotification();
yueg77cb8e52017-10-27 16:42:51 -07001726 }
1727 }
1728
Eric Erfanianccca3152017-02-22 16:32:36 -08001729 /** All the main states of InCallActivity. */
1730 public enum InCallState {
1731 // InCall Screen is off and there are no calls
1732 NO_CALLS,
1733
1734 // Incoming-call screen is up
1735 INCOMING,
1736
1737 // In-call experience is showing
1738 INCALL,
1739
1740 // Waiting for user input before placing outgoing call
1741 WAITING_FOR_ACCOUNT,
1742
1743 // UI is starting up but no call has been initiated yet.
1744 // The UI is waiting for Telecom to respond.
1745 PENDING_OUTGOING,
1746
1747 // User is dialing out
1748 OUTGOING;
1749
1750 public boolean isIncoming() {
1751 return (this == INCOMING);
1752 }
1753
1754 public boolean isConnectingOrConnected() {
1755 return (this == INCOMING || this == OUTGOING || this == INCALL);
1756 }
1757 }
1758
1759 /** Interface implemented by classes that need to know about the InCall State. */
1760 public interface InCallStateListener {
1761
1762 // TODO: Enhance state to contain the call objects instead of passing CallList
1763 void onStateChange(InCallState oldState, InCallState newState, CallList callList);
1764 }
1765
1766 public interface IncomingCallListener {
1767
1768 void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call);
1769 }
1770
1771 public interface CanAddCallListener {
1772
1773 void onCanAddCallChanged(boolean canAddCall);
1774 }
1775
1776 public interface InCallDetailsListener {
1777
1778 void onDetailsChanged(DialerCall call, android.telecom.Call.Details details);
1779 }
1780
1781 public interface InCallOrientationListener {
1782
1783 void onDeviceOrientationChanged(@ScreenOrientation int orientation);
1784 }
1785
1786 /**
1787 * Interface implemented by classes that need to know about events which occur within the In-Call
1788 * UI. Used as a means of communicating between fragments that make up the UI.
1789 */
1790 public interface InCallEventListener {
1791
1792 void onFullscreenModeChanged(boolean isFullscreenMode);
1793 }
1794
1795 public interface InCallUiListener {
1796
1797 void onUiShowing(boolean showing);
1798 }
twyen8efb4952017-10-06 16:35:54 -07001799
1800 private class InCallUiLockImpl implements InCallUiLock {
1801 private final String tag;
1802
1803 private InCallUiLockImpl(String tag) {
1804 this.tag = tag;
1805 }
1806
1807 @MainThread
1808 @Override
1809 public void release() {
1810 Assert.isMainThread();
1811 releaseInCallUiLock(InCallUiLockImpl.this);
1812 }
1813
1814 @Override
1815 public String toString() {
1816 return "InCallUiLock[" + tag + "]";
1817 }
1818 }
1819
1820 @MainThread
1821 public InCallUiLock acquireInCallUiLock(String tag) {
1822 Assert.isMainThread();
1823 InCallUiLock lock = new InCallUiLockImpl(tag);
linyuh183cb712017-12-27 17:02:37 -08001824 inCallUiLocks.add(lock);
twyen8efb4952017-10-06 16:35:54 -07001825 return lock;
1826 }
1827
1828 @MainThread
1829 private void releaseInCallUiLock(InCallUiLock lock) {
1830 Assert.isMainThread();
1831 LogUtil.i("InCallPresenter.releaseInCallUiLock", "releasing %s", lock);
linyuh183cb712017-12-27 17:02:37 -08001832 inCallUiLocks.remove(lock);
1833 if (inCallUiLocks.isEmpty()) {
twyen8efb4952017-10-06 16:35:54 -07001834 LogUtil.i("InCallPresenter.releaseInCallUiLock", "all locks released");
linyuh183cb712017-12-27 17:02:37 -08001835 if (inCallState == InCallState.NO_CALLS) {
twyen8efb4952017-10-06 16:35:54 -07001836 LogUtil.i("InCallPresenter.releaseInCallUiLock", "no more calls, finishing UI");
1837 attemptFinishActivity();
1838 attemptCleanup();
1839 }
1840 }
1841 }
1842
1843 @MainThread
1844 public boolean isInCallUiLocked() {
1845 Assert.isMainThread();
linyuh183cb712017-12-27 17:02:37 -08001846 if (inCallUiLocks.isEmpty()) {
twyen8efb4952017-10-06 16:35:54 -07001847 return false;
1848 }
linyuh183cb712017-12-27 17:02:37 -08001849 for (InCallUiLock lock : inCallUiLocks) {
twyen8efb4952017-10-06 16:35:54 -07001850 LogUtil.i("InCallPresenter.isInCallUiLocked", "still locked by %s", lock);
1851 }
1852 return true;
1853 }
1854
linyuh183cb712017-12-27 17:02:37 -08001855 private final Set<InCallUiLock> inCallUiLocks = new ArraySet<>();
Eric Erfanianccca3152017-02-22 16:32:36 -08001856}