blob: d2aae485d8aa1b04c474629a69fa010654dffdc2 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.incallui;
import android.app.ActivityManager;
import android.app.ActivityManager.AppTask;
import android.app.Dialog;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Trace;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.telecom.CallAudioState;
import android.telecom.PhoneAccountHandle;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
import com.android.dialer.animation.AnimUtils;
import com.android.dialer.animation.AnimationListenerAdapter;
import com.android.dialer.common.LogUtil;
import com.android.dialer.compat.CompatUtils;
import com.android.dialer.logging.Logger;
import com.android.dialer.logging.ScreenEvent;
import com.android.dialer.util.ViewUtil;
import com.android.incallui.audiomode.AudioModeProvider;
import com.android.incallui.call.CallList;
import com.android.incallui.call.DialerCall;
import com.android.incallui.call.DialerCall.State;
import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment.Callback;
import com.google.common.base.Optional;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/** Shared functionality between the new and old in call activity. */
public class InCallActivityCommon {
private static final String INTENT_EXTRA_SHOW_DIALPAD = "InCallActivity.show_dialpad";
private static final String INTENT_EXTRA_NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
private static final String INTENT_EXTRA_FOR_FULL_SCREEN =
"InCallActivity.for_full_screen_intent";
private static final String DIALPAD_TEXT_KEY = "InCallActivity.dialpad_text";
private static final String TAG_SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
private static final String TAG_INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DIALPAD_REQUEST_NONE,
DIALPAD_REQUEST_SHOW,
DIALPAD_REQUEST_HIDE,
})
@interface DialpadRequestType {}
private static final int DIALPAD_REQUEST_NONE = 1;
private static final int DIALPAD_REQUEST_SHOW = 2;
private static final int DIALPAD_REQUEST_HIDE = 3;
private static Optional<Integer> audioRouteForTesting = Optional.absent();
private final InCallActivity inCallActivity;
private boolean showPostCharWaitDialogOnResume;
private String showPostCharWaitDialogCallId;
private String showPostCharWaitDialogChars;
private Dialog errorDialog;
private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
private Animation dialpadSlideInAnimation;
private Animation dialpadSlideOutAnimation;
private boolean animateDialpadOnShow;
private String dtmfTextToPreopulate;
@DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
// If activity is going to be recreated. This is usually happening in {@link onNewIntent}.
private boolean isRecreating;
private final SelectPhoneAccountListener selectAccountListener =
new SelectPhoneAccountListener() {
@Override
public void onPhoneAccountSelected(
PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
DialerCall call = CallList.getInstance().getCallById(callId);
LogUtil.i(
"InCallActivityCommon.SelectPhoneAccountListener.onPhoneAccountSelected",
"call: " + call);
if (call != null) {
call.phoneAccountSelected(selectedAccountHandle, setDefault);
}
}
@Override
public void onDialogDismissed(String callId) {
DialerCall call = CallList.getInstance().getCallById(callId);
LogUtil.i(
"InCallActivityCommon.SelectPhoneAccountListener.onDialogDismissed",
"disconnecting call: " + call);
if (call != null) {
call.disconnect();
}
}
};
private InternationalCallOnWifiDialogFragment.Callback internationalCallOnWifiCallback =
new Callback() {
@Override
public void continueCall(@NonNull String callId) {
LogUtil.i("InCallActivityCommon.continueCall", "continuing call with id: %s", callId);
}
@Override
public void cancelCall(@NonNull String callId) {
DialerCall call = CallList.getInstance().getCallById(callId);
if (call == null) {
LogUtil.i("InCallActivityCommon.cancelCall", "call destroyed before dialog closed");
return;
}
LogUtil.i("InCallActivityCommon.cancelCall", "disconnecting international call on wifi");
call.disconnect();
}
};
public static void setIntentExtras(
Intent intent, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
if (showDialpad) {
intent.putExtra(INTENT_EXTRA_SHOW_DIALPAD, true);
}
intent.putExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, newOutgoingCall);
intent.putExtra(INTENT_EXTRA_FOR_FULL_SCREEN, isForFullScreen);
}
public InCallActivityCommon(InCallActivity inCallActivity) {
this.inCallActivity = inCallActivity;
}
public void onCreate(Bundle icicle) {
setWindowFlags();
inCallActivity.setContentView(R.layout.incall_screen);
internalResolveIntent(inCallActivity.getIntent());
boolean isLandscape =
inCallActivity.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
boolean isRtl = ViewUtil.isRtl();
if (isLandscape) {
dialpadSlideInAnimation =
AnimationUtils.loadAnimation(
inCallActivity, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
dialpadSlideOutAnimation =
AnimationUtils.loadAnimation(
inCallActivity,
isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
} else {
dialpadSlideInAnimation =
AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_in_bottom);
dialpadSlideOutAnimation =
AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_out_bottom);
}
dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
dialpadSlideOutAnimation.setAnimationListener(
new AnimationListenerAdapter() {
@Override
public void onAnimationEnd(Animation animation) {
performHideDialpadFragment();
}
});
// Don't override the value if show dialpad request is true in intent extras.
if (icicle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) {
// If the dialpad was shown before, set variables indicating it should be shown and
// populated with the previous DTMF text. The dialpad is actually shown and populated
// in onResume() to ensure the hosting fragment has been inflated and is ready to receive it.
if (icicle.containsKey(INTENT_EXTRA_SHOW_DIALPAD)) {
boolean showDialpad = icicle.getBoolean(INTENT_EXTRA_SHOW_DIALPAD);
showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
animateDialpadOnShow = false;
}
dtmfTextToPreopulate = icicle.getString(DIALPAD_TEXT_KEY);
SelectPhoneAccountDialogFragment dialogFragment =
(SelectPhoneAccountDialogFragment)
inCallActivity.getFragmentManager().findFragmentByTag(TAG_SELECT_ACCOUNT_FRAGMENT);
if (dialogFragment != null) {
dialogFragment.setListener(selectAccountListener);
}
}
InternationalCallOnWifiDialogFragment existingInternationalFragment =
(InternationalCallOnWifiDialogFragment)
inCallActivity
.getSupportFragmentManager()
.findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
if (existingInternationalFragment != null) {
LogUtil.i(
"InCallActivityCommon.onCreate", "international fragment exists attaching callback");
existingInternationalFragment.setCallback(internationalCallOnWifiCallback);
}
}
public void onResume() {
Trace.beginSection("InCallActivityCommon.onResume");
if (InCallPresenter.getInstance().isReadyForTearDown()) {
LogUtil.i(
"InCallActivityCommon.onResume",
"InCallPresenter is ready for tear down, not sending updates");
} else {
inCallActivity.updateTaskDescription();
InCallPresenter.getInstance().onUiShowing(true);
}
// If there is a pending request to show or hide the dialpad, handle that now.
if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
// Exit fullscreen so that the user has access to the dialpad hide/show button and
// can hide the dialpad. Important when showing the dialpad from within dialer.
InCallPresenter.getInstance().setFullScreen(false, true /* force */);
inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
animateDialpadOnShow = false;
DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
if (dialpadFragment != null) {
dialpadFragment.setDtmfText(dtmfTextToPreopulate);
dtmfTextToPreopulate = null;
}
} else {
LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad");
if (inCallActivity.getDialpadFragment() != null) {
inCallActivity.showDialpadFragment(false /* show */, false /* animate */);
}
}
showDialpadRequest = DIALPAD_REQUEST_NONE;
}
inCallActivity.updateNavigationBar(inCallActivity.isDialpadVisible());
if (showPostCharWaitDialogOnResume) {
showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
}
CallList.getInstance()
.onInCallUiShown(
inCallActivity.getIntent().getBooleanExtra(INTENT_EXTRA_FOR_FULL_SCREEN, false));
Trace.endSection();
}
void onNewIntent(Intent intent, boolean isRecreating) {
LogUtil.i("InCallActivityCommon.onNewIntent", "");
this.isRecreating = isRecreating;
// We're being re-launched with a new Intent. Since it's possible for a
// single InCallActivity instance to persist indefinitely (even if we
// finish() ourselves), this sequence can potentially happen any time
// the InCallActivity needs to be displayed.
// Stash away the new intent so that we can get it in the future
// by calling getIntent(). (Otherwise getIntent() will return the
// original Intent from when we first got created!)
inCallActivity.setIntent(intent);
// Activities are always paused before receiving a new intent, so
// we can count on our onResume() method being called next.
// Just like in onCreate(), handle the intent.
// Skip if InCallActivity is going to recreate since this will be called in onCreate().
if (!isRecreating) {
internalResolveIntent(intent);
}
}
private void setWindowFlags() {
// Allow the activity to be shown when the screen is locked and filter out touch events that are
// "too fat".
int flags =
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
// When the audio stream is not directed through Bluetooth, turn the screen on once the
// activity is shown.
final int audioRoute = getAudioRoute();
if (audioRoute != CallAudioState.ROUTE_BLUETOOTH) {
flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
}
inCallActivity.getWindow().addFlags(flags);
}
private static int getAudioRoute() {
if (audioRouteForTesting.isPresent()) {
return audioRouteForTesting.get();
}
return AudioModeProvider.getInstance().getAudioState().getRoute();
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
public static void setAudioRouteForTesting(int audioRoute) {
audioRouteForTesting = Optional.of(audioRoute);
}
public void showPostCharWaitDialog(String callId, String chars) {
if (inCallActivity.isVisible()) {
PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
fragment.show(inCallActivity.getSupportFragmentManager(), "postCharWait");
showPostCharWaitDialogOnResume = false;
showPostCharWaitDialogCallId = null;
showPostCharWaitDialogChars = null;
} else {
showPostCharWaitDialogOnResume = true;
showPostCharWaitDialogCallId = callId;
showPostCharWaitDialogChars = chars;
}
}
/**
* When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
* be shown on launch.
*
* @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
* false} to indicate no change should be made to the dialpad visibility.
*/
private void relaunchedFromDialer(boolean showDialpad) {
showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
animateDialpadOnShow = true;
if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
// If there's only one line in use, AND it's on hold, then we're sure the user
// wants to use the dialpad toward the exact line, so un-hold the holding line.
DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
if (call != null && call.getState() == State.ONHOLD) {
call.unhold();
}
}
}
public void setExcludeFromRecents(boolean exclude) {
List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
int taskId = inCallActivity.getTaskId();
for (int i = 0; i < tasks.size(); i++) {
ActivityManager.AppTask task = tasks.get(i);
try {
if (task.getTaskInfo().id == taskId) {
task.setExcludeFromRecents(exclude);
}
} catch (RuntimeException e) {
LogUtil.e(
"InCallActivityCommon.setExcludeFromRecents",
"RuntimeException when excluding task from recents.",
e);
}
}
}
public boolean showDialpadFragment(boolean show, boolean animate) {
// If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
boolean isDialpadVisible = inCallActivity.isDialpadVisible();
LogUtil.i(
"InCallActivityCommon.showDialpadFragment",
"show: %b, animate: %b, " + "isDialpadVisible: %b",
show,
animate,
isDialpadVisible);
if (show == isDialpadVisible) {
return false;
}
FragmentManager dialpadFragmentManager = inCallActivity.getDialpadFragmentManager();
if (dialpadFragmentManager == null) {
LogUtil.i(
"InCallActivityCommon.showDialpadFragment", "unable to show or hide dialpad fragment");
return false;
}
// We don't do a FragmentTransaction on the hide case because it will be dealt with when
// the listener is fired after an animation finishes.
if (!animate) {
if (show) {
performShowDialpadFragment(dialpadFragmentManager);
} else {
performHideDialpadFragment();
}
} else {
if (show) {
performShowDialpadFragment(dialpadFragmentManager);
inCallActivity.getDialpadFragment().animateShowDialpad();
}
inCallActivity
.getDialpadFragment()
.getView()
.startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
}
ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
if (sensor != null) {
sensor.onDialpadVisible(show);
}
showDialpadRequest = DIALPAD_REQUEST_NONE;
return true;
}
private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
if (dialpadFragment == null) {
transaction.add(
inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
} else {
transaction.show(dialpadFragment);
}
transaction.commitAllowingStateLoss();
dialpadFragmentManager.executePendingTransactions();
Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
inCallActivity.updateNavigationBar(true /* isDialpadVisible */);
}
private void performHideDialpadFragment() {
FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
if (fragmentManager == null) {
LogUtil.e(
"InCallActivityCommon.performHideDialpadFragment", "child fragment manager is null");
return;
}
Fragment fragment = fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
if (fragment != null) {
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.hide(fragment);
transaction.commitAllowingStateLoss();
fragmentManager.executePendingTransactions();
}
inCallActivity.updateNavigationBar(false /* isDialpadVisible */);
}
private void internalResolveIntent(Intent intent) {
if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
return;
}
if (intent.hasExtra(INTENT_EXTRA_SHOW_DIALPAD)) {
// SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
// dialpad should be initially visible. If the extra isn't
// present at all, we just leave the dialpad in its previous state.
boolean showDialpad = intent.getBooleanExtra(INTENT_EXTRA_SHOW_DIALPAD, false);
LogUtil.i("InCallActivityCommon.internalResolveIntent", "SHOW_DIALPAD_EXTRA: " + showDialpad);
relaunchedFromDialer(showDialpad);
}
DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
if (outgoingCall == null) {
outgoingCall = CallList.getInstance().getPendingOutgoingCall();
}
if (intent.getBooleanExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, false)) {
intent.removeExtra(INTENT_EXTRA_NEW_OUTGOING_CALL);
// InCallActivity is responsible for disconnecting a new outgoing call if there
// is no way of making it (i.e. no valid call capable accounts).
// If the version is not MSIM compatible, then ignore this code.
if (CompatUtils.isMSIMCompatible()
&& InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
LogUtil.i(
"InCallActivityCommon.internalResolveIntent",
"call with no valid accounts, disconnecting");
outgoingCall.disconnect();
}
inCallActivity.dismissKeyguard(true);
}
boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
if (didShowAccountSelectionDialog) {
inCallActivity.hideMainInCallFragment();
}
}
private boolean maybeShowAccountSelectionDialog() {
DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
if (waitingForAccountCall == null) {
return false;
}
Bundle extras = waitingForAccountCall.getIntentExtras();
List<PhoneAccountHandle> phoneAccountHandles;
if (extras != null) {
phoneAccountHandles =
extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
} else {
phoneAccountHandles = new ArrayList<>();
}
selectPhoneAccountDialogFragment =
SelectPhoneAccountDialogFragment.newInstance(
R.string.select_phone_account_for_calls,
true,
0,
phoneAccountHandles,
selectAccountListener,
waitingForAccountCall.getId(),
null);
selectPhoneAccountDialogFragment.show(
inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
return true;
}
/** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
@Deprecated
@Nullable
Dialog getErrorDialog() {
return errorDialog;
}
/** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
@Deprecated
void setErrorDialog(@Nullable Dialog errorDialog) {
this.errorDialog = errorDialog;
}
/** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
@Deprecated
boolean getIsRecreating() {
return isRecreating;
}
/** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
@Deprecated
@Nullable
SelectPhoneAccountDialogFragment getSelectPhoneAccountDialogFragment() {
return selectPhoneAccountDialogFragment;
}
/** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
@Deprecated
void setSelectPhoneAccountDialogFragment(
@Nullable SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment) {
this.selectPhoneAccountDialogFragment = selectPhoneAccountDialogFragment;
}
/** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
@Deprecated
InternationalCallOnWifiDialogFragment.Callback getCallbackForInternationalCallOnWifiDialog() {
return internationalCallOnWifiCallback;
}
}