blob: eaaedff2ccd9a57b3e550e5067a149c09ad54522 [file] [log] [blame]
/*
* Copyright (C) 2006 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.ActionBar;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Point;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Trace;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import com.android.contacts.common.activity.TransactionSafeActivity;
import com.android.contacts.common.compat.CompatUtils;
import com.android.contacts.common.interactions.TouchPointManager;
import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
import com.android.dialer.R;
import com.android.dialer.logging.Logger;
import com.android.dialer.logging.ScreenEvent;
import com.android.incallui.Call.State;
import com.android.incallui.util.AccessibilityUtil;
import com.android.phone.common.animation.AnimUtils;
import com.android.phone.common.animation.AnimationListenerAdapter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Main activity that the user interacts with while in a live call.
*/
public class InCallActivity extends TransactionSafeActivity implements FragmentDisplayManager {
public static final String TAG = InCallActivity.class.getSimpleName();
public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad";
public static final String DIALPAD_TEXT_EXTRA = "InCallActivity.dialpad_text";
public static final String NEW_OUTGOING_CALL_EXTRA = "InCallActivity.new_outgoing_call";
public static final String FOR_FULL_SCREEN_INTENT = "InCallActivity.for_full_screen_intent";
private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
private static final String TAG_CONFERENCE_FRAGMENT = "tag_conference_manager_fragment";
private static final String TAG_CALLCARD_FRAGMENT = "tag_callcard_fragment";
private static final String TAG_ANSWER_FRAGMENT = "tag_answer_fragment";
private static final String TAG_SELECT_ACCT_FRAGMENT = "tag_select_acct_fragment";
private static final int DIALPAD_REQUEST_NONE = 1;
private static final int DIALPAD_REQUEST_SHOW = 2;
private static final int DIALPAD_REQUEST_HIDE = 3;
/**
* This is used to relaunch the activity if resizing beyond which it needs to load different
* layout file.
*/
private static final int SCREEN_HEIGHT_RESIZE_THRESHOLD = 500;
private CallButtonFragment mCallButtonFragment;
private CallCardFragment mCallCardFragment;
private AnswerFragment mAnswerFragment;
private DialpadFragment mDialpadFragment;
private ConferenceManagerFragment mConferenceManagerFragment;
private FragmentManager mChildFragmentManager;
private AlertDialog mDialog;
private InCallOrientationEventListener mInCallOrientationEventListener;
/**
* Used to indicate whether the dialpad should be hidden or shown {@link #onResume}.
* {@code #DIALPAD_REQUEST_SHOW} indicates that the dialpad should be shown.
* {@code #DIALPAD_REQUEST_HIDE} indicates that the dialpad should be hidden.
* {@code #DIALPAD_REQUEST_NONE} indicates no change should be made to dialpad visibility.
*/
private int mShowDialpadRequest = DIALPAD_REQUEST_NONE;
/**
* Use to determine if the dialpad should be animated on show.
*/
private boolean mAnimateDialpadOnShow;
/**
* Use to determine the DTMF Text which should be pre-populated in the dialpad.
*/
private String mDtmfText;
/**
* Use to pass parameters for showing the PostCharDialog to {@link #onResume}
*/
private boolean mShowPostCharWaitDialogOnResume;
private String mShowPostCharWaitDialogCallId;
private String mShowPostCharWaitDialogChars;
private boolean mIsLandscape;
private Animation mSlideIn;
private Animation mSlideOut;
private boolean mDismissKeyguard = false;
AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() {
@Override
public void onAnimationEnd(Animation animation) {
showFragment(TAG_DIALPAD_FRAGMENT, false, true);
}
};
private OnTouchListener mDispatchTouchEventListener;
private SelectPhoneAccountListener mSelectAcctListener = new SelectPhoneAccountListener() {
@Override
public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle,
boolean setDefault) {
InCallPresenter.getInstance().handleAccountSelection(selectedAccountHandle,
setDefault);
}
@Override
public void onDialogDismissed() {
InCallPresenter.getInstance().cancelAccountSelection();
}
};
@Override
protected void onCreate(Bundle icicle) {
Log.d(this, "onCreate()... this = " + this);
super.onCreate(icicle);
// set this flag so this activity will stay in front of the keyguard
// Have the WindowManager filter out touch events that are "too fat".
int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
getWindow().addFlags(flags);
// Setup action bar for the conference call manager.
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
actionBar.hide();
}
// TODO(klp): Do we need to add this back when prox sensor is not available?
// lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
setContentView(R.layout.incall_screen);
internalResolveIntent(getIntent());
mIsLandscape = getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
final boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
View.LAYOUT_DIRECTION_RTL;
if (mIsLandscape) {
mSlideIn = AnimationUtils.loadAnimation(this,
isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
mSlideOut = AnimationUtils.loadAnimation(this,
isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
} else {
mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
}
mSlideIn.setInterpolator(AnimUtils.EASE_IN);
mSlideOut.setInterpolator(AnimUtils.EASE_OUT);
mSlideOut.setAnimationListener(mSlideOutListener);
// If the dialpad fragment already exists, retrieve it. This is important when rotating as
// we will not be able to hide or show the dialpad after the rotation otherwise.
Fragment existingFragment =
getFragmentManager().findFragmentByTag(DialpadFragment.class.getName());
if (existingFragment != null) {
mDialpadFragment = (DialpadFragment) existingFragment;
}
if (icicle != null) {
// 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 CallCardFragment has been inflated and is ready
// to receive it.
if (icicle.containsKey(SHOW_DIALPAD_EXTRA)) {
boolean showDialpad = icicle.getBoolean(SHOW_DIALPAD_EXTRA);
mShowDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
mAnimateDialpadOnShow = false;
}
mDtmfText = icicle.getString(DIALPAD_TEXT_EXTRA);
SelectPhoneAccountDialogFragment dialogFragment = (SelectPhoneAccountDialogFragment)
getFragmentManager().findFragmentByTag(TAG_SELECT_ACCT_FRAGMENT);
if (dialogFragment != null) {
dialogFragment.setListener(mSelectAcctListener);
}
}
mInCallOrientationEventListener = new InCallOrientationEventListener(this);
Log.d(this, "onCreate(): exit");
}
@Override
protected void onSaveInstanceState(Bundle out) {
// TODO: The dialpad fragment should handle this as part of its own state
out.putBoolean(SHOW_DIALPAD_EXTRA,
mCallButtonFragment != null && mCallButtonFragment.isDialpadVisible());
if (mDialpadFragment != null) {
out.putString(DIALPAD_TEXT_EXTRA, mDialpadFragment.getDtmfText());
}
super.onSaveInstanceState(out);
}
@Override
protected void onStart() {
Log.d(this, "onStart()...");
super.onStart();
// setting activity should be last thing in setup process
InCallPresenter.getInstance().setActivity(this);
enableInCallOrientationEventListener(getRequestedOrientation() ==
InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION);
InCallPresenter.getInstance().onActivityStarted();
}
@Override
protected void onResume() {
Log.i(this, "onResume()...");
super.onResume();
InCallPresenter.getInstance().setThemeColors();
InCallPresenter.getInstance().onUiShowing(true);
// Clear fullscreen state onResume; the stored value may not match reality.
InCallPresenter.getInstance().clearFullscreen();
// If there is a pending request to show or hide the dialpad, handle that now.
if (mShowDialpadRequest != DIALPAD_REQUEST_NONE) {
if (mShowDialpadRequest == 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 */);
mCallButtonFragment.displayDialpad(true /* show */,
mAnimateDialpadOnShow /* animate */);
mAnimateDialpadOnShow = false;
if (mDialpadFragment != null) {
mDialpadFragment.setDtmfText(mDtmfText);
mDtmfText = null;
}
} else {
Log.v(this, "onResume : force hide dialpad");
if (mDialpadFragment != null) {
mCallButtonFragment.displayDialpad(false /* show */, false /* animate */);
}
}
mShowDialpadRequest = DIALPAD_REQUEST_NONE;
}
if (mShowPostCharWaitDialogOnResume) {
showPostCharWaitDialog(mShowPostCharWaitDialogCallId, mShowPostCharWaitDialogChars);
}
CallList.getInstance().onInCallUiShown(
getIntent().getBooleanExtra(FOR_FULL_SCREEN_INTENT, false));
}
// onPause is guaranteed to be called when the InCallActivity goes
// in the background.
@Override
protected void onPause() {
Log.d(this, "onPause()...");
if (mDialpadFragment != null) {
mDialpadFragment.onDialerKeyUp(null);
}
InCallPresenter.getInstance().onUiShowing(false);
if (isFinishing()) {
InCallPresenter.getInstance().unsetActivity(this);
}
super.onPause();
}
@Override
protected void onStop() {
Log.d(this, "onStop()...");
enableInCallOrientationEventListener(false);
InCallPresenter.getInstance().updateIsChangingConfigurations();
InCallPresenter.getInstance().onActivityStopped();
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(this, "onDestroy()... this = " + this);
InCallPresenter.getInstance().unsetActivity(this);
InCallPresenter.getInstance().updateIsChangingConfigurations();
super.onDestroy();
}
/**
* When fragments have a parent fragment, onAttachFragment is not called on the parent
* activity. To fix this, register our own callback instead that is always called for
* all fragments.
*
* @see {@link BaseFragment#onAttach(Activity)}
*/
@Override
public void onFragmentAttached(Fragment fragment) {
if (fragment instanceof DialpadFragment) {
mDialpadFragment = (DialpadFragment) fragment;
} else if (fragment instanceof AnswerFragment) {
mAnswerFragment = (AnswerFragment) fragment;
} else if (fragment instanceof CallCardFragment) {
mCallCardFragment = (CallCardFragment) fragment;
mChildFragmentManager = mCallCardFragment.getChildFragmentManager();
} else if (fragment instanceof ConferenceManagerFragment) {
mConferenceManagerFragment = (ConferenceManagerFragment) fragment;
} else if (fragment instanceof CallButtonFragment) {
mCallButtonFragment = (CallButtonFragment) fragment;
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Configuration oldConfig = getResources().getConfiguration();
Log.v(this, String.format(
"incallui config changed, screen size: w%ddp x h%ddp old:w%ddp x h%ddp",
newConfig.screenWidthDp, newConfig.screenHeightDp,
oldConfig.screenWidthDp, oldConfig.screenHeightDp));
// Recreate this activity if height is changing beyond the threshold to load different
// layout file.
if (oldConfig.screenHeightDp < SCREEN_HEIGHT_RESIZE_THRESHOLD &&
newConfig.screenHeightDp > SCREEN_HEIGHT_RESIZE_THRESHOLD ||
oldConfig.screenHeightDp > SCREEN_HEIGHT_RESIZE_THRESHOLD &&
newConfig.screenHeightDp < SCREEN_HEIGHT_RESIZE_THRESHOLD) {
Log.i(this, String.format(
"Recreate activity due to resize beyond threshold: %d dp",
SCREEN_HEIGHT_RESIZE_THRESHOLD));
recreate();
}
}
/**
* Returns true when the Activity is currently visible.
*/
/* package */ boolean isVisible() {
return isSafeToCommitTransactions();
}
private boolean hasPendingDialogs() {
return mDialog != null || (mAnswerFragment != null && mAnswerFragment.hasPendingDialogs());
}
@Override
public void finish() {
Log.i(this, "finish(). Dialog showing: " + (mDialog != null));
// skip finish if we are still showing a dialog.
if (!hasPendingDialogs()) {
super.finish();
}
}
@Override
protected void onNewIntent(Intent intent) {
Log.d(this, "onNewIntent: intent = " + intent);
// 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!)
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.
internalResolveIntent(intent);
}
@Override
public void onBackPressed() {
Log.i(this, "onBackPressed");
// BACK is also used to exit out of any "special modes" of the
// in-call UI:
if (!isVisible()) {
return;
}
if ((mConferenceManagerFragment == null || !mConferenceManagerFragment.isVisible())
&& (mCallCardFragment == null || !mCallCardFragment.isVisible())) {
return;
}
if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
mCallButtonFragment.displayDialpad(false /* show */, true /* animate */);
return;
} else if (mConferenceManagerFragment != null && mConferenceManagerFragment.isVisible()) {
showConferenceFragment(false);
return;
}
// Always disable the Back key while an incoming call is ringing
final Call call = CallList.getInstance().getIncomingCall();
if (call != null) {
Log.i(this, "Consume Back press for an incoming call");
return;
}
// Nothing special to do. Fall back to the default behavior.
super.onBackPressed();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final int itemId = item.getItemId();
if (itemId == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// push input to the dialer.
if (mDialpadFragment != null && (mDialpadFragment.isVisible()) &&
(mDialpadFragment.onDialerKeyUp(event))) {
return true;
} else if (keyCode == KeyEvent.KEYCODE_CALL) {
// Always consume CALL to be sure the PhoneWindow won't do anything with it
return true;
}
return super.onKeyUp(keyCode, event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mDispatchTouchEventListener != null) {
boolean handled = mDispatchTouchEventListener.onTouch(null, ev);
if (handled) {
return true;
}
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_CALL:
boolean handled = InCallPresenter.getInstance().handleCallKey();
if (!handled) {
Log.w(this, "InCallActivity should always handle KEYCODE_CALL in onKeyDown");
}
// Always consume CALL to be sure the PhoneWindow won't do anything with it
return true;
// Note there's no KeyEvent.KEYCODE_ENDCALL case here.
// The standard system-wide handling of the ENDCALL key
// (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
// already implements exactly what the UI spec wants,
// namely (1) "hang up" if there's a current active call,
// or (2) "don't answer" if there's a current ringing call.
case KeyEvent.KEYCODE_CAMERA:
// Disable the CAMERA button while in-call since it's too
// easy to press accidentally.
return true;
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE:
// Ringer silencing handled by PhoneWindowManager.
break;
case KeyEvent.KEYCODE_MUTE:
// toggle mute
TelecomAdapter.getInstance().mute(!AudioModeProvider.getInstance().getMute());
return true;
// Various testing/debugging features, enabled ONLY when VERBOSE == true.
case KeyEvent.KEYCODE_SLASH:
if (Log.VERBOSE) {
Log.v(this, "----------- InCallActivity View dump --------------");
// Dump starting from the top-level view of the entire activity:
Window w = this.getWindow();
View decorView = w.getDecorView();
Log.d(this, "View dump:" + decorView);
return true;
}
break;
case KeyEvent.KEYCODE_EQUALS:
// TODO: Dump phone state?
break;
}
if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
return true;
}
return super.onKeyDown(keyCode, event);
}
private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
Log.v(this, "handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "...");
// As soon as the user starts typing valid dialable keys on the
// keyboard (presumably to type DTMF tones) we start passing the
// key events to the DTMFDialer's onDialerKeyDown.
if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
return mDialpadFragment.onDialerKeyDown(event);
}
return false;
}
public CallButtonFragment getCallButtonFragment() {
return mCallButtonFragment;
}
public CallCardFragment getCallCardFragment() {
return mCallCardFragment;
}
public AnswerFragment getAnswerFragment() {
return mAnswerFragment;
}
private void internalResolveIntent(Intent intent) {
final String action = intent.getAction();
if (action.equals(Intent.ACTION_MAIN)) {
// This action is the normal way to bring up the in-call UI.
//
// But we do check here for one extra that can come along with the
// ACTION_MAIN intent:
if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
// 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.
final boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
Log.d(this, "- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
relaunchedFromDialer(showDialpad);
}
boolean newOutgoingCall = false;
if (intent.getBooleanExtra(NEW_OUTGOING_CALL_EXTRA, false)) {
intent.removeExtra(NEW_OUTGOING_CALL_EXTRA);
Call call = CallList.getInstance().getOutgoingCall();
if (call == null) {
call = CallList.getInstance().getPendingOutgoingCall();
}
Bundle extras = null;
if (call != null) {
extras = call.getTelecomCall().getDetails().getIntentExtras();
}
if (extras == null) {
// Initialize the extras bundle to avoid NPE
extras = new Bundle();
}
Point touchPoint = null;
if (TouchPointManager.getInstance().hasValidPoint()) {
// Use the most immediate touch point in the InCallUi if available
touchPoint = TouchPointManager.getInstance().getPoint();
} else {
// Otherwise retrieve the touch point from the call intent
if (call != null) {
touchPoint = (Point) extras.getParcelable(TouchPointManager.TOUCH_POINT);
}
}
// Start animation for new outgoing call
CircularRevealFragment.startCircularReveal(getFragmentManager(), touchPoint,
InCallPresenter.getInstance());
// 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(call)) {
TelecomAdapter.getInstance().disconnectCall(call.getId());
}
dismissKeyguard(true);
newOutgoingCall = true;
}
Call pendingAccountSelectionCall = CallList.getInstance().getWaitingForAccountCall();
if (pendingAccountSelectionCall != null) {
showCallCardFragment(false);
Bundle extras =
pendingAccountSelectionCall.getTelecomCall().getDetails().getIntentExtras();
final List<PhoneAccountHandle> phoneAccountHandles;
if (extras != null) {
phoneAccountHandles = extras.getParcelableArrayList(
android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
} else {
phoneAccountHandles = new ArrayList<>();
}
DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance(
R.string.select_phone_account_for_calls, true, phoneAccountHandles,
mSelectAcctListener);
dialogFragment.show(getFragmentManager(), TAG_SELECT_ACCT_FRAGMENT);
} else if (!newOutgoingCall) {
showCallCardFragment(true);
}
return;
}
}
/**
* 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) {
mShowDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
mAnimateDialpadOnShow = true;
if (mShowDialpadRequest == 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.
final Call call = CallList.getInstance().getActiveOrBackgroundCall();
if (call != null && call.getState() == State.ONHOLD) {
TelecomAdapter.getInstance().unholdCall(call.getId());
}
}
}
public void dismissKeyguard(boolean dismiss) {
if (mDismissKeyguard == dismiss) {
return;
}
mDismissKeyguard = dismiss;
if (dismiss) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
}
}
private void showFragment(String tag, boolean show, boolean executeImmediately) {
Trace.beginSection("showFragment - " + tag);
final FragmentManager fm = getFragmentManagerForTag(tag);
if (fm == null) {
Log.w(TAG, "Fragment manager is null for : " + tag);
return;
}
Fragment fragment = fm.findFragmentByTag(tag);
if (!show && fragment == null) {
// Nothing to show, so bail early.
return;
}
final FragmentTransaction transaction = fm.beginTransaction();
if (show) {
if (fragment == null) {
fragment = createNewFragmentForTag(tag);
transaction.add(getContainerIdForFragment(tag), fragment, tag);
} else {
transaction.show(fragment);
}
Logger.logScreenView(getScreenTypeForTag(tag), this);
} else {
transaction.hide(fragment);
}
transaction.commitAllowingStateLoss();
if (executeImmediately) {
fm.executePendingTransactions();
}
Trace.endSection();
}
private Fragment createNewFragmentForTag(String tag) {
if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
mDialpadFragment = new DialpadFragment();
return mDialpadFragment;
} else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
if (AccessibilityUtil.isTalkBackEnabled(this)) {
mAnswerFragment = new AccessibleAnswerFragment();
} else {
mAnswerFragment = new GlowPadAnswerFragment();
}
return mAnswerFragment;
} else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
mConferenceManagerFragment = new ConferenceManagerFragment();
return mConferenceManagerFragment;
} else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
mCallCardFragment = new CallCardFragment();
return mCallCardFragment;
}
throw new IllegalStateException("Unexpected fragment: " + tag);
}
private FragmentManager getFragmentManagerForTag(String tag) {
if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
return mChildFragmentManager;
} else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
return mChildFragmentManager;
} else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
return getFragmentManager();
} else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
return getFragmentManager();
}
throw new IllegalStateException("Unexpected fragment: " + tag);
}
private int getScreenTypeForTag(String tag) {
switch (tag) {
case TAG_DIALPAD_FRAGMENT:
return ScreenEvent.INCALL_DIALPAD;
case TAG_CALLCARD_FRAGMENT:
return ScreenEvent.INCALL;
case TAG_CONFERENCE_FRAGMENT:
return ScreenEvent.CONFERENCE_MANAGEMENT;
case TAG_ANSWER_FRAGMENT:
return ScreenEvent.INCOMING_CALL;
default:
return ScreenEvent.UNKNOWN;
}
}
private int getContainerIdForFragment(String tag) {
if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
return R.id.answer_and_dialpad_container;
} else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
return R.id.answer_and_dialpad_container;
} else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
return R.id.main;
} else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
return R.id.main;
}
throw new IllegalStateException("Unexpected fragment: " + tag);
}
/**
* @return {@code true} while the visibility of the dialpad has actually changed.
*/
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.
if ((show && isDialpadVisible()) || (!show && !isDialpadVisible())) {
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) {
showFragment(TAG_DIALPAD_FRAGMENT, show, true);
} else {
if (show) {
showFragment(TAG_DIALPAD_FRAGMENT, true, true);
mDialpadFragment.animateShowDialpad();
}
mDialpadFragment.getView().startAnimation(show ? mSlideIn : mSlideOut);
}
// Note: onDialpadVisibilityChange is called here to ensure that the dialpad FAB
// repositions itself.
mCallCardFragment.onDialpadVisibilityChange(show);
final ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
if (sensor != null) {
sensor.onDialpadVisible(show);
}
return true;
}
public boolean isDialpadVisible() {
return mDialpadFragment != null && mDialpadFragment.isVisible();
}
public void showCallCardFragment(boolean show) {
showFragment(TAG_CALLCARD_FRAGMENT, show, true);
}
/**
* Hides or shows the conference manager fragment.
*
* @param show {@code true} if the conference manager should be shown, {@code false} if it
* should be hidden.
*/
public void showConferenceFragment(boolean show) {
showFragment(TAG_CONFERENCE_FRAGMENT, show, true);
mConferenceManagerFragment.onVisibilityChanged(show);
// Need to hide the call card fragment to ensure that accessibility service does not try to
// give focus to the call card when the conference manager is visible.
mCallCardFragment.getView().setVisibility(show ? View.GONE : View.VISIBLE);
}
public void showAnswerFragment(boolean show) {
// CallCardFragment is the parent fragment of AnswerFragment.
// Must create the CallCardFragment first before creating
// AnswerFragment if CallCardFragment is null.
if (show && getCallCardFragment() == null) {
showCallCardFragment(true);
}
showFragment(TAG_ANSWER_FRAGMENT, show, true);
}
public void showPostCharWaitDialog(String callId, String chars) {
if (isVisible()) {
final PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
fragment.show(getFragmentManager(), "postCharWait");
mShowPostCharWaitDialogOnResume = false;
mShowPostCharWaitDialogCallId = null;
mShowPostCharWaitDialogChars = null;
} else {
mShowPostCharWaitDialogOnResume = true;
mShowPostCharWaitDialogCallId = callId;
mShowPostCharWaitDialogChars = chars;
}
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
if (mCallCardFragment != null) {
mCallCardFragment.dispatchPopulateAccessibilityEvent(event);
}
return super.dispatchPopulateAccessibilityEvent(event);
}
public void maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause) {
Log.d(this, "maybeShowErrorDialogOnDisconnect");
if (!isFinishing() && !TextUtils.isEmpty(disconnectCause.getDescription())
&& (disconnectCause.getCode() == DisconnectCause.ERROR ||
disconnectCause.getCode() == DisconnectCause.RESTRICTED)) {
showErrorDialog(disconnectCause.getDescription());
}
}
public void dismissPendingDialogs() {
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
if (mAnswerFragment != null) {
mAnswerFragment.dismissPendingDialogs();
}
SelectPhoneAccountDialogFragment dialogFragment = (SelectPhoneAccountDialogFragment)
getFragmentManager().findFragmentByTag(TAG_SELECT_ACCT_FRAGMENT);
if (dialogFragment != null) {
dialogFragment.dismiss();
}
}
/**
* Utility function to bring up a generic "error" dialog.
*/
private void showErrorDialog(CharSequence msg) {
Log.i(this, "Show Dialog: " + msg);
dismissPendingDialogs();
mDialog = new AlertDialog.Builder(this)
.setMessage(msg)
.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onDialogDismissed();
}
})
.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
onDialogDismissed();
}
})
.create();
mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
mDialog.show();
}
private void onDialogDismissed() {
mDialog = null;
CallList.getInstance().onErrorDialogDismissed();
InCallPresenter.getInstance().onDismissDialog();
}
public void setExcludeFromRecents(boolean exclude) {
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.AppTask> tasks = am.getAppTasks();
int taskId = getTaskId();
for (int i = 0; i < tasks.size(); i++) {
ActivityManager.AppTask task = tasks.get(i);
if (task.getTaskInfo().id == taskId) {
try {
task.setExcludeFromRecents(exclude);
} catch (RuntimeException e) {
Log.e(TAG, "RuntimeException when excluding task from recents.", e);
}
}
}
}
public OnTouchListener getDispatchTouchEventListener() {
return mDispatchTouchEventListener;
}
public void setDispatchTouchEventListener(OnTouchListener mDispatchTouchEventListener) {
this.mDispatchTouchEventListener = mDispatchTouchEventListener;
}
/**
* Enables the OrientationEventListener if enable flag is true. Disables it if enable is
* false
* @param enable true or false.
*/
public void enableInCallOrientationEventListener(boolean enable) {
if (enable) {
mInCallOrientationEventListener.enable(enable);
} else {
mInCallOrientationEventListener.disable();
}
}
}