blob: 64e856c9350316277cec53dfddbafe2a95c59887 [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 com.android.services.telephony.common.Call;
import com.android.services.telephony.common.Call.State;
import android.app.Activity;
import android.app.AlertDialog;
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.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Toast;
/**
* Phone app "in call" screen.
*/
public class InCallActivity extends Activity {
public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad";
private static final int INVALID_RES_ID = -1;
private CallButtonFragment mCallButtonFragment;
private CallCardFragment mCallCardFragment;
private AnswerFragment mAnswerFragment;
private DialpadFragment mDialpadFragment;
private ConferenceManagerFragment mConferenceManagerFragment;
private boolean mIsForegroundActivity;
private AlertDialog mDialog;
/** Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume} */
private boolean mShowDialpadRequested;
@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".
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
| WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// TODO(klp): Do we need to add this back when prox sensor is not available?
// lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
// Inflate everything in incall_screen.xml and add it to the screen.
setContentView(R.layout.incall_screen);
initializeInCall();
Log.d(this, "onCreate(): exit");
}
@Override
protected void onStart() {
Log.d(this, "onStart()...");
super.onStart();
// setting activity should be last thing in setup process
InCallPresenter.getInstance().setActivity(this);
}
@Override
protected void onResume() {
Log.i(this, "onResume()...");
super.onResume();
mIsForegroundActivity = true;
InCallPresenter.getInstance().onUiShowing(true);
if (mShowDialpadRequested) {
mCallButtonFragment.displayDialpad(true);
mShowDialpadRequested = false;
}
}
// onPause is guaranteed to be called when the InCallActivity goes
// in the background.
@Override
protected void onPause() {
Log.d(this, "onPause()...");
super.onPause();
mIsForegroundActivity = false;
mDialpadFragment.onDialerKeyUp(null);
InCallPresenter.getInstance().onUiShowing(false);
}
@Override
protected void onStop() {
Log.d(this, "onStop()...");
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(this, "onDestroy()... this = " + this);
InCallPresenter.getInstance().setActivity(null);
super.onDestroy();
}
/**
* Returns true when theActivity is in foreground (between onResume and onPause).
*/
/* package */ boolean isForegroundActivity() {
return mIsForegroundActivity;
}
private boolean hasPendingErrorDialog() {
return mDialog != null;
}
/**
* Dismisses the in-call screen.
*
* We never *really* finish() the InCallActivity, since we don't want to get destroyed and then
* have to be re-created from scratch for the next call. Instead, we just move ourselves to the
* back of the activity stack.
*
* This also means that we'll no longer be reachable via the BACK button (since moveTaskToBack()
* puts us behind the Home app, but the home app doesn't allow the BACK key to move you any
* farther down in the history stack.)
*
* (Since the Phone app itself is never killed, this basically means that we'll keep a single
* InCallActivity instance around for the entire uptime of the device. This noticeably improves
* the UI responsiveness for incoming calls.)
*/
@Override
public void finish() {
Log.i(this, "finish(). Dialog showing: " + (mDialog != null));
// skip finish if we are still showing a dialog.
if (!hasPendingErrorDialog() && !mAnswerFragment.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.d(this, "onBackPressed()...");
// BACK is also used to exit out of any "special modes" of the
// in-call UI:
if (mDialpadFragment.isVisible()) {
mCallButtonFragment.displayDialpad(false); // do the "closing" animation
return;
} else if (mConferenceManagerFragment.isVisible()) {
mConferenceManagerFragment.setVisible(false);
return;
}
// Always disable the Back key while an incoming call is ringing
final Call call = CallList.getInstance().getIncomingCall();
if (call != null) {
Log.d(this, "Consume Back press for an inconing call");
return;
}
// Nothing special to do. Fall back to the default behavior.
super.onBackPressed();
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// push input to the dialer.
if ((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
CallCommandClient.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();
decorView.debug();
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.isVisible()) {
return mDialpadFragment.onDialerKeyDown(event);
// TODO: If the dialpad isn't currently visible, maybe
// consider automatically bringing it up right now?
// (Just to make sure the user sees the digits widget...)
// But this probably isn't too critical since it's awkward to
// use the hard keyboard while in-call in the first place,
// especially now that the in-call UI is portrait-only...
}
return false;
}
@Override
public void onConfigurationChanged(Configuration config) {
InCallPresenter.getInstance().getProximitySensor().onConfigurationChanged(config);
}
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);
}
return;
}
}
private void relaunchedFromDialer(boolean showDialpad) {
mShowDialpadRequested = showDialpad;
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) {
CallCommandClient.getInstance().hold(call.getCallId(), false);
}
}
}
private void initializeInCall() {
if (mCallButtonFragment == null) {
mCallButtonFragment = (CallButtonFragment) getFragmentManager()
.findFragmentById(R.id.callButtonFragment);
mCallButtonFragment.getView().setVisibility(View.INVISIBLE);
}
if (mCallCardFragment == null) {
mCallCardFragment = (CallCardFragment) getFragmentManager()
.findFragmentById(R.id.callCardFragment);
}
if (mAnswerFragment == null) {
mAnswerFragment = (AnswerFragment) getFragmentManager()
.findFragmentById(R.id.answerFragment);
}
if (mDialpadFragment == null) {
mDialpadFragment = (DialpadFragment) getFragmentManager()
.findFragmentById(R.id.dialpadFragment);
mDialpadFragment.getView().setVisibility(View.INVISIBLE);
}
if (mConferenceManagerFragment == null) {
mConferenceManagerFragment = (ConferenceManagerFragment) getFragmentManager()
.findFragmentById(R.id.conferenceManagerFragment);
mConferenceManagerFragment.getView().setVisibility(View.INVISIBLE);
}
}
private void toast(String text) {
final Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
toast.show();
}
/**
* Simulates a user click to hide the dialpad. This will update the UI to show the call card,
* update the checked state of the dialpad button, and update the proximity sensor state.
*/
public void hideDialpadForDisconnect() {
mCallButtonFragment.displayDialpad(false);
}
public void displayDialpad(boolean showDialpad) {
if (showDialpad) {
mDialpadFragment.setVisible(true);
mCallCardFragment.setVisible(false);
} else {
mDialpadFragment.setVisible(false);
mCallCardFragment.setVisible(true);
}
InCallPresenter.getInstance().getProximitySensor().onDialpadVisible(showDialpad);
}
public boolean isDialpadVisible() {
return mDialpadFragment.isVisible();
}
public void displayManageConferencePanel(boolean showPanel) {
if (showPanel) {
mConferenceManagerFragment.setVisible(true);
}
}
public void showPostCharWaitDialog(int callId, String chars) {
final PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
fragment.show(getFragmentManager(), "postCharWait");
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
if (mCallCardFragment != null) {
mCallCardFragment.dispatchPopulateAccessibilityEvent(event);
}
return super.dispatchPopulateAccessibilityEvent(event);
}
public void maybeShowErrorDialogOnDisconnect(Call.DisconnectCause cause) {
Log.d(this, "maybeShowErrorDialogOnDisconnect");
if (!isFinishing()) {
final int resId = getResIdForDisconnectCause(cause);
if (resId != INVALID_RES_ID) {
showErrorDialog(resId);
}
}
}
public void dismissPendingDialogs() {
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
mAnswerFragment.dismissPendingDialogues();
}
/**
* Utility function to bring up a generic "error" dialog.
*/
private void showErrorDialog(int resId) {
final CharSequence msg = getResources().getText(resId);
Log.i(this, "Show Dialog: " + msg);
dismissPendingDialogs();
mDialog = new AlertDialog.Builder(this)
.setMessage(msg)
.setPositiveButton(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 int getResIdForDisconnectCause(Call.DisconnectCause cause) {
int resId = INVALID_RES_ID;
if (cause == Call.DisconnectCause.CALL_BARRED) {
resId = R.string.callFailed_cb_enabled;
} else if (cause == Call.DisconnectCause.FDN_BLOCKED) {
resId = R.string.callFailed_fdn_only;
} else if (cause == Call.DisconnectCause.CS_RESTRICTED) {
resId = R.string.callFailed_dsac_restricted;
} else if (cause == Call.DisconnectCause.CS_RESTRICTED_EMERGENCY) {
resId = R.string.callFailed_dsac_restricted_emergency;
} else if (cause == Call.DisconnectCause.CS_RESTRICTED_NORMAL) {
resId = R.string.callFailed_dsac_restricted_normal;
}
return resId;
}
private void onDialogDismissed() {
mDialog = null;
InCallPresenter.getInstance().onDismissDialog();
}
}