blob: c6892b21a16130d883f8fe816afe7b954610f523 [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.OnClickListener;
import android.content.DialogInterface.OnCancelListener;
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.Display;
import android.view.MenuItem;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import com.android.phone.common.animation.AnimUtils;
import com.android.phone.common.animation.AnimationListenerAdapter;
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.incallui.Call.State;
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 Activity 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";
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 CallButtonFragment mCallButtonFragment;
private CallCardFragment mCallCardFragment;
private AnswerFragment mAnswerFragment;
private DialpadFragment mDialpadFragment;
private ConferenceManagerFragment mConferenceManagerFragment;
private FragmentManager mChildFragmentManager;
private boolean mIsVisible;
private AlertDialog mDialog;
/** Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume} */
private boolean mShowDialpadRequested;
/** 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 SelectPhoneAccountListener mSelectAcctListener = new SelectPhoneAccountListener() {
@Override
public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle,
boolean setDefault) {
InCallPresenter.getInstance().handleAccountSelection(selectedAccountHandle,
setDefault);
}
@Override
public void onDialogDismissed() {
InCallPresenter.getInstance().cancelAccountSelection();
}
};
/** Listener for orientation changes. */
private OrientationEventListener mOrientationEventListener;
/**
* Used to determine if a change in rotation has occurred.
*/
private static int sPreviousRotation = -1;
@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 (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.
mShowDialpadRequested = icicle.getBoolean(SHOW_DIALPAD_EXTRA);
mAnimateDialpadOnShow = false;
mDtmfText = icicle.getString(DIALPAD_TEXT_EXTRA);
SelectPhoneAccountDialogFragment dialogFragment = (SelectPhoneAccountDialogFragment)
getFragmentManager().findFragmentByTag(TAG_SELECT_ACCT_FRAGMENT);
if (dialogFragment != null) {
dialogFragment.setListener(mSelectAcctListener);
}
}
mOrientationEventListener = new OrientationEventListener(this,
SensorManager.SENSOR_DELAY_NORMAL) {
@Override
public void onOrientationChanged(int orientation) {
// Device is flat, don't change orientation.
if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
return;
}
int newRotation = Surface.ROTATION_0;
// We only shift if we're within 22.5 (23) degrees of the target
// orientation. This avoids flopping back and forth when holding
// the device at 45 degrees or so.
if (orientation >= 337 || orientation <= 23) {
newRotation = Surface.ROTATION_0;
} else if (orientation >= 67 && orientation <= 113) {
// Why not 90? Because screen and sensor orientation are
// reversed.
newRotation = Surface.ROTATION_270;
} else if (orientation >= 157 && orientation <= 203) {
newRotation = Surface.ROTATION_180;
} else if (orientation >= 247 && orientation <= 293) {
newRotation = Surface.ROTATION_90;
}
// Orientation is the current device orientation in degrees. Ultimately we want
// the rotation (in fixed 90 degree intervals).
if (newRotation != sPreviousRotation) {
doOrientationChanged(newRotation);
}
}
};
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();
mIsVisible = true;
if (mOrientationEventListener.canDetectOrientation()) {
Log.v(this, "Orientation detection enabled.");
mOrientationEventListener.enable();
} else {
Log.v(this, "Orientation detection disabled.");
mOrientationEventListener.disable();
}
// setting activity should be last thing in setup process
InCallPresenter.getInstance().setActivity(this);
InCallPresenter.getInstance().onActivityStarted();
}
@Override
protected void onResume() {
Log.i(this, "onResume()...");
super.onResume();
InCallPresenter.getInstance().setThemeColors();
InCallPresenter.getInstance().onUiShowing(true);
if (mShowDialpadRequested) {
mCallButtonFragment.displayDialpad(true /* show */,
mAnimateDialpadOnShow /* animate */);
mShowDialpadRequested = false;
mAnimateDialpadOnShow = false;
if (mDialpadFragment != null) {
mDialpadFragment.setDtmfText(mDtmfText);
mDtmfText = null;
}
}
if (mShowPostCharWaitDialogOnResume) {
showPostCharWaitDialog(mShowPostCharWaitDialogCallId, mShowPostCharWaitDialogChars);
}
}
// 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()...");
mIsVisible = false;
InCallPresenter.getInstance().updateIsChangingConfigurations();
InCallPresenter.getInstance().onActivityStopped();
mOrientationEventListener.disable();
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;
}
}
/**
* Returns true when the Activity is currently visible (between onStart and onStop).
*/
/* package */ boolean isVisible() {
return mIsVisible;
}
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 ((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 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;
}
/**
* Handles changes in device rotation.
*
* @param rotation The new device rotation (one of: {@link Surface#ROTATION_0},
* {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180},
* {@link Surface#ROTATION_270}).
*/
private void doOrientationChanged(int rotation) {
Log.d(this, "doOrientationChanged prevOrientation=" + sPreviousRotation +
" newOrientation=" + rotation);
// Check to see if the rotation changed to prevent triggering rotation change events
// for other configuration changes.
if (rotation != sPreviousRotation) {
sPreviousRotation = rotation;
InCallPresenter.getInstance().onDeviceRotationChange(rotation);
InCallPresenter.getInstance().onDeviceOrientationChange(sPreviousRotation);
}
}
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.getTelecommCall().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 (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
.getTelecommCall().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;
}
}
private void relaunchedFromDialer(boolean showDialpad) {
mShowDialpadRequested = showDialpad;
mAnimateDialpadOnShow = true;
if (mShowDialpadRequested) {
// 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);
}
} 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)) {
mAnswerFragment = new AnswerFragment();
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 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);
}
public void 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;
}
// 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();
}
mCallCardFragment.onDialpadVisibilityChange(show);
mDialpadFragment.getView().startAnimation(show ? mSlideIn : mSlideOut);
}
final ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
if (sensor != null) {
sensor.onDialpadVisible(show);
}
}
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) {
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();
}
}
/**
* 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);
}
}
}
}
}