package com.android.phone;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Application;
import android.app.Dialog;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnKeyListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.media.AudioManager;
import android.media.MediaPhone;
import android.net.Uri;
import android.os.AsyncResult;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IPowerManager;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StatFs;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.pim.ContactsAsyncHelper;
import android.preference.PreferenceManager;
import android.provider.ContactsContract.Contacts;
import android.telephony.ServiceState;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.view.IWindowManager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.util.EventLog;
import android.util.Log;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SlidingDrawer;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallManager;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.CallerInfoAsyncQuery;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.PhoneProxy;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.gsm.TDPhone;
import java.io.IOException;
import static com.android.internal.telephony.MsmsConstants.SUBSCRIPTION_KEY;
public class InVideoCallScreen extends Activity
implements View.OnClickListener, CallTime.OnTickListener, CallerInfoAsyncQuery.OnQueryCompleteListener,
MediaPhone.OnErrorListener,
MediaPhone.OnMediaInfoListener,
MediaPhone.OnCallEventListener,
MediaPhone.OnVideoSizeChangedListener,
AdapterView.OnItemClickListener,
AdjustMenuView.OnSeekBarChangeListener,
SurfaceHolder.Callback{
private static final String LOG_TAG = "InVideoCallScreen";
private static final boolean DBG = true;
//(PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
private static final boolean VDBG = true;//(PhoneApp.DBG_LEVEL >= 2);
static final String SHOW_DIALPAD_EXTRA = "com.android.phone.ShowDialpad";
private static final boolean ENABLE_MEDIAPHONE = true;
private static final boolean ENABLE_DIALPANEL_HANDLER = true;
// High-level "modes" of the in-call UI.
private enum InCallScreenMode {
/**
* Normal in-call UI elements visible.
*/
NORMAL,
/**
* Non-interactive UI state. Call card is visible,
* displaying information about the call that just ended.
*/
CALL_ENDED,
FALL_BACK,
/**
* Default state when not on call
*/
UNDEFINED
}
private InCallScreenMode mInCallScreenMode = InCallScreenMode.UNDEFINED;
// Possible error conditions that can happen on startup.
// These are returned as status codes from the various helper
// functions we call from onCreate() and/or onResume().
// See syncWithPhoneState() and checkIfOkToInitiateOutgoingCall() for details.
private enum InCallInitStatus {
SUCCESS,
SURFACE_NOT_READY,
VOICEMAIL_NUMBER_MISSING,
POWER_OFF,
EMERGENCY_ONLY,
OUT_OF_SERVICE,
PHONE_NOT_IN_USE,
NO_PHONE_NUMBER_SUPPLIED,
NO_IN_3G,
CALL_FAILED,
ALREADY_IN_3GCALL,
ALREADY_IN_2GCALL,
CALL_FAILED_FDN_ONLY,
AIRPLANE_MODE_ON,
}
private InCallInitStatus mInCallInitialStatus; // see onResume()
private boolean mRegisteredForPhoneStates;
private boolean mNeedShowCallLostDialog;
private Phone mPhone;
// Phone app instance
private PhoneApp mApplication;
private CallManager mCM;
private Call mForegroundCall;
private Call mBackgroundCall;
private Call mRingingCall;
private BluetoothHandsfree mBluetoothHandsfree;
private BluetoothHeadset mBluetoothHeadset;
private boolean mBluetoothConnectionPending;
private long mBluetoothConnectionRequestTime;
// Text colors, used for various labels / titles
private int mTextColorDefaultPrimary;
private int mTextColorDefaultSecondary;
private int mTextColorConnected;
private int mTextColorConnectedBluetooth;
private int mTextColorEnded;
private int mTextColorOnHold;
// Surface view for video phone
private SurfaceView mRemoteVideoPhoneView;
private SurfaceView mLocalVideoPhoneView;
private SurfaceView mLargeVideoPhoneView;
private SurfaceView mSmallVideoPhoneView;
private int mSurfaceReadyCount = 0;
PowerManager.WakeLock mWakeLock;
Call.State mLastFGCallState = Call.State.IDLE;
// Parmeter for videocall
private enum ViewType {
LIVE,
PICTURE,
VIDEO,
NONE
}
private ViewType mLocalViewType = ViewType.LIVE;
private ViewType mRemoteViewType = ViewType.LIVE;
private String mLocalPicUri = null;
private String mRemotePicUri = null;
private String mLocalVideoUri = null;
private String mRemoteVideoUri = null;
// Caller Info
private ViewGroup mCallInfo;
private VideoCallTime mCallTime;
private TextView mName;
private TextView mPhoneNumber;
private View mInCallPanel;
private View mInComingCallPanel;
private Button mDialerBtn;
private Button mHangupBtn;
private Button mAnswerBtn;
private Button mDeclineBtn;
// option menu items
private Menu mOptionMenu;
private MenuItem mCameraSwitch_item;
private MenuItem mViewSwitch_item;
private MenuItem mCamera_item;
private MenuItem mSpeaker_item;
private MenuItem mBluetooth_item;
private MenuItem mMute_item;
private MenuItem mCamera_brightness_item;
private MenuItem mCamera_contrast_item;
private MenuItem mStartrecord_item;
private MenuItem mStoprecord_item;
private boolean mbExternalStorageAvail = false;
// Title and elapsed-time widgets
private TextView mUpperTitle;
private TextView mElapsedTime;
private TextView mLabel;
private TextView mBeginTime;
// Track the state for the photo.
private ContactsAsyncHelper.ImageTracker mPhotoTracker;
// AdjustMenu
AdjustMenuView mBrightnessAdjustMenu;
AdjustMenuView mCameraBrightnessAdjustMenu;
AdjustMenuView mCameraContrastAdjustMenu;
private static final int MINIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_DIM + 10;
private static final int MAXIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_ON;
private static final int MINIMUM_CAMERA_BACKLIGHT = 0;
private static final int MAXIMUM_CAMERA_BACKLIGHT = 6;
private static final int MINIMUM_CAMERA_CONTRAST = 0;
private static final int MAXIMUM_CAMERA_CONTRAST = 6;
private int mMyBrightness;
private int mMyCameraBrightness;
private int mMyCameraContrast;
private int mSysBrightness;
private int mSysCameraBrightness;
private int mSysCameraContrast;
// Cached DisplayMetrics density.
private float mDensity;
private static final String DEFAULT_COMM = "videophone:///dev/ts0710mux12";
private static final String DEFAULT_FALLBACK = "DEFAULT_FALLBACK";
MediaPhone mp;
HandlerThread mMediaPhoneThread;
Thread mStartPreviewThread;
private enum ScreenLayoutConfig {
LOCAL_SMALL_REMOTE_LARGE,
LOCAL_LARGE_REMOTE_SMALL
}
private ScreenLayoutConfig mScreenLayoutConfig = ScreenLayoutConfig.LOCAL_SMALL_REMOTE_LARGE;
private enum FallBackConfig {
FALLBACK_ASK,
FALLBACK_ALWAYS,
FALLBACK_NEVER
}
private FallBackConfig mFallBackConfig = FallBackConfig.FALLBACK_ASK;
private enum CameraType {
BOTTOM_CAMERA,
FRONT_CAMERA,
FAKE_CAMERA,
}
private CameraType mCameraType = CameraType.FRONT_CAMERA;
private CameraType mRealCameraType = CameraType.FRONT_CAMERA;
private boolean mbEnableCamera = false;
private boolean mbShowSubstitutePic = true;
private boolean mbEnableRecord = false;
private boolean mbInitialCamera = false;
private boolean mAllowVolumeButtion = false;
private String mLocalSubstitutePic = null;
private String mRemoteSubstitutePic = null;
private BroadcastReceiver mStorageReceiver;
private static final String DEFAULT_STORE_SUBDIR = "/videocall";
private static final String DEFAULT_RECORD_SUFFIX = ".3gp";
private static final long MINIMUM_START_FREE_SIZE = 2 * 1024 * 1024;
private static final long MINIMUM_FREE_SIZE = 1024 * 1024;
private enum RemoteCameraClose_ActionConfig {
REMOTECAMERACLOSE_DONOTHING,
REMOTECAMERACLOSE_SUBTITUTE,
}
private RemoteCameraClose_ActionConfig mRemoteCameraClose_ActionConfig = RemoteCameraClose_ActionConfig.REMOTECAMERACLOSE_SUBTITUTE;
private class CallData {
String number;
Uri contactUri;
public CallData(String num, Uri uri){
number = num;
contactUri = uri;
}
}
private CallData mCallData;
// Dialog
// private AlertDialog mIncomingDialog;
// "Touch lock overlay" feature
private boolean mUseTouchLockOverlay; // True if we use this feature on the current device
private View mTouchLockOverlay; // The overlay over the whole screen
private View mTouchLockIcon; // The "lock" icon in the middle of the screen
private long mTouchLockLastTouchTime; // in SystemClock.uptimeMillis() time base
// DTMF Dialer controller and its view:
private VideoPhoneTwelveKeyDialer mDialer;
private VideoPhoneTwelveKeyDialerView mDialerView;
// Various dialogs we bring up (see dismissAllDialogs()).
// TODO: convert these all to use the "managed dialogs" framework.
//
// The MMI started dialog can actually be one of 2 items:
// 1. An alert dialog if the MMI code is a normal MMI
// 2. A progress dialog if the user requested a USSD
private AlertDialog mGenericErrorDialog;
private AlertDialog mWaitPromptDialog;
private AlertDialog mWildPromptDialog;
private AlertDialog mCallLostDialog;
private AlertDialog mPausePromptDialog;
private Dialog mFallBackDialog;
// NOTE: if you add a new dialog here, be sure to add it to dismissAllDialogs() also.
// TODO: If the Activity class ever provides an easy way to get the
// current "activity lifecycle" state, we can remove these flags.
private boolean mIsDestroyed = false;
private boolean mIsForegroundActivity = false;
private boolean mHasFocus = false;
// Amount of time (in msec) that we display the "Call ended" state.
// The "short" value is for calls ended by the local user, and the
// "long" value is for calls ended by the remote caller.
private static final int CALL_ENDED_SHORT_DELAY = 1000; // msec
private static final int CALL_ENDED_LONG_DELAY = 3000; // msec
// Message codes; see mHandler below.
// Note message codes < 100 are reserved for the PhoneApp.
private static final int PHONE_STATE_CHANGED = 101;
private static final int PHONE_DISCONNECT = 102;
private static final int EVENT_HEADSET_PLUG_STATE_CHANGED = 103;
private static final int POST_ON_DIAL_CHARS = 104;
private static final int WILD_PROMPT_CHAR_ENTERED = 105;
private static final int ADD_VOICEMAIL_NUMBER = 106;
private static final int DONT_ADD_VOICEMAIL_NUMBER = 107;
private static final int DELAYED_CLEANUP_AFTER_DISCONNECT = 108;
private static final int SUPP_SERVICE_FAILED = 110;
private static final int DISMISS_MENU = 111;
private static final int ALLOW_SCREEN_ON = 112;
private static final int TOUCH_LOCK_TIMER = 113;
private static final int REQUEST_UPDATE_BLUETOOTH_INDICATION = 114;
private static final int PHONE_CDMA_CALL_WAITING = 115;
private static final int THREEWAY_CALLERINFO_DISPLAY_DONE = 116;
private static final int EVENT_OTA_PROVISION_CHANGE = 117;
private static final int REQUEST_CLOSE_SPC_ERROR_NOTICE = 118;
private static final int REQUEST_CLOSE_OTA_FAILURE_NOTICE = 119;
private static final int EVENT_PAUSE_DIALOG_COMPLETE = 120;
private static final int EVENT_HIDE_PROVIDER_OVERLAY = 121; // Time to remove the overlay.
private static final int REQUEST_UPDATE_TOUCH_UI = 122;
private static final int DELAYED_SET_SURFACE = 123;
private static final int DELAYED_END_WAITCC = 124;
private static final int DELAYED_SWITCH_SPEAKER = 125;
private static final int MEDIAPHONE_CAMERA_OPEN = 126;
private static final int MEDIAPHONE_CAMERA_CLOSE = 127;
private static final int MEDIAPHONE_STRING = 128;
private static final int MEDIAPHONE_CODEC_START = 129;
private static final int MEDIAPHONE_CODEC_CLOSE = 130;
private static final int MEDIAPHONE_CAMERA_FAIL = 131;
private static final int MEDIAPHONE_MEDIA_START = 132;
private static final int DELAY_UPDATEVIEWCONFIG = 133;
private static final int REQUEST_UPDATE_MUTE_INDICATION = 134;
private static final int DELAYED_CREATE_CAMERA = 135;
private static final int INITIALED_CAMERA = 136;
private static final int CHECK_FREESPACE = 137;
// arg1 for DELAYED_SET_SURFACE
private static final int ARG_PLACE_CALL = 1;
private static final int ARG_RESUME = 2;
// view size
private static final int SMALL_VIEW_SIZE = 80;
private static final int LARGE_VIEW_SIZE = 160;
private android.hardware.Camera mCameraDevice;
private boolean mStartPreviewFail = false;
boolean mPreviewing = false; // True if preview is started.
private Parameters mParameters;
private ListView mFallBackList;
private Dialog mActiveDlg = null;
private String mRecordFile = null;
private Camera mCamera = null;
private Object mCameraLock = new Object();
private SurfaceHolder m_sHolder = null;
private boolean mWaitCC = false;
private boolean mWaitCD = false;
private long mCCTime = 0;
private long mMMRingDuring = 0;
private Call.State mOldCallState = Call.State.IDLE;
private static final int WAIT_CC_DELAY = 4000;
private enum LastAction {
ACTION_NONE,
ACTION_VIDEOCALL,
ACTION_VOICECALL
}
private LastAction mLastAction = LastAction.ACTION_NONE;
private IWindowManager mWindowManager;
private Button mButtonIncrease;
private Button mButtonDecline;
// whether in dialing state. If not, don't display disconnect cause
// it only be true in incoming call state
private boolean mbIncoming = true;
private boolean mbAnswered = false;
private boolean mbEverConnected = false;
private boolean mbHangupByUser = false;
private boolean mbMediaStarted = false;
// save initial intent to make call
private Intent mCallIntent = null;
// Flag indicating whether or not we should bring up the Call Log when
// exiting the in-call UI due to the Phone becoming idle. (This is
// true if the most recently disconnected Call was initiated by the
// user, or false if it was an incoming call.)
// This flag is used by delayedCleanupAfterDisconnect(), and is set by
// onDisconnect() (which is the only place that either posts a
// DELAYED_CLEANUP_AFTER_DISCONNECT event *or* calls
// delayedCleanupAfterDisconnect() directly.)
private boolean mShowCallLogAfterDisconnect;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (mIsDestroyed) {
if (DBG) log("Handler: ignoring message " + msg + "; we're destroyed!");
return;
}
if (!mIsForegroundActivity) {
if (DBG) log("Handler: handling message " + msg + " while not in foreground");
// Continue anyway; some of the messages below *want* to
// be handled even if we're not the foreground activity
// (like DELAYED_CLEANUP_AFTER_DISCONNECT), and they all
// should at least be safe to handle if we're not in the
// foreground...
}
PhoneApp app = PhoneApp.getInstance();
switch (msg.what) {
case PHONE_STATE_CHANGED:
onPhoneStateChanged((AsyncResult) msg.obj);
break;
case PHONE_DISCONNECT:
onDisconnect((AsyncResult) msg.obj);
break;
case EVENT_HEADSET_PLUG_STATE_CHANGED:
case REQUEST_UPDATE_MUTE_INDICATION:
updateAudioMenu();
break;
case DELAYED_CLEANUP_AFTER_DISCONNECT:
delayedCleanupAfterDisconnect();
break;
case ALLOW_SCREEN_ON:
if (VDBG) log("ALLOW_SCREEN_ON message...");
// Undo our previous call to preventScreenOn(true).
// (Note this will cause the screen to turn on
// immediately, if it's currently off because of a
// prior preventScreenOn(true) call.)
app.preventScreenOn(false);
break;
case REQUEST_UPDATE_BLUETOOTH_INDICATION:
if (VDBG) log("REQUEST_UPDATE_BLUETOOTH_INDICATION...");
// The bluetooth headset state changed, so some UI
// elements may need to update. (There's no need to
// look up the current state here, since any UI
// elements that care about the bluetooth state get it
// directly from PhoneApp.showBluetoothIndication().)
updateScreen();
break;
case DELAYED_END_WAITCC:
Log.w(LOG_TAG, "handleMessage(), DELAYED_END_WAITCC, mWaitCC: " + mWaitCC);
if (mWaitCC){
mWaitCC = false;
updateMainCallStatus();
}
break;
case DELAYED_SWITCH_SPEAKER:
Log.w(LOG_TAG, "handleMessage(), DELAYED_SWITCH_SPEAKER" );
if (Call.State.ACTIVE == mForegroundCall.getState()) {
if (isAudioInCall()) {
switchToSpeaker();
} else {
this.sendEmptyMessageDelayed(DELAYED_SWITCH_SPEAKER, 100);
}
}
break;
case MEDIAPHONE_CAMERA_OPEN:{
Toast txtToast = Toast.makeText(getWindow().getContext(), getString(R.string.prompt_remotecamera_open), Toast.LENGTH_LONG);
txtToast.show();
if (ViewType.LIVE != mRemoteViewType){
mRemoteViewType = ViewType.LIVE;
//updateViewConfig();
removeMessages(DELAY_UPDATEVIEWCONFIG);
sendEmptyMessageDelayed(DELAY_UPDATEVIEWCONFIG, 1000);
}
}
break;
case MEDIAPHONE_CAMERA_CLOSE:{
Toast txtToast = Toast.makeText(getWindow().getContext(), getString(R.string.prompt_remotecamera_close), Toast.LENGTH_LONG);
txtToast.show();
if (mbShowSubstitutePic && (ViewType.PICTURE != mRemoteViewType)){
mRemoteViewType = ViewType.PICTURE;
updateViewConfig();
}
}
break;
case MEDIAPHONE_STRING:{
String strRemote = (String)msg.obj;
log("MEDIAPHONE_STRING, strRemote: " + strRemote + ", mWaitCC: " + mWaitCC + ", mWaitCD: " + mWaitCD);
if (strRemote.equals("CC")){
if (mWaitCC){
mWaitCC = false;
mWaitCD = true;
mHandler.removeMessages(DELAYED_END_WAITCC);
//mCCTime = SystemClock.elapsedRealtime();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
mBeginTime.setText(sdf.format(new Date()));
log("MEDIA_CALLEVENT_STRING, mBeginTime: " + mBeginTime.getText());
} else {
log("receive 'CC' unexpected");
}
} else if (strRemote.equals("CD")){
if (mWaitCD){
mWaitCC = false;
mWaitCD = false;
mMMRingDuring = SystemClock.elapsedRealtime() - mCCTime;
updateMainCallStatus();
} else {
log("receive 'CD' unexpected");
}
}
}
break;
case MEDIAPHONE_CODEC_START:
updateViewConfig();
updateOptionMenu();
break;
case MEDIAPHONE_CODEC_CLOSE:
updateViewConfig();
updateOptionMenu();
if (mbEnableRecord && (mRecordFile != null)) {
Toast.makeText(getWindow().getContext(), getString(R.string.prompt_record_finish) + mRecordFile, Toast.LENGTH_LONG).show();
mbEnableRecord = false;
}
break;
case MEDIAPHONE_CAMERA_FAIL:
Toast.makeText(getWindow().getContext(), getString(R.string.prompt_camera_fail), Toast.LENGTH_LONG).show();
break;
case MEDIAPHONE_MEDIA_START:
mElapsedTime.setVisibility(View.VISIBLE);
long duration = VideoCallTime.getCallDuration(null); // msec
updateElapsedTimeWidget(duration / 1000);
break;
case DELAY_UPDATEVIEWCONFIG:
if (isForegroundActivity()) {
updateViewConfig();
}
break;
case DELAYED_CREATE_CAMERA:
startCameraPreview();
break;
case INITIALED_CAMERA:
//updateInCallPanel();
updateOptionMenu();
break;
case CHECK_FREESPACE:
if (!isSpaceEnough()) {
Toast.makeText(getWindow().getContext(), getString(R.string.prompt_no_freespace), Toast.LENGTH_LONG).show();
enableRecord(false, 0);
} else {
this.sendEmptyMessageDelayed(CHECK_FREESPACE, 10 * 1000);
}
break;
}
}
};
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
// Listen for ACTION_HEADSET_PLUG broadcasts so that we
// can update the onscreen UI when the headset state changes.
// if (DBG) log("mReceiver: ACTION_HEADSET_PLUG");
// if (DBG) log("==> intent: " + intent);
// if (DBG) log(" state: " + intent.getIntExtra("state", 0));
// if (DBG) log(" name: " + intent.getStringExtra("name"));
// send the event and add the state as an argument.
Message message = Message.obtain(mHandler, EVENT_HEADSET_PLUG_STATE_CHANGED,
intent.getIntExtra("state", 0), 0);
mHandler.sendMessage(message);
}
}
};
private void initVideoPoneView(SurfaceView view){
view.getHolder().addCallback(this);
view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
view.setFocusable(true);
view.setFocusableInTouchMode(true);
view.requestFocus();
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
Log.d(LOG_TAG, "surfaceChanged");
}
public void surfaceCreated(SurfaceHolder arg0) {
if (mSurfaceReadyCount < 2) mSurfaceReadyCount++;
Log.d(LOG_TAG, "surfaceCreated, mSurfaceReadyCount: " + mSurfaceReadyCount);
if (mSurfaceReadyCount == 2) {
mp.setLocalDisplay(mLocalVideoPhoneView.getHolder().getSurface());
mp.setRemoteDisplay(mRemoteVideoPhoneView.getHolder().getSurface());
if (mp.getCodecState() == MediaPhone.CodecState.CODEC_START) {
mp.startDownLink();
} else {
if (mCamera != null) {
synchronized(mCameraLock) {
if (mCamera != null) {
mCamera.lock();
if (!mCamera.previewEnabled()) {
try{
Log.d(LOG_TAG, "surfaceCreated startPreview. ");
mCamera.setPreviewDisplay(mLocalVideoPhoneView.getHolder());
mCamera.startPreview();
} catch (Exception e) {
Log.e(LOG_TAG, "setPreviewDisplay failed, " + e);
}
} else {
try{
Log.d(LOG_TAG, "surfaceCreated setPreviewDisplay. ");
mCamera.setPreviewDisplay(mLocalVideoPhoneView.getHolder());
} catch (Exception e) {
Log.e(LOG_TAG, "setPreviewDisplay failed, " + e);
}
}
mCamera.unlock();
}
}
}
}
}
Log.d(LOG_TAG, "surfaceCreated end. ");
}
public void surfaceDestroyed(SurfaceHolder arg0) {
if (mSurfaceReadyCount > 0) mSurfaceReadyCount--;
Log.d(LOG_TAG, "surfaceDestroyed, mSurfaceReadyCount: " + mSurfaceReadyCount);
// sometimes, this function will be called after onDestroy(), so we need check (mp != null) here
if ((mSurfaceReadyCount == 0) && (mp != null)){
if (mp.getCodecState() == MediaPhone.CodecState.CODEC_START) {
mp.stopDownLink();
} else {
if (mCamera != null) {
synchronized(mCameraLock) {
if (mCamera != null) {
mCamera.lock();
try{
Log.d(LOG_TAG, "surfaceDestroyed stopPreview. "+mCamera.previewEnabled());
if (mCamera.previewEnabled()) {
mCamera.stopPreview();
}
mCamera.setPreviewDisplay(null);
mCamera.startPreview();
} catch (Exception e) {
Log.e(LOG_TAG, "setPreviewDisplay failed, " + e);
}
mCamera.unlock();
}
}
}
}
mp.setLocalDisplay(null);
mp.setRemoteDisplay(null);
}
Log.d(LOG_TAG, "surfaceDestroyed end. ");
}
private void initMediaPhone(Phone phone) {
mp.setOnVideoSizeChangedListener(this);
mp.setOnMediaInfoListener(this);
mp.setOnCallEventListener(this);
mp.setOnErrorListener(this);
}
private void showCameraBusyAndFinish() {
Log.e(LOG_TAG, "camera busy and finish");
//Resources ress = getResources();
// Util.showFatalErrorAndFinish(InVideoCallScreen.this,
// "camera busy and finish",
// "camera busy and finish");
}
private static Phone getPhoneBase(Phone phone) {
if (phone instanceof PhoneProxy) {
return phone.getForegroundCall().getPhone();
}
return phone;
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
if (DBG) log("onCreate()... this = " + this);
setCameraFlag(true);
if(SystemProperties.getInt("ro.sprd.volume_control_icon", 0) == 1){
mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
}
super.onCreate(savedInstanceState);
final PhoneApp app = PhoneApp.getInstance();
mApplication = PhoneApp.getInstance();
app.setInVideoCallScreenInstance(this);
// set this flag so this activity will stay in front of the keyguard
int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
if (app.getPhoneState() == Phone.State.OFFHOOK) {
// While we are in call, the in-call screen should dismiss the keyguard.
// This allows the user to press Home to go directly home without going through
// an insecure lock screen.
// But we do not want to do this if there is no active call so we do not
// bypass the keyguard if the call is not answered or declined.
flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
}
getWindow().addFlags(flags);
int phoneId = getIntent().getIntExtra(SUBSCRIPTION_KEY,
app.getDefaultSubscription());
log("onCreate phoneId: " + phoneId);
setPhone(app.getPhone(phoneId)); // Sets mPhone and mForegroundCall/mBackgroundCall/mRingingCall
mCM = PhoneApp.getInstance().mCM;
mBluetoothHandsfree = app.getBluetoothHandsfree();
if (VDBG) log("- mBluetoothHandsfree: " + mBluetoothHandsfree);
if (mBluetoothHandsfree != null) {
// The PhoneApp only creates a BluetoothHandsfree instance in the
// first place if BluetoothAdapter.getDefaultAdapter()
// succeeds. So at this point we know the device is BT-capable.
mBluetoothHeadset = new BluetoothHeadset(this, null);
if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
}
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.invideocall_screen);
initInCallScreen();
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "InVideoCallScreen Tag");
// Create the dtmf dialer. The dialer view we use depends on the
// current platform:
//
// - On non-prox-sensor devices, it's the dialpad contained inside
// a SlidingDrawer widget (see dtmf_twelve_key_dialer.xml).
//
// - On "full touch UI" devices, it's the compact non-sliding
// dialpad that appears on the upper half of the screen,
// above the main cluster of InCallTouchUi buttons
// (see non_drawer_dialpad.xml).
//
// TODO: These should both be ViewStubs, and right here we should
// inflate one or the other. (Also, while doing that, let's also
// move this block of code over to initInCallScreen().)
//
SlidingDrawer dialerDrawer;
if (isTouchUiEnabled()) {
// This is a "full touch" device.
mDialerView = (VideoPhoneTwelveKeyDialerView) findViewById(R.id.non_drawer_dtmf_dialer);
if (DBG) log("- Full touch device! Found dialerView: " + mDialerView);
dialerDrawer = null; // No SlidingDrawer used on this device.
} else {
// Use the old-style dialpad contained within the SlidingDrawer.
mDialerView = (VideoPhoneTwelveKeyDialerView) findViewById(R.id.dtmf_dialer);
if (DBG) log("- Using SlidingDrawer-based dialpad. Found dialerView: " + mDialerView);
dialerDrawer = (SlidingDrawer) findViewById(R.id.dialer_container);
if (DBG) log(" ...and the SlidingDrawer: " + dialerDrawer);
}
// Sanity-check that (regardless of the device) at least the
// dialer view is present:
if (mDialerView == null) {
Log.e(LOG_TAG, "onCreate: couldn't find dialerView", new IllegalStateException());
}
// Finally, create the DTMFTwelveKeyDialer instance.
mDialer = new VideoPhoneTwelveKeyDialer(this, mDialerView, null);
registerForPhoneStates();
getVideoPhoneConfig();
updateViewConfig();
mMyBrightness = getBrightness();
// if in incoming call, don't checkout network anymore and always start camera preview.
String action = getIntent().getAction();
if ((action != null) && (action.equals(Intent.ACTION_CALL))) {
if (checkIfOkToInitiateOutgoingCall() == InCallInitStatus.SUCCESS) {
startCameraPreview();
}
} else {
startCameraPreview();
}
if (ENABLE_MEDIAPHONE) {
if (mp == null) {
final Object syncObj = new Object();
final CommandsInterface ril = ((TDPhone)getPhoneBase(mPhone)).mCM;//PhoneFactory.getDefaultCM();
mMediaPhoneThread = new HandlerThread("MediaPhone"){
protected void onLooperPrepared() {
Log.e(LOG_TAG, "before create mediaphone");
synchronized(syncObj) {
mp = MediaPhone.create(ril, DEFAULT_COMM);
syncObj.notifyAll();
}
Log.e(LOG_TAG, "after create mediaphone");
}
};
mMediaPhoneThread.start();
Log.e(LOG_TAG, "before wait mediaphone");
synchronized(syncObj) {
try{
syncObj.wait(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.e(LOG_TAG, "after wait mediaphone");
}
}
// No need to change wake state here; that happens in onResume() when we
// are actually displayed.
// Handle the Intent we were launched with, but only if this is the
// the very first time we're being launched (ie. NOT if we're being
// re-initialized after previously being shut down.)
// Once we're up and running, any future Intents we need
// to handle will come in via the onNewIntent() method.
if (savedInstanceState == null) {
if (DBG) log("onCreate(): this is our very first launch, checking intent...");
// Stash the result code from internalResolveIntent() in the
// mInCallInitialStatus field. If it's an error code, we'll
// handle it in onResume().
mInCallInitialStatus = internalResolveIntent(getIntent());
if (DBG) log("onCreate(): mInCallInitialStatus = " + mInCallInitialStatus);
if (mInCallInitialStatus != InCallInitStatus.SUCCESS) {
Log.w(LOG_TAG, "onCreate: status " + mInCallInitialStatus
+ " from internalResolveIntent()");
// See onResume() for the actual error handling.
}
} else {
mInCallInitialStatus = InCallInitStatus.SUCCESS;
}
// The "touch lock overlay" feature is used only on devices that
// *don't* use a proximity sensor to turn the screen off while in-call.
mUseTouchLockOverlay = !app.proximitySensorModeEnabled();
mStorageReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
onReceiveMediaBroadcast(intent);
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
intentFilter.addDataScheme("file");
registerReceiver(mStorageReceiver, intentFilter);
if (DBG) log("onCreate(): exit");
}
private void onReceiveMediaBroadcast(Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
log("ACTION_MEDIA_MOUNTED");
mbExternalStorageAvail = true;
} else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
log("ACTION_MEDIA_UNMOUNTED");
mbExternalStorageAvail = false;
enableRecord(false, 0);
}
}
/**
* Sets the Phone object used internally by the InCallScreen.
*
* In normal operation this is called from onCreate(), and the
* passed-in Phone object comes from the PhoneApp.
* For testing, test classes can use this method to
* inject a test Phone instance.
*/
/* package */ void setPhone(Phone phone) {
mPhone = phone;
// Hang onto the three Call objects too; they're singletons that
// are constant (and never null) for the life of the Phone.
mForegroundCall = mPhone.getForegroundCall();
mBackgroundCall = mPhone.getBackgroundCall();
mRingingCall = mPhone.getRingingCall();
}
public Phone getPhone() {
return mPhone;
}
@Override
protected void onStart() {
if (DBG) log("onStart()...");
super.onStart();
// get config from system
getSysConfig();
}
@Override
protected void onResume() {
if (DBG) log("onResume()...");
super.onResume();
mWakeLock.acquire();
getSysConfig();
setMyConfig();
updateViewConfig();
mIsForegroundActivity = true;
final PhoneApp app = PhoneApp.getInstance();
//app.disableStatusBar();
// Touch events are never considered "user activity" while the
// InCallScreen is active, so that unintentional touches won't
// prevent the device from going to sleep.
app.setIgnoreTouchUserActivity(true);
// Disable the status bar "window shade" the entire time we're on
// the in-call screen.
// NotificationMgr.getDefault().getStatusBarMgr().enableExpandedView(false);
// Listen for broadcast intents that might affect the onscreen UI.
registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
// Keep a "dialer session" active when we're in the foreground.
// (This is needed to play DTMF tones.)
mDialer.startDialerSession();
// Check for any failures that happened during onCreate() or onNewIntent().
if (DBG) log("- onResume: initial status = " + mInCallInitialStatus);
if (mInCallInitialStatus != InCallInitStatus.SUCCESS) {
if (DBG) log("- onResume: failure during startup: " + mInCallInitialStatus);
// Don't bring up the regular Phone UI! Instead bring up
// something more specific to let the user deal with the
// problem.
if (handleStartupError(mInCallInitialStatus)) {
// But it *is* OK to continue with the rest of onResume(),
// since any further setup steps (like updateScreen() and the
// CallCard setup) will fall back to a "blank" state if the
// phone isn't in use.
mInCallInitialStatus = InCallInitStatus.SUCCESS;
}
}
// Set the volume control handler while we are in the foreground.
final boolean bluetoothConnected = isBluetoothAudioConnected();
if (bluetoothConnected) {
setVolumeControlStream(AudioManager.STREAM_BLUETOOTH_SCO);
} else {
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
}
takeKeyEvents(true);
// Before checking the state of the phone, clean up any
// connections in the DISCONNECTED state.
// (The DISCONNECTED state is used only to drive the "call ended"
// UI; it's totally useless when *entering* the InCallScreen.)
mPhone.clearDisconnected();
// InCallScreen is now active.
EventLog.writeEvent(EventLogTags.PHONE_UI_ENTER);
// Update the poke lock and wake lock when we move to
// the foreground.
//
// But we need to do something special if we're coming
// to the foreground while an incoming call is ringing:
if (mPhone.getState() == Phone.State.RINGING) {
// If the phone is ringing, we *should* already be holding a
// full wake lock (which we would have acquired before
// firing off the intent that brought us here; see
// PhoneUtils.showIncomingCallUi().)
//
// We also called preventScreenOn(true) at that point, to
// avoid cosmetic glitches while we were being launched.
// So now we need to post an ALLOW_SCREEN_ON message to
// (eventually) undo the prior preventScreenOn(true) call.
//
// (In principle we shouldn't do this until after our first
// layout/draw pass. But in practice, the delay caused by
// simply waiting for the end of the message queue is long
// enough to avoid any flickering of the lock screen before
// the InCallScreen comes up.)
if (VDBG) log("- posting ALLOW_SCREEN_ON message...");
mHandler.removeMessages(ALLOW_SCREEN_ON);
mHandler.sendEmptyMessage(ALLOW_SCREEN_ON);
// TODO: There ought to be a more elegant way of doing this,
// probably by having the PowerManager and ActivityManager
// work together to let apps request that the screen on/off
// state be synchronized with the Activity lifecycle.
// (See bug 1648751.)
} else {
// The phone isn't ringing; this is either an outgoing call, or
// we're returning to a call in progress. There *shouldn't* be
// any prior preventScreenOn(true) call that we need to undo,
// but let's do this just to be safe:
app.preventScreenOn(false);
}
app.updateWakeState();
// Restore the mute state if the last mute state change was NOT
// done by the user.
if (app.getRestoreMuteOnInCallResume()) {
//TS for compile
PhoneUtils.restoreMuteState();
app.setRestoreMuteOnInCallResume(false);
}
//getVideoPhoneConfig();
if (DBG) log("onResume: mSurfaceReadyCount = " + mSurfaceReadyCount);
updateScreen();
Profiler.profileViewCreate(getWindow(), InCallScreen.class.getName());
if (VDBG) log("onResume() done.");
}
// onPause is guaranteed to be called when the InCallScreen goes
// in the background.
@Override
protected void onPause() {
if (DBG) log("onPause()...");
super.onPause();
mWakeLock.release();
//restore system config
setSysConfig();
mIsForegroundActivity = false;
final PhoneApp app = PhoneApp.getInstance();
// A safety measure to disable proximity sensor in case call failed
// and the telephony state did not change.
app.setBeginningCall(false);
// as a catch-all, make sure that any dtmf tones are stopped
// when the UI is no longer in the foreground.
mDialer.onDialerKeyUp(null);
// Release any "dialer session" resources, now that we're no
// longer in the foreground.
mDialer.stopDialerSession();
// If the device is put to sleep as the phone call is ending,
// we may see cases where the DELAYED_CLEANUP_AFTER_DISCONNECT
// event gets handled AFTER the device goes to sleep and wakes
// up again.
// This is because it is possible for a sleep command
// (executed with the End Call key) to come during the 2
// seconds that the "Call Ended" screen is up. Sleep then
// pauses the device (including the cleanup event) and
// resumes the event when it wakes up.
// To fix this, we introduce a bit of code that pushes the UI
// to the background if we pause and see a request to
// DELAYED_CLEANUP_AFTER_DISCONNECT.
// Note: We can try to finish directly, by:
// 1. Removing the DELAYED_CLEANUP_AFTER_DISCONNECT messages
// 2. Calling delayedCleanupAfterDisconnect directly
// However, doing so can cause problems between the phone
// app and the keyguard - the keyguard is trying to sleep at
// the same time that the phone state is changing. This can
// end up causing the sleep request to be ignored.
if (mHandler.hasMessages(DELAYED_CLEANUP_AFTER_DISCONNECT)
&& mPhone.getState() != Phone.State.RINGING) {
if (DBG) log("DELAYED_CLEANUP_AFTER_DISCONNECT detected, moving UI to background.");
}
EventLog.writeEvent(EventLogTags.PHONE_UI_EXIT);
// Re-enable the status bar (which we disabled in onResume().)
//NotificationMgr.getDefault().getStatusBarMgr().enableExpandedView(true);
// Unregister for broadcast intents. (These affect the visible UI
// of the InCallScreen, so we only care about them while we're in the
// foreground.)
unregisterReceiver(mReceiver);
// Re-enable "user activity" for touch events.
// We actually do this slightly *after* onPause(), to work around a
// race condition where a touch can come in after we've paused
// but before the device actually goes to sleep.
// TODO: The PowerManager itself should prevent this from happening.
mHandler.postDelayed(new Runnable() {
public void run() {
app.setIgnoreTouchUserActivity(false);
}
}, 500);
// Dismiss any dialogs we may have brought up, just to be 100%
// sure they won't still be around when we get back here.
dismissAllDialogs();
//app.reenableStatusBar();
// Make sure we revert the poke lock and wake lock when we move to
// the background.
app.updateWakeState();
// clear the dismiss keyguard flag so we are back to the default state
// when we next resume
updateKeyguardPolicy(false);
log("onPause()...mInCallScreenMode: " + mInCallScreenMode);
if (InCallScreenMode.FALL_BACK == mInCallScreenMode) {
finish();
}
}
@Override
protected void onStop() {
if (DBG) log("onStop()...");
super.onStop();
Phone.State state = mPhone.getState();
if (DBG) log("onStop: state = " + state);
}
@Override
protected void onDestroy() {
if (DBG) log("onDestroy()...");
super.onDestroy();
if (mStorageReceiver != null) {
unregisterReceiver(mStorageReceiver);
mStorageReceiver = null;
}
if (mStartPreviewThread != null) {
try {
mStartPreviewThread.join();
} catch (InterruptedException ex) {
log("mStartPreviewThread.quit() exception " + ex);
}
}
setCameraFlag(false);
closeCamera(1000);
mCallTime.cancelTimer();
if (mp != null){
mp.release();
mp = null;
if (DBG) log("mMediaPhoneThread.quit(): " + mMediaPhoneThread.quit());
}
// Set the magic flag that tells us NOT to handle any handler
// messages that come in asynchronously after we get destroyed.
mIsDestroyed = true;
final PhoneApp app = PhoneApp.getInstance();
app.setInVideoCallScreenInstance(null);
mDialer.clearInCallScreenReference();
mDialer = null;
unregisterForPhoneStates();
// No need to change wake state here; that happens in onPause() when we
// are moving out of the foreground.
if (mBluetoothHeadset != null) {
mBluetoothHeadset.close();
mBluetoothHeadset = null;
}
if (mCallIntent != null) {
if (LastAction.ACTION_VIDEOCALL == mLastAction) {
startActivity(mCallIntent);
} else if (LastAction.ACTION_VOICECALL == mLastAction) {
Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED);
try {
intent.setData(Uri.fromParts("tel", getInitialNumber(mCallIntent), null));
} catch (PhoneUtils.VoiceMailNumberMissingException ex) {
log(" exception when getInitialNumber: " + ex);
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
mLastAction = LastAction.ACTION_NONE;
// Dismiss all dialogs, to be absolutely sure we won't leak any of
// them while changing orientation.
dismissAllDialogs();
}
/**
* Dismisses the in-call screen.
*
* We never *really* finish() the InCallScreen, 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 InCallScreen instance around for the
* entire uptime of the device. This noticeably improves the UI
* responsiveness for incoming calls.)
*/
/*@Override
public void finish() {
if (DBG) log("finish()...");
moveTaskToBack(true);
}*/
/**
* End the current in call screen session.
*
* This must be called when an InCallScreen session has
* complete so that the next invocation via an onResume will
* not be in an old state.
*/
public void endInCallScreenSession() {
if (DBG) log("endInCallScreenSession()...");
//moveTaskToBack(true);
setInCallScreenMode(InCallScreenMode.UNDEFINED);
this.finish();
}
/* package */ boolean isForegroundActivity() {
return mIsForegroundActivity;
}
/* package */ void updateKeyguardPolicy(boolean dismissKeyguard) {
if (dismissKeyguard) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
}
}
private void registerForPhoneStates() {
if (!mRegisteredForPhoneStates) {
mPhone.registerForPreciseVideoCallStateChanged(mHandler, PHONE_STATE_CHANGED, null);
mPhone.registerForVideoCallDisconnect(mHandler, PHONE_DISCONNECT, null);
/* int phoneType = mPhone.getPhoneType();
if (phoneType == Phone.PHONE_TYPE_GSM) {
mPhone.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null);
// register for the MMI complete message. Upon completion,
// PhoneUtils will bring up a system dialog instead of the
// message display class in PhoneUtils.displayMMIComplete().
// We'll listen for that message too, so that we can finish
// the activity at the same time.
mPhone.registerForMmiComplete(mHandler, PhoneApp.MMI_COMPLETE, null);
} else if (phoneType == Phone.PHONE_TYPE_CDMA) {
if (DBG) log("Registering for Call Waiting.");
mPhone.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
} else {
throw new IllegalStateException("Unexpected phone type: " + phoneType);
}
mPhone.setOnPostDialCharacter(mHandler, POST_ON_DIAL_CHARS, null);
mPhone.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED, null);
if (phoneType == Phone.PHONE_TYPE_CDMA) {
mPhone.registerForCdmaOtaStatusChange(mHandler, EVENT_OTA_PROVISION_CHANGE, null);
}*/
mRegisteredForPhoneStates = true;
}
}
private void unregisterForPhoneStates() {
mPhone.unregisterForPreciseVideoCallStateChanged(mHandler);
mPhone.unregisterForVideoCallDisconnect(mHandler);
mPhone.unregisterForMmiInitiate(mHandler);
mPhone.setOnPostDialCharacter(null, POST_ON_DIAL_CHARS, null);
mRegisteredForPhoneStates = false;
}
/* package */ void updateAfterRadioTechnologyChange() {
if (DBG) Log.d(LOG_TAG, "updateAfterRadioTechnologyChange()...");
// Unregister for all events from the old obsolete phone
unregisterForPhoneStates();
// (Re)register for all events relevant to the new active phone
registerForPhoneStates();
}
@Override
protected void onNewIntent(Intent intent) {
if (DBG) log("onNewIntent: intent=" + intent);
// We're being re-launched with a new Intent. Since we keep
// around a single InCallScreen instance for the life of the phone
// process (see finish()), this sequence will happen EVERY time
// there's a new incoming or outgoing call except for the very
// first time the InCallScreen gets created. This sequence will
// also happen if the InCallScreen is already in the foreground
// (e.g. getting a new ACTION_CALL intent while we were already
// using the other line.)
// 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 this intent, and stash the
// result code from internalResolveIntent() in the
// mInCallInitialStatus field. If it's an error code, we'll
// handle it in onResume().
mInCallInitialStatus = internalResolveIntent(intent);
if (mInCallInitialStatus != InCallInitStatus.SUCCESS) {
Log.w(LOG_TAG, "onNewIntent: status " + mInCallInitialStatus
+ " from internalResolveIntent()");
// See onResume() for the actual error handling.
}
}
/* package */ InCallInitStatus internalResolveIntent(Intent intent) {
if (intent == null || intent.getAction() == null) {
return InCallInitStatus.SUCCESS;
}
String action = intent.getAction();
if (DBG) log("internalResolveIntent: action=" + action);
// The calls to setRestoreMuteOnInCallResume() inform the phone
// that we're dealing with new connections (either a placing an
// outgoing call or answering an incoming one, and NOT handling
// an aborted "Add Call" request), so we should let the mute state
// be handled by the PhoneUtils phone state change handler.
final PhoneApp app = PhoneApp.getInstance();
// If OTA Activation is configured for Power up scenario, then
// InCallScreen UI started with Intent of ACTION_SHOW_ACTIVATION
// to show OTA Activation screen at power up.
if (action.equals(Intent.ACTION_ANSWER)) {
internalAnswerCall();
app.setRestoreMuteOnInCallResume(false);
return InCallInitStatus.SUCCESS;
} else if (action.equals(Intent.ACTION_CALL)){
mbIncoming = false;
mCallIntent = intent;
if (mPhone.getState() != Phone.State.IDLE) {
if (PhoneUtils.isVideoCall()) {
Log.e(LOG_TAG, "Cann't make another videocall, during video call");
return InCallInitStatus.ALREADY_IN_3GCALL;
} else {
Log.e(LOG_TAG, "Cann't make another videocall, during voice call");
return InCallInitStatus.ALREADY_IN_2GCALL;
}
}
app.setRestoreMuteOnInCallResume(false);
InCallInitStatus status = placeCall(intent);
Log.w(LOG_TAG, "internalResolveIntent: placecall status: " + status);
return status;
} else if (action.equals(intent.ACTION_MAIN)) {
//mbIncoming = intent.getBooleanExtra(PhoneApp.EXTRA_IS_INCOMINGCALL, false);
log("internalResolveIntent: mbIncoming=" + mbIncoming);
if (mbIncoming){
if (ENABLE_MEDIAPHONE) {
initMediaPhone(mPhone);
mp.onCodecRequest(MediaPhone.CODEC_OPEN, 0);
}
if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
if (VDBG) log("- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
if (showDialpad) {
mDialer.openDialer(false); // no "opening" animation
} else {
mDialer.closeDialer(false); // no "closing" animation
}
}
return InCallInitStatus.SUCCESS;
} else {
// re-enter video call screen
mbIncoming = false;
}
return InCallInitStatus.SUCCESS;
} else {
Log.w(LOG_TAG, "internalResolveIntent: unexpected intent action: " + action);
// But continue the best we can (basically treating this case
// like ACTION_MAIN...)
return InCallInitStatus.SUCCESS;
}
}
private void initInCallScreen() {
if (VDBG) log("initInCallScreen()...");
// Have the WindowManager filter out touch events that are "too fat".
getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
// Run in a 32-bit window, which improves the appearance of some
// semitransparent artwork in the in-call UI (like the CallCard
// photo borders).
getWindow().setFormat(PixelFormat.RGBX_8888);
// Helper class to keep track of enabledness/state of UI controls
// mInCallControlState = new InCallControlState(this, mPhone);
mDensity = getResources().getDisplayMetrics().density;
if (DBG) log("- Density: " + mDensity);
mAllowVolumeButtion = getResources().getBoolean(R.bool.config_show_vt_onscreen_volume_button);
mButtonIncrease = (Button)findViewById(R.id.increase_volume);
mButtonDecline = (Button)findViewById(R.id.decline_volume);
if (mAllowVolumeButtion) {
mButtonIncrease.setOnClickListener(this);
mButtonDecline.setOnClickListener(this);
mButtonIncrease.setText("+");
mButtonDecline.setText("-");
} else {
mButtonIncrease.setVisibility(View.GONE);
mButtonDecline.setVisibility(View.GONE);
}
// Text colors
mTextColorDefaultPrimary = // corresponds to textAppearanceLarge
getResources().getColor(android.R.color.primary_text_dark);
mTextColorDefaultSecondary = // corresponds to textAppearanceSmall
getResources().getColor(android.R.color.secondary_text_dark);
mTextColorConnected = getResources().getColor(R.color.incall_textConnected);
mTextColorConnectedBluetooth =
getResources().getColor(R.color.incall_textConnectedBluetooth);
mTextColorEnded = getResources().getColor(R.color.incall_textEnded);
mTextColorOnHold = getResources().getColor(R.color.incall_textOnHold);
mLargeVideoPhoneView =(SurfaceView)findViewById(R.id.LargeVideoPhoneView);
mSmallVideoPhoneView = (SurfaceView)findViewById(R.id.SmallVideoPhoneView);
initVideoPoneView(mLargeVideoPhoneView);
initVideoPoneView(mSmallVideoPhoneView);
mBrightnessAdjustMenu = (AdjustMenuView)findViewById(R.id.brightness_adjustmenu);
mCameraBrightnessAdjustMenu = (AdjustMenuView)findViewById(R.id.camera_brightness_adjustmenu);
mCameraContrastAdjustMenu = (AdjustMenuView)findViewById(R.id.camera_contrast_adjustmenu);
if((mBrightnessAdjustMenu != null)
&& (mCameraBrightnessAdjustMenu != null)
&& (mCameraContrastAdjustMenu != null)){
mBrightnessAdjustMenu.setMax(MAXIMUM_BACKLIGHT);
mBrightnessAdjustMenu.setMin(MINIMUM_BACKLIGHT);
mBrightnessAdjustMenu.setOnSeekBarChangeListener(this);
mCameraBrightnessAdjustMenu.setMax(MAXIMUM_CAMERA_BACKLIGHT);
mCameraBrightnessAdjustMenu.setMin(MINIMUM_CAMERA_BACKLIGHT);
mCameraBrightnessAdjustMenu.setOnSeekBarChangeListener(this);
mCameraContrastAdjustMenu.setMax(MAXIMUM_CAMERA_CONTRAST);
mCameraContrastAdjustMenu.setMin(MINIMUM_CAMERA_CONTRAST);
mCameraContrastAdjustMenu.setOnSeekBarChangeListener(this);
}
//SurfaceHolder largeHolder = mLargeVideoPhoneView.getHolder();
//largeHolder.addCallback(mLargeVideoPhoneView);
//SurfaceHolder smallHolder = mSmallVideoPhoneView.getHolder();
//smallHolder.addCallback(mSmallVideoPhoneView);
mInCallPanel = findViewById(R.id.InCallPanel);
mInComingCallPanel = findViewById(R.id.InComingCallPanel);
mDialerBtn = (Button)findViewById(R.id.dialer);
mHangupBtn = (Button)findViewById(R.id.hangup);
mAnswerBtn = (Button)findViewById(R.id.answer);
mDeclineBtn = (Button)findViewById(R.id.decline);
mDialerBtn.setOnClickListener(this);
mHangupBtn.setOnClickListener(this);
mAnswerBtn.setOnClickListener(this);
mDeclineBtn.setOnClickListener(this);
// "Upper" and "lower" title widgets
mName = (TextView) findViewById(R.id.name);
mPhoneNumber = (TextView) findViewById(R.id.phoneNumber);
mUpperTitle = (TextView) findViewById(R.id.upperTitle);
mElapsedTime = (TextView) findViewById(R.id.elapsedTime);
//mLabel = (TextView) findViewById(R.id.label);
mBeginTime = (TextView) findViewById(R.id.beginTime);
mCallInfo = (ViewGroup)findViewById(R.id.CallInfo);
mCallTime = new VideoCallTime(this);
// create a new object to track the state for the photo.
mPhotoTracker = new ContactsAsyncHelper.ImageTracker();
}
/**
* Returns true if the phone is "in use", meaning that at least one line
* is active (ie. off hook or ringing or dialing). Conversely, a return
* value of false means there's currently no phone activity at all.
*/
private boolean phoneIsInUse() {
return mPhone.getState() != Phone.State.IDLE;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// if (DBG) log("onKeyUp(keycode " + keyCode + ")...");
// push input to the dialer.
if ((mDialer != null) && (mDialer.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) {
if (DBG) log("onKeyDown(keycode " + keyCode + ")...");
switch (keyCode) {
case KeyEvent.KEYCODE_CALL:
if (mPhone.getState() == Phone.State.RINGING)
internalAnswerCall();
break;
case KeyEvent.KEYCODE_ENDCALL:
if (mPhone.getState() == Phone.State.RINGING)
internalHangupRingingCall();
else if (mPhone.getState() == Phone.State.OFFHOOK)
internalHangup();
break;
case KeyEvent.KEYCODE_MENU:
//event.startTracking();
if (event.getRepeatCount() != 0) {
if ((event.getFlags()&KeyEvent.FLAG_LONG_PRESS) != 0)
return true;
}
}
if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
return true;
}
return super.onKeyDown(keyCode, event);
}
/**
* Something has changed in the phone's state. Update the UI.
*/
private void onPhoneStateChanged(AsyncResult r) {
if (DBG) log("onPhoneStateChanged()...");
// There's nothing to do here if we're not the foreground activity.
// (When we *do* eventually come to the foreground, we'll do a
// full update then.)
if (!mIsForegroundActivity) {
if (DBG) log("onPhoneStateChanged: Activity not in foreground! Bailing out...");
return;
}
if (mInCallInitialStatus != InCallInitStatus.SUCCESS) {
if (DBG) log("onPhoneStateChanged: failure during startup! Bailing out...");
return;
}
updateScreen();
Call.State fgCallState = mForegroundCall.getState();
if (DBG) log("mLastFGCallState: " + mLastFGCallState + ", fgCallState: " + fgCallState);
if ( (mLastFGCallState != Call.State.ACTIVE) && (fgCallState == Call.State.ACTIVE)){
mHandler.sendEmptyMessageDelayed(DELAYED_SWITCH_SPEAKER, 1000);
}
mLastFGCallState = mForegroundCall.getState();
// Make sure we update the poke lock and wake lock when certain
// phone state changes occur.
PhoneApp.getInstance().updateWakeState();
}
/**
* Updates the UI after a phone connection is disconnected, as follows:
*
* - If this was a missed or rejected incoming call, and no other
* calls are active, dismiss the in-call UI immediately. (The
* CallNotifier will still create a "missed call" notification if
* necessary.)
*
* - With any other disconnect cause, if the phone is now totally
* idle, display the "Call ended" state for a couple of seconds.
*
* - Or, if the phone is still in use, stay on the in-call screen
* (and update the UI to reflect the current state of the Phone.)
*
* @param r r.result contains the connection that just ended
*/
private void onDisconnect(AsyncResult r) {
if (mInCallScreenMode == InCallScreenMode.FALL_BACK){
if (DBG) log("onDisconnect: during fallback, just return");
return;
}
displayStartTime();
Connection c = (Connection) r.result;
Connection.DisconnectCause cause = c.getDisconnectCause();
if (DBG) log("onDisconnect: " + c + ", cause=" + cause);
boolean currentlyIdle = !phoneIsInUse();
// Any time a call disconnects, clear out the "history" of DTMF
// digits you typed (to make sure it doesn't persist from one call
// to the next.)
mDialer.clearDigits();
// Under certain call disconnected states, we want to alert the user
// with a dialog instead of going through the normal disconnect
// routine.
if (cause == Connection.DisconnectCause.CALL_BARRED) {
showGenericErrorDialog(R.string.callFailed_cb_enabled, false);
return;
} else if (cause == Connection.DisconnectCause.FDN_BLOCKED) {
showGenericErrorDialog(R.string.callFailed_fdn_only, false);
return;
} else if (cause == Connection.DisconnectCause.CS_RESTRICTED) {
showGenericErrorDialog(R.string.callFailed_dsac_restricted, false);
return;
} else if (cause == Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY) {
showGenericErrorDialog(R.string.callFailed_dsac_restricted_emergency, false);
return;
} else if (cause == Connection.DisconnectCause.CS_RESTRICTED_NORMAL) {
showGenericErrorDialog(R.string.callFailed_dsac_restricted_normal, false);
return;
}
// Note: see CallNotifier.onDisconnect() for some other behavior
// that might be triggered by a disconnect event, like playing the
// busy/congestion tone.
// Keep track of whether this call was user-initiated or not.
// (This affects where we take the user next; see delayedCleanupAfterDisconnect().)
mShowCallLogAfterDisconnect = !c.isIncoming();
{
if (VDBG) log("- onDisconnect: delayed bailout...");
// Stay on the in-call screen for now. (Either the phone is
// still in use, or the phone is idle but we want to display
// the "call ended" state for a couple of seconds.)
// Force a UI update in case we need to display anything
// special given this connection's DisconnectCause (see
// CallCard.getCallFailedString()).
updateScreen();
// Display the special "Call ended" state when the phone is idle
// but there's still a call in the DISCONNECTED state:
if (currentlyIdle
&& ((mForegroundCall.getState() == Call.State.DISCONNECTED)
|| (mBackgroundCall.getState() == Call.State.DISCONNECTED))) {
if (VDBG) log("- onDisconnect: switching to 'Call ended' state...");
setInCallScreenMode(InCallScreenMode.CALL_ENDED);
}
setUpperTitle(mPhone.getContext().getString(R.string.card_title_call_ended), mTextColorEnded, Call.State.DISCONNECTED);
// Finally, arrange for delayedCleanupAfterDisconnect() to get
// called after a short interval (during which we display the
// "call ended" state.) At that point, if the
// Phone is idle, we'll finish out of this activity.
int callEndedDisplayDelay =
(cause == Connection.DisconnectCause.LOCAL)
? CALL_ENDED_SHORT_DELAY : CALL_ENDED_LONG_DELAY;
mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
callEndedDisplayDelay);
}
}
private void onDisconnect() {
if (DBG) log("onDisconnect: ");
boolean currentlyIdle = !phoneIsInUse();
{
if (VDBG) log("- onDisconnect: delayed bailout...");
// Stay on the in-call screen for now. (Either the phone is
// still in use, or the phone is idle but we want to display
// the "call ended" state for a couple of seconds.)
// Force a UI update in case we need to display anything
// special given this connection's DisconnectCause (see
// CallCard.getCallFailedString()).
updateScreen();
// Display the special "Call ended" state when the phone is idle
// but there's still a call in the DISCONNECTED state:
if (currentlyIdle
&& ((mForegroundCall.getState() == Call.State.DISCONNECTED)
|| (mBackgroundCall.getState() == Call.State.DISCONNECTED))) {
if (VDBG) log("- onDisconnect: switching to 'Call ended' state...");
setInCallScreenMode(InCallScreenMode.CALL_ENDED);
}
setUpperTitle(mPhone.getContext().getString(R.string.card_title_call_ended), mTextColorEnded, Call.State.DISCONNECTED);
mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
CALL_ENDED_SHORT_DELAY);
}
}
private void updateMainCallStatus() {
//if (DBG) log("updateMainCallStatus()...");
Phone.State state = mPhone.getState();
if (state == Phone.State.RINGING){
mRingingCall = mPhone.getRingingCall();
displayMainCallStatus(mPhone,mRingingCall);
} else if (state == Phone.State.OFFHOOK){
mForegroundCall = mPhone.getForegroundCall();
displayMainCallStatus(mPhone,mForegroundCall);
} else {
displayMainCallStatus(mPhone,null);
}
}
/**
* Updates the state of the in-call UI based on the current state of
* the Phone.
*/
private void updateScreen() {
if (DBG) log("updateScreen()...");
// Don't update anything if we're not in the foreground (there's
// no point updating our UI widgets since we're not visible!)
// Also note this check also ensures we won't update while we're
// in the middle of pausing, which could cause a visible glitch in
// the "activity ending" transition.
if (!mIsForegroundActivity) {
if (DBG) log("- updateScreen: not the foreground Activity! Bailing out...");
return;
}
final PhoneApp app = PhoneApp.getInstance();
if (mInCallScreenMode == InCallScreenMode.CALL_ENDED) {
if (DBG) log("- updateScreen: call ended state (NOT updating in-call UI)...");
// Actually we do need to update one thing: the background.
updateInCallBackground();
return;
}
if (DBG) log("- updateScreen: updating the in-call UI...");
updateDialpadVisibility();
updateInCallBackground();
updateInCallPanel();
updateMainCallStatus();
}
/**
* Given the Intent we were initially launched with,
* figure out the actual phone number we should dial.
*
* @return the phone number corresponding to the
* specified Intent, or null if the Intent is not
* an ACTION_CALL intent or if the intent's data is
* malformed or missing.
*
* @throws VoiceMailNumberMissingException if the intent
* contains a "voicemail" URI, but there's no voicemail
* number configured on the device.
*/
private String getInitialNumber(Intent intent)
throws PhoneUtils.VoiceMailNumberMissingException {
String action = intent.getAction();
if (action == null) {
return null;
}
if (action != null && action.equals(Intent.ACTION_CALL) &&
intent.hasExtra(Intent.EXTRA_PHONE_NUMBER)) {
return intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
}
//TS for compile
return PhoneUtils.getNumberFromIntent(this, intent);
}
/**
* Make a call to whomever the intent tells us to.
*
* @param intent the Intent we were launched with
* @return InCallInitStatus.SUCCESS if we successfully initiated an
* outgoing call. If there was some kind of failure, return one of
* the other InCallInitStatus codes indicating what went wrong.
*/
private InCallInitStatus placeCall(Intent intent) {
if (VDBG) log("placeCall()... intent = " + intent);
String number;
// Check the current ServiceState to make sure it's OK
// to even try making a call.
InCallInitStatus okToCallStatus = checkIfOkToInitiateOutgoingCall();
try {
number = getInitialNumber(intent);
} catch (PhoneUtils.VoiceMailNumberMissingException ex) {
// If the call status is NOT in an acceptable state, it
// may effect the way the voicemail number is being
// retrieved. Mask the VoiceMailNumberMissingException
// with the underlying issue of the phone state.
if (okToCallStatus != InCallInitStatus.SUCCESS) {
if (DBG) log("Voicemail number not reachable in current SIM card state.");
return okToCallStatus;
}
if (DBG) log("VoiceMailNumberMissingException from getInitialNumber()");
return InCallInitStatus.VOICEMAIL_NUMBER_MISSING;
}
if (number == null) {
Log.w(LOG_TAG, "placeCall: couldn't get a phone number from Intent " + intent);
return InCallInitStatus.NO_PHONE_NUMBER_SUPPLIED;
}
if (okToCallStatus != InCallInitStatus.SUCCESS) {
return okToCallStatus;
}
final PhoneApp app = PhoneApp.getInstance();
mNeedShowCallLostDialog = false;
// We have a valid number, so try to actually place a call:
// make sure we pass along the intent's URI which is a
// reference to the contact. We may have a provider gateway
// phone number to use for the outgoing call.
int callStatus;
Uri contactUri = intent.getData();
mCallData = new CallData(number, contactUri);
return internalPlaceCall();
}
private InCallInitStatus internalPlaceCall() {
int callStatus;
InCallInitStatus ret = InCallInitStatus.SUCCESS;
String number = mCallData.number;
Uri contactUri = mCallData.contactUri;
if (InCallInitStatus.SUCCESS != ret)
return ret;
Log.w(LOG_TAG, "internalPlaceCall");
if (ENABLE_MEDIAPHONE) {
initMediaPhone(mPhone);
}
callStatus = PhoneUtils.placeVideoCall(mPhone, number, contactUri);
switch (callStatus) {
case PhoneUtils.CALL_STATUS_DIALED:
if (VDBG) log("placeCall: PhoneUtils.placeVideoCall() succeeded for regular call '"
+ number + "'.");
// Any time we initiate a call, force the DTMF dialpad to
// close. (We want to make sure the user can see the regular
// in-call UI while the new call is dialing, and when it
// first gets connected.)
mDialer.closeDialer(false); // no "closing" animation
// Also, in case a previous call was already active (i.e. if
// we just did "Add call"), clear out the "history" of DTMF
// digits you typed, to make sure it doesn't persist from the
// previous call to the new call.
// TODO: it would be more precise to do this when the actual
// phone state change happens (i.e. when a new foreground
// call appears and the previous call moves to the
// background), but the InCallScreen doesn't keep enough
// state right now to notice that specific transition in
// onPhoneStateChanged().
mDialer.clearDigits();
return InCallInitStatus.SUCCESS;
case PhoneUtils.CALL_STATUS_FAILED:
Log.w(LOG_TAG, "placeCall: PhoneUtils.placeVideoCall() FAILED for number '"
+ number + "'.");
// We couldn't successfully place the call; there was some
// failure in the telephony layer.
return InCallInitStatus.CALL_FAILED;
case PhoneUtils.CALL_STATUS_FAILED_ONLY_FDN:
Log.w(LOG_TAG, "placeCall: PhoneUtils.placeCall() FAILED for number '"
+ number + "'.");
// We couldn't successfully place the call; there was some
// failure in the telephony layer.
return InCallInitStatus.CALL_FAILED_FDN_ONLY;
default:
Log.w(LOG_TAG, "placeCall: unknown callStatus " + callStatus
+ " from PhoneUtils.placeVideoCall() for number '" + number + "'.");
return InCallInitStatus.SUCCESS; // Try to continue anyway...
}
}
/**
* Checks the current ServiceState to make sure it's OK
* to try making an outgoing call to the specified number.
*
* @return InCallInitStatus.SUCCESS if it's OK to try calling the specified
* number. If not, like if the radio is powered off or we have no
* signal, return one of the other InCallInitStatus codes indicating what
* the problem is.
*/
private InCallInitStatus checkIfOkToInitiateOutgoingCall() {
if (Settings.System.getInt(getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) > 0) {
return InCallInitStatus.AIRPLANE_MODE_ON;
}
if (!isIn3GNetwork())
return InCallInitStatus.NO_IN_3G;
//isIn3GNetwork();
// Watch out: do NOT use PhoneStateIntentReceiver.getServiceState() here;
// that's not guaranteed to be fresh. To synchronously get the
// CURRENT service state, ask the Phone object directly:
int state = mPhone.getServiceState().getState();
if (VDBG) log("checkIfOkToInitiateOutgoingCall: ServiceState = " + state);
switch (state) {
case ServiceState.STATE_IN_SERVICE:
// Normal operation. It's OK to make outgoing calls.
return InCallInitStatus.SUCCESS;
case ServiceState.STATE_POWER_OFF:
// Radio is explictly powered off.
return InCallInitStatus.POWER_OFF;
case ServiceState.STATE_EMERGENCY_ONLY:
// The phone is registered, but locked. Only emergency
// numbers are allowed.
// Note that as of Android 2.0 at least, the telephony layer
// does not actually use ServiceState.STATE_EMERGENCY_ONLY,
// mainly since there's no guarantee that the radio/RIL can
// make this distinction. So in practice the
// InCallInitStatus.EMERGENCY_ONLY state and the string
// "incall_error_emergency_only" are totally unused.
return InCallInitStatus.EMERGENCY_ONLY;
case ServiceState.STATE_OUT_OF_SERVICE:
// No network connection.
return InCallInitStatus.OUT_OF_SERVICE;
default:
throw new IllegalStateException("Unexpected ServiceState: " + state);
}
}
//
// Helper functions for answering incoming calls.
//
/**
* Answer a ringing call. This method does nothing if there's no
* ringing or waiting call.
*/
/* package */ void internalAnswerCall() {
if (DBG) log("internalAnswerCall()...");
// if (DBG) PhoneUtils.dumpCallState(mPhone);
final boolean hasRingingCall = !mRingingCall.isIdle();
if (hasRingingCall) {
// mLocalVideoPhoneView.setVisibility(View.VISIBLE);
// mRemoteVideoPhoneView.setVisibility(View.VISIBLE);
// Message.obtain(mHandler, DELAYED_PLACE_CALL);
PhoneUtils.answerCall(mRingingCall);
}
}
/**
* Answer the ringing call *and* hang up the ongoing call.
*/
/* package */ void internalAnswerAndEnd() {
if (DBG) log("internalAnswerAndEnd()...");
// if (DBG) PhoneUtils.dumpCallState(mPhone);
PhoneUtils.answerAndEndActive(mCM, mRingingCall);
}
/**
* Hang up the ringing call (aka "Don't answer").
*/
/* package */ void internalHangupRingingCall() {
if (DBG) log("internalHangupRingingCall()...");
if (VDBG) PhoneUtils.dumpCallManager();
// In the rare case when multiple calls are ringing, the UI policy
// it to always act on the first ringing call.v
PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
}
/**
* Hang up the current active call.
*/
/* package */ void internalHangup() {
if (DBG) log("internalHangup()...");
mbHangupByUser = true;
PhoneUtils.hangup(mCM);
}
/**
* Fallback the current ringing call.
*/
/* package */ void internalFallBack() {
if (DBG) log("internalFallBack()...");
PhoneUtils.fallBack(mPhone);
Intent intent = new Intent();
intent.setClass(getWindow().getContext(), ToastActivity.class);
startActivity(intent);
}
/**
* Sets the current high-level "mode" of the in-call UI.
*
* NOTE: if newMode is CALL_ENDED, the caller is responsible for
* posting a delayed DELAYED_CLEANUP_AFTER_DISCONNECT message, to make
* sure the "call ended" state goes away after a couple of seconds.
*/
private void setInCallScreenMode(InCallScreenMode newMode) {
if (DBG) log("setInCallScreenMode: " + newMode);
mInCallScreenMode = newMode;
switch (mInCallScreenMode) {
case CALL_ENDED:
break;
case NORMAL:
break;
case UNDEFINED:
break;
}
// Update the visibility of the DTMF dialer tab on any state
// change.
updateDialpadVisibility();
}
public void resetInCallScreenMode() {
if (DBG) log("resetInCallScreenMode - InCallScreenMode set to UNDEFINED");
setInCallScreenMode(InCallScreenMode.UNDEFINED);
}
private void log(String msg) {
Log.d(LOG_TAG, msg);
}
/* package */ void requestUpdateTouchUi() {
if (DBG) log("requestUpdateTouchUi()...");
}
/**
* Do some delayed cleanup after a Phone call gets disconnected.
*
* This method gets called a couple of seconds after any DISCONNECT
* event from the Phone; it's triggered by the
* DELAYED_CLEANUP_AFTER_DISCONNECT message we send in onDisconnect().
*
* If the Phone is totally idle right now, that means we've already
* shown the "call ended" state for a couple of seconds, and it's now
* time to endInCallScreenSession this activity.
*
* If the Phone is *not* idle right now, that probably means that one
* call ended but the other line is still in use. In that case, we
* *don't* exit the in-call screen, but we at least turn off the
* backlight (which we turned on in onDisconnect().)
*/
private void delayedCleanupAfterDisconnect() {
if (VDBG) log("delayedCleanupAfterDisconnect()... Phone state = " + mPhone.getState());
// Clean up any connections in the DISCONNECTED state.
//
// [Background: Even after a connection gets disconnected, its
// Connection object still stays around, in the special
// DISCONNECTED state. This is necessary because we we need the
// caller-id information from that Connection to properly draw the
// "Call ended" state of the CallCard.
// But at this point we truly don't need that connection any
// more, so tell the Phone that it's now OK to to clean up any
// connections still in that state.]
mPhone.clearDisconnected();
{ // And (finally!) exit from the in-call screen
// (but not if we're already in the process of pausing...)
if (mIsForegroundActivity) {
if (DBG) log("- delayedCleanupAfterDisconnect: finishing InCallScreen...");
// If this is a call that was initiated by the user, and
// we're *not* in emergency mode, finish the call by
// taking the user to the Call Log.
// Otherwise we simply call endInCallScreenSession, which will take us
// back to wherever we came from.
if (mShowCallLogAfterDisconnect) {
if (VDBG) log("- Show Call Log after disconnect...");
final Intent intent = PhoneApp.createCallLogIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(intent);
// Even in this case we still call endInCallScreenSession (below),
// to make sure we don't stay in the activity history.
}
}
endInCallScreenSession();
}
}
public void onClick(View view) {
int id = view.getId();
switch (id) {
case R.id.answer:
if (mPhone.getState() == Phone.State.RINGING) {
if (mbInitialCamera && (!mbAnswered)) {
internalAnswerCall();
mInComingCallPanel.setVisibility(View.INVISIBLE);
mbAnswered = true;
}
}
return;
case R.id.hangup:
if (mDialer.isOpened()){
if (VDBG) log("onClick: mDialer is opened, don't hangup...");
return;
}
if (mPhone.getState() == Phone.State.OFFHOOK)
internalHangup();
return;
case R.id.decline:
if (mPhone.getState() == Phone.State.RINGING)
internalHangupRingingCall();
return;
case R.id.dialer:
if (mDialer.isOpened()){
if (VDBG) log("onClick: mDialer is opened, don't response dialer button...");
return;
}
onShowHideDialpad();
return;
case R.id.increase_volume:
if (VDBG) log("onClick: increase_volume...");
handleIncallVolumeButton(id);
break;
case R.id.decline_volume:
if (VDBG) log("onClick: decline_volume...");
handleIncallVolumeButton(id);
break;
}
}
private void handleIncallVolumeButton(int Id) {
if (VDBG) log("handleIncallVolumeButton()...Id ="+Id);
if(Id == R.id.increase_volume){
if (VDBG) log("KeyEvent.KEYCODE_VOLUME_UP");
new Thread(new Runnable() {
public void run() {
/* Simulate a KeyStroke to the menu-button. */
simulateKeystroke(KeyEvent.KEYCODE_VOLUME_UP);
}
}).start(); /* And start the Thread. */
}
else if(Id == R.id.decline_volume){
if (VDBG) log("KeyEvent.KEYCODE_VOLUME_DOWN");
new Thread(new Runnable() {
public void run() {
/* Simulate a KeyStroke to the menu-button. */
simulateKeystroke(KeyEvent.KEYCODE_VOLUME_DOWN);
}
}).start(); /* And start the Thread. */
}
}
private void simulateKeystroke(int KeyCode) {
if (mHasFocus && mIsForegroundActivity) {
doInjectKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyCode));
doInjectKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyCode));
}
}
/** This function actually handles the KeyStroke-Injection. */
private void doInjectKeyEvent(KeyEvent kEvent) {
try {
/* Inject the KeyEvent to the Window-Manager. */
mWindowManager.injectKeyEvent(kEvent, true);
} catch (RemoteException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
/**
* Updates the background of the InCallScreen to indicate the state of
* the current call(s).
*/
private void updateInCallBackground() {
}
private void updateInCallPanel() {
Phone.State state = mPhone.getState();
boolean bShowVolumeButton = false;
if (state == Phone.State.OFFHOOK){
mInCallPanel.setVisibility(View.VISIBLE);
mInComingCallPanel.setVisibility(View.GONE);
Call call = mPhone.getForegroundCall();
if (Call.State.ACTIVE == call.getState()) {
bShowVolumeButton = true;
} else {
bShowVolumeButton = false;
}
} else if (state == Phone.State.RINGING) {
mInCallPanel.setVisibility(View.GONE);
mInComingCallPanel.setVisibility(View.VISIBLE);
mAnswerBtn.setEnabled(true);
bShowVolumeButton = false;
} else {
mInCallPanel.setVisibility(View.GONE);
mInComingCallPanel.setVisibility(View.GONE);
bShowVolumeButton = false;
}
if (mAllowVolumeButtion) {
if (bShowVolumeButton) {
mButtonIncrease.setVisibility(View.VISIBLE);
mButtonDecline.setVisibility(View.VISIBLE);
} else {
mButtonIncrease.setVisibility(View.GONE);
mButtonDecline.setVisibility(View.GONE);
}
}
}
private String getSubstitutePic(boolean bLocal){
String picFn = null;
if (bLocal) {
picFn = "/data/data/com.android.phone/files/" + VideoPhoneSetting.FAKE_LOCAL_IMAGE;
}
else {
picFn = "/data/data/com.android.phone/files/" + VideoPhoneSetting.FAKE_REMOTE_IMAGE;
}
File picFile = new File(picFn);
if (!picFile.exists()){
Log.e(LOG_TAG, "getSubstitutePic, file: " + picFn + " does not exist");
if (bLocal) {
VideoPhoneSetting.createThumb(getWindow().getContext(), null, true, VideoPhoneSetting.FAKE_LOCAL_IMAGE);
} else {
VideoPhoneSetting.createThumb(getWindow().getContext(), null, true, VideoPhoneSetting.FAKE_REMOTE_IMAGE);
}
}
Log.d(LOG_TAG, "getSubstitutePic, bLocal: " + bLocal + ", picFn: " + picFn);
return picFn;
}
private boolean isSurffixJPG(String str){
int len = str.length();
if (len<=4)
return false;
return (str.regionMatches(true, len - 4, ".jpg", 0, 4));
}
/**
* Updates the view references according to the "view size configure" and "view type configure"
*/
private void updateViewConfig() {
Log.d(LOG_TAG, "updateViewConfig mScreenLayoutConfig=" + mScreenLayoutConfig + " mLocalViewType=" + mLocalViewType + " mRemoteViewType=" + mRemoteViewType);
mLocalVideoPhoneView = null;
mRemoteVideoPhoneView = null;
mLargeVideoPhoneView.setBackgroundDrawable(null);
mSmallVideoPhoneView.setBackgroundDrawable(null);
if (mScreenLayoutConfig == ScreenLayoutConfig.LOCAL_LARGE_REMOTE_SMALL) {
mLocalVideoPhoneView = mLargeVideoPhoneView;
mRemoteVideoPhoneView = mSmallVideoPhoneView;
} else if (mScreenLayoutConfig == ScreenLayoutConfig.LOCAL_SMALL_REMOTE_LARGE) {
mLocalVideoPhoneView = mSmallVideoPhoneView;
mRemoteVideoPhoneView = mLargeVideoPhoneView;
} else {
Log.e(LOG_TAG, "ViewConfig mScreenLayoutConfig is illegal.");
return;
}
boolean bShowRemoteSurface = ((mp != null) && ((mp.getCodecState() == MediaPhone.CodecState.CODEC_START)
|| (mp.getCodecState() == MediaPhone.CodecState.CODEC_CLOSE)));
if (bShowRemoteSurface) {
if (mRemoteViewType == ViewType.LIVE) {
} else if (mRemoteViewType == ViewType.PICTURE) {
if (mbShowSubstitutePic){
Drawable pic = Drawable.createFromPath(getSubstitutePic(false));
if (pic != null){
mRemoteVideoPhoneView.setBackgroundDrawable(pic);
}
}
} else {
Log.e(LOG_TAG, "ViewConfig is illegal.");
}
} else {
Drawable pic = Drawable.createFromPath(VideoPhoneSetting.DEF_SUBSTITUTE_IMAGE_PATH);
if (pic != null){
mRemoteVideoPhoneView.setBackgroundDrawable(pic);
}
}
}
/**
* Updates the main block of caller info on the CallCard
* (ie. the stuff in the primaryCallInfo block) based on the specified Call.
*/
private void displayMainCallStatus(Phone phone, Call call) {
if (DBG) log("displayMainCallStatus(phone " + phone
+ ", call " + call + ")...");
if (call == null) {
// There's no call to display, presumably because the phone is idle.
//mCallInfo.setVisibility(View.GONE);
return;
}
mCallInfo.setVisibility(View.VISIBLE);
Call.State state = call.getState();
if (DBG) log("displayMainCallStatus - state: " + state + ", oldstate: " + mOldCallState);
if (DBG) log("displayMainCallStatus - mWaitCC:" + mWaitCC + ", mWaitCD:" + mWaitCD);
if (((mOldCallState == Call.State.DIALING)||(mOldCallState == Call.State.ALERTING))
&& (state == Call.State.ACTIVE)){
if (!mWaitCC){
if (DBG) log("displayMainCallStatus - mWaitCC: " + mWaitCC + ", send DELAYED_END_WAITCC");
mWaitCC = true;
mHandler.removeMessages(DELAYED_END_WAITCC);
mHandler.sendEmptyMessageDelayed(DELAYED_END_WAITCC,
WAIT_CC_DELAY);
}
}
mOldCallState = state;
/*if ((mWaitCC | mWaitCD) && (state == Call.State.ACTIVE)){
//if (DBG) log("displayMainCallStatus don't action, mWaitCC:" + mWaitCC + ", mWaitCD:" + mWaitCD);
return;
}*/
switch (state) {
case ACTIVE:
mbIncoming = false;
mbEverConnected = true;
mCallTime.setActiveCallMode(call);/*
mCallTime.reset();
mCallTime.periodicUpdateTimer();
mCCTime = SystemClock.elapsedRealtime();
if (mBeginTime.getText().length() <= 0) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
mBeginTime.setText(getString(R.string.video_label_begintime) + sdf.format(new Date()));
log("ACTIVE, mBeginTime: " + mBeginTime.getText());
}*/
break;
case DISCONNECTING:
mCallTime.cancelTimer();
break;
case DISCONNECTED:
// Stop getting timer ticks from this call
mCallTime.cancelTimer();
break;
default:
Log.w(LOG_TAG, "displayMainCallStatus: unexpected call state: " + state);
break;
}
updateCardTitleWidgets(phone, call);
if (PhoneUtils.isConferenceCall(call)) {
// Update onscreen info for a conference call.
// updateDisplayForConference();
} else {
// Update onscreen info for a regular call (which presumably
// has only one connection.)
Connection conn = null;
int phoneType = phone.getPhoneType();
conn = call.getEarliestConnection();
if (conn == null) {
// if (DBG) log("displayMainCallStatus: connection is null, using default values.");
// if the connection is null, we run through the behaviour
// we had in the past, which breaks down into trivial steps
// with the current implementation of getCallerInfo and
// updateDisplayForPerson.
CallerInfo info = PhoneUtils.getCallerInfo(mPhone.getContext(), null /* conn */);
updateDisplayForPerson(info, Connection.PRESENTATION_ALLOWED, false, call);
} else {
//if (DBG) log(" - CONN: " + conn + ", state = " + conn.getState());
int presentation = conn.getNumberPresentation();
// make sure that we only make a new query when the current
// callerinfo differs from what we've been requested to display.
boolean runQuery = true;
Object o = conn.getUserData();
if (o instanceof PhoneUtils.CallerInfoToken) {
runQuery = mPhotoTracker.isDifferentImageRequest(
((PhoneUtils.CallerInfoToken) o).currentInfo);
} else {
runQuery = mPhotoTracker.isDifferentImageRequest(conn);
}
if (runQuery) {
if (DBG) log("- displayMainCallStatus: starting CallerInfo query...");
PhoneUtils.CallerInfoToken info =
PhoneUtils.startGetCallerInfo(mPhone.getContext(), conn, this, call);
updateDisplayForPerson(info.currentInfo, presentation, !info.isFinal, call);
} else {
// No need to fire off a new query. We do still need
// to update the display, though (since we might have
// previously been in the "conference call" state.)
if (DBG) log("- displayMainCallStatus: using data we already have...");
if (o instanceof CallerInfo) {
CallerInfo ci = (CallerInfo) o;
// Update CNAP information if Phone state change occurred
ci.cnapName = conn.getCnapName();
ci.numberPresentation = conn.getNumberPresentation();
ci.namePresentation = conn.getCnapNamePresentation();
if (DBG) log("- displayMainCallStatus: CNAP data from Connection: "
+ "CNAP name=" + ci.cnapName
+ ", Number/Name Presentation=" + ci.numberPresentation);
if (DBG) log(" ==> Got CallerInfo; updating display: ci = " + ci);
updateDisplayForPerson(ci, presentation, false, call);
} else if (o instanceof PhoneUtils.CallerInfoToken){
CallerInfo ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
if (DBG) log("- displayMainCallStatus: CNAP data from Connection: "
+ "CNAP name=" + ci.cnapName
+ ", Number/Name Presentation=" + ci.numberPresentation);
if (DBG) log(" ==> Got CallerInfoToken; updating display: ci = " + ci);
updateDisplayForPerson(ci, presentation, true, call);
} else {
Log.w(LOG_TAG, "displayMainCallStatus: runQuery was false, "
+ "but we didn't have a cached CallerInfo object! o = " + o);
// TODO: any easy way to recover here (given that
// the CallCard is probably displaying stale info
// right now?) Maybe force the CallCard into the
// "Unknown" state?
}
}
}
}
// If we don't have a hint to display, just don't touch
// mPhoneNumber and mLabel. (Their text / color / visibility have
// already been set correctly, by either updateDisplayForPerson()
// or updateDisplayForConference().)
}
/**
* Updates the "card title" (and also elapsed time widget) based on
* the current state of the call.
*/
// TODO: it's confusing for updateCardTitleWidgets() and
// getTitleForCallCard() to be separate methods, since they both
// just list out the exact same "phone state" cases.
// Let's merge the getTitleForCallCard() logic into here.
private void updateCardTitleWidgets(Phone phone, Call call) {
// if (DBG) log("updateCardTitleWidgets(call " + call + ")...");
Call.State state = call.getState();
// TODO: Still need clearer spec on exactly how title *and* status get
// set in all states. (Then, given that info, refactor the code
// here to be more clear about exactly which widgets on the card
// need to be set.)
String cardTitle;
int phoneType = mApplication.phone.getPhoneType();
cardTitle = getTitleForCallCard(call);
// if (DBG) log("updateCardTitleWidgets: " + cardTitle);
// Update the title and elapsed time widgets based on the current call state.
switch (state) {
case ACTIVE:
case DISCONNECTING:
final boolean bluetoothActive = mApplication.showBluetoothIndication();
int ongoingCallIcon = bluetoothActive ? R.drawable.ic_incall_ongoing_bluetooth
: R.drawable.ic_incall_ongoing;
int connectedTextColor = bluetoothActive
? mTextColorConnectedBluetooth : mTextColorConnected;
// While in the DISCONNECTING state we display a
// "Hanging up" message in order to make the UI feel more
// responsive. (In GSM it's normal to see a delay of a
// couple of seconds while negotiating the disconnect with
// the network, so the "Hanging up" state at least lets
// the user know that we're doing something.)
// TODO: consider displaying the "Hanging up" state for
// CDMA also if the latency there ever gets high enough.
if (state == Call.State.DISCONNECTING) {
// Display the brief "Hanging up" indication.
setUpperTitle(cardTitle, mTextColorDefaultPrimary, state);
} else { // state == Call.State.ACTIVE
// Normal "ongoing call" state; don't use any "title" at all.
//clearUpperTitle();
setUpperTitle(cardTitle, mTextColorConnected, state);
}
// Use the elapsed time widget to show the current call duration.
mElapsedTime.setTextColor(connectedTextColor);
// Also see onTickForCallTimeElapsed(), which updates this
// widget once per second while the call is active.
break;
case DISCONNECTED:
// Display "Call ended" (or possibly some error indication;
// see getCallFailedString()) in the upper title, in red.
// TODO: display a "call ended" icon somewhere, like the old
// R.drawable.ic_incall_end?
setUpperTitle(cardTitle, mTextColorEnded, state);
// In the "Call ended" state, leave the mElapsedTime widget
// visible, but don't touch it (so we continue to see the elapsed time of
// the call that just ended.)
mElapsedTime.setVisibility(View.VISIBLE);
mElapsedTime.setTextColor(mTextColorEnded);
break;
case HOLDING:
// For a single call on hold, display the title "On hold" in
// orange.
// (But since the upper title overlaps the label of the
// Hold/Unhold button, we actually use the elapsedTime widget
// to display the title in this case.)
// TODO: display an "On hold" icon somewhere, like the old
// R.drawable.ic_incall_onhold?
clearUpperTitle();
mElapsedTime.setText(cardTitle);
// While on hold, the elapsed time widget displays an
// "on hold" indication rather than an amount of time.
mElapsedTime.setVisibility(View.VISIBLE);
mElapsedTime.setTextColor(mTextColorOnHold);
break;
default:
// All other states (DIALING, INCOMING, etc.) use the "upper title":
setUpperTitle(cardTitle, mTextColorDefaultPrimary, state);
// ...and we don't show the elapsed time.
mElapsedTime.setVisibility(View.INVISIBLE);
break;
}
}
/**
* Updates mElapsedTime based on the specified number of seconds.
* A timeElapsed value of zero means to not show an elapsed time at all.
*/
private void updateElapsedTimeWidget(long timeElapsed) {
// if (DBG) log("updateElapsedTimeWidget: " + timeElapsed);
if (DBG) log("updateElapsedTimeWidget: timeElapsed: " + timeElapsed + ", mMMRingDuring: " + mMMRingDuring);
if (mWaitCD || (timeElapsed == 0)) {
mElapsedTime.setText("");
} else {
mElapsedTime.setText(getString(R.string.video_label_time) + DateUtils.formatElapsedTime(timeElapsed - mMMRingDuring/1000));
}
}
/**
* Returns the "card title" displayed at the top of a foreground
* ("active") CallCard to indicate the current state of this call, like
* "Dialing" or "In call" or "On hold". A null return value means that
* there's no title string for this state.
*/
private String getTitleForCallCard(Call call) {
String retVal = null;
Call.State state = call.getState();
Context context = mPhone.getContext();
int resId;
if (DBG) log("- getTitleForCallCard(Call " + call + ")...");
switch (state) {
case IDLE:
break;
case ACTIVE:
// Title is "Call in progress". (Note this appears in the
// "lower title" area of the CallCard.)
int phoneType = mApplication.phone.getPhoneType();
retVal = context.getString(R.string.card_title_in_progress);
break;
case HOLDING:
retVal = context.getString(R.string.card_title_on_hold);
// TODO: if this is a conference call on hold,
// maybe have a special title here too?
break;
case DIALING:
retVal = context.getString(R.string.card_title_dialing_video);
break;
case ALERTING:
retVal = context.getString(R.string.card_title_alerting_video);
break;
case INCOMING:
case WAITING:
retVal = context.getString(R.string.card_title_incoming_video_call);
break;
case DISCONNECTING:
retVal = context.getString(R.string.card_title_hanging_up);
break;
case DISCONNECTED:
retVal = getCallFailedString(call);
break;
}
if (DBG) log(" ==> result: " + retVal);
return retVal;
}
/**
* Updates the name / photo / number / label fields on the CallCard
* based on the specified CallerInfo.
*
* If the current call is a conference call, use
* updateDisplayForConference() instead.
*/
private void updateDisplayForPerson(CallerInfo info,
int presentation,
boolean isTemporary,
Call call) {
//if (DBG) log("updateDisplayForPerson(" + info + ")\npresentation:" +
//presentation + " isTemporary:" + isTemporary);
// inform the state machine that we are displaying a photo.
mPhotoTracker.setPhotoRequest(info);
mPhotoTracker.setPhotoState(ContactsAsyncHelper.ImageTracker.DISPLAY_IMAGE);
String name;
String displayNumber = null;
String label = null;
Uri personUri = null;
String socialStatusText = null;
Drawable socialStatusBadge = null;
if (info != null) {
// It appears that there is a small change in behaviour with the
// PhoneUtils' startGetCallerInfo whereby if we query with an
// empty number, we will get a valid CallerInfo object, but with
// fields that are all null, and the isTemporary boolean input
// parameter as true.
// In the past, we would see a NULL callerinfo object, but this
// ends up causing null pointer exceptions elsewhere down the
// line in other cases, so we need to make this fix instead. It
// appears that this was the ONLY call to PhoneUtils
// .getCallerInfo() that relied on a NULL CallerInfo to indicate
// an unknown contact.
if (TextUtils.isEmpty(info.name)) {
if (TextUtils.isEmpty(info.phoneNumber)) {
name = getPresentationString(presentation);
} else if (presentation != Connection.PRESENTATION_ALLOWED) {
// This case should never happen since the network should never send a phone #
// AND a restricted presentation. However we leave it here in case of weird
// network behavior
name = getPresentationString(presentation);
} else if (!TextUtils.isEmpty(info.cnapName)) {
name = info.cnapName;
info.name = info.cnapName;
displayNumber = info.phoneNumber;
} else {
name = info.phoneNumber;
}
} else {
if (presentation != Connection.PRESENTATION_ALLOWED) {
// This case should never happen since the network should never send a name
// AND a restricted presentation. However we leave it here in case of weird
// network behavior
name = getPresentationString(presentation);
} else {
name = info.name;
displayNumber = info.phoneNumber;
label = info.phoneLabel;
}
}
personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, info.person_id);
} else {
name = getPresentationString(presentation);
}
if (call.isGeneric()) {
mName.setText(R.string.card_title_in_call);
} else {
mName.setText(name);
}
mName.setVisibility(View.VISIBLE);
/*
// Update mPhoto
// if the temporary flag is set, we know we'll be getting another call after
// the CallerInfo has been correctly updated. So, we can skip the image
// loading until then.
// If the photoResource is filled in for the CallerInfo, (like with the
// Emergency Number case), then we can just set the photo image without
// requesting for an image load. Please refer to CallerInfoAsyncQuery.java
// for cases where CallerInfo.photoResource may be set. We can also avoid
// the image load step if the image data is cached.
if (isTemporary && (info == null || !info.isCachedPhotoCurrent)) {
mPhoto.setVisibility(View.INVISIBLE);
} else if (info != null && info.photoResource != 0){
showImage(mPhoto, info.photoResource);
} else if (!showCachedImage(mPhoto, info)) {
// Load the image with a callback to update the image state.
// Use the default unknown picture while the query is running.
ContactsAsyncHelper.updateImageViewWithContactPhotoAsync(
info, 0, this, call, getContext(), mPhoto, personUri, R.drawable.picture_unknown);
}
// And no matter what, on all devices, we never see the "manage
// conference" button in this state.
mManageConferencePhotoButton.setVisibility(View.INVISIBLE);*/
if (displayNumber != null && !call.isGeneric()) {
mPhoneNumber.setText(displayNumber);
mPhoneNumber.setTextColor(mTextColorDefaultSecondary);
mPhoneNumber.setVisibility(View.VISIBLE);
} else {
mPhoneNumber.setVisibility(View.GONE);
}
if (label != null && !call.isGeneric()) {
//mLabel.setText(label);
//mLabel.setVisibility(View.VISIBLE);
} else {
//mLabel.setVisibility(View.GONE);
}
// "Social status": currently unused.
// Note socialStatus is *only* visible while an incoming
// call is ringing, never in any other call state.
/*if ((socialStatusText != null) && call.isRinging() && !call.isGeneric()) {
mSocialStatus.setVisibility(View.VISIBLE);
mSocialStatus.setText(socialStatusText);
mSocialStatus.setCompoundDrawablesWithIntrinsicBounds(
socialStatusBadge, null, null, null);
mSocialStatus.setCompoundDrawablePadding((int) (mDensity * 6));
} else {
mSocialStatus.setVisibility(View.GONE);
}*/
}
/**
* Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
* refreshes the CallCard data when it called.
*/
public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
if (DBG) log("onQueryComplete: token " + token + ", cookie " + cookie + ", ci " + ci);
if (cookie instanceof Call) {
// grab the call object and update the display for an individual call,
// as well as the successive call to update image via call state.
// If the object is a textview instead, we update it as we need to.
if (DBG) log("callerinfo query complete, updating ui from displayMainCallStatus()");
Call call = (Call) cookie;
Connection conn = null;
conn = call.getEarliestConnection();
PhoneUtils.CallerInfoToken cit =
PhoneUtils.startGetCallerInfo(mPhone.getContext(), conn, this, null);
int presentation = Connection.PRESENTATION_ALLOWED;
if (conn != null) presentation = conn.getNumberPresentation();
if (DBG) log("- onQueryComplete: presentation=" + presentation
+ ", contactExists=" + ci.contactExists);
// Depending on whether there was a contact match or not, we want to pass in different
// CallerInfo (for CNAP). Therefore if ci.contactExists then use the ci passed in.
// Otherwise, regenerate the CIT from the Connection and use the CallerInfo from there.
if (ci.contactExists) {
updateDisplayForPerson(ci, Connection.PRESENTATION_ALLOWED, false, call);
} else {
updateDisplayForPerson(cit.currentInfo, presentation, false, call);
}
} else if (cookie instanceof TextView){
if (DBG) log("callerinfo query complete, updating ui from ongoing or onhold");
((TextView) cookie).setText(PhoneUtils.getCompactNameFromCallerInfo(ci, mPhone.getContext()));
}
}
public void onTickForCallTimeElapsed(long timeElapsed) {
// While a call is in progress, update the elapsed time shown
// onscreen.
log("onTickForCallTimeElapsed(), timeElapsed: " + timeElapsed);
updateElapsedTimeWidget(timeElapsed);
}
/**
* Sets the CallCard "upper title". Also, depending on the passed-in
* Call state, possibly display an icon along with the title.
*/
private void setUpperTitle(String title, int color, Call.State state) {
mUpperTitle.setText(title);
mUpperTitle.setTextColor(color);
int bluetoothIconId = 0;
if (!TextUtils.isEmpty(title)
&& ((state == Call.State.INCOMING) || (state == Call.State.WAITING))
&& mApplication.showBluetoothIndication()) {
// Display the special bluetooth icon also, if this is an incoming
// call and the audio will be routed to bluetooth.
bluetoothIconId = R.drawable.ic_incoming_call_bluetooth;
}
mUpperTitle.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0);
if (bluetoothIconId != 0) mUpperTitle.setCompoundDrawablePadding((int) (mDensity * 5));
}
/**
* Clears the CallCard "upper title", for states (like a normal
* ongoing call) where we don't use any "title" at all.
*/
private void clearUpperTitle() {
setUpperTitle("", 0, Call.State.IDLE); // Use dummy values for "color" and "state"
}
private String getCallFailedString(Call call) {
Connection c = call.getEarliestConnection();
int resID;
if (c == null) {
if (DBG) log("getCallFailedString: connection is null, using default values.");
// if this connection is null, just assume that the
// default case occurs.
resID = R.string.card_title_call_ended;
} else {
Connection.DisconnectCause cause = c.getDisconnectCause();
// TODO: The card *title* should probably be "Call ended" in all
// cases, but if the DisconnectCause was an error condition we should
// probably also display the specific failure reason somewhere...
switch (cause) {
case BUSY:
resID = R.string.callFailed_userBusy;
break;
case CONGESTION:
resID = R.string.callFailed_congestion;
break;
case LOST_SIGNAL:
case CDMA_DROP:
resID = R.string.callFailed_noSignal;
break;
case LIMIT_EXCEEDED:
resID = R.string.callFailed_limitExceeded;
break;
case POWER_OFF:
resID = R.string.callFailed_powerOff;
break;
case ICC_ERROR:
resID = R.string.callFailed_simError;
break;
case OUT_OF_SERVICE:
resID = R.string.callFailed_outOfService;
break;
default:
resID = R.string.card_title_call_ended;
break;
}
}
return mPhone.getContext().getString(resID);
}
private String getPresentationString(int presentation) {
String name = mPhone.getContext().getString(R.string.unknown);
if (presentation == Connection.PRESENTATION_RESTRICTED) {
name = mPhone.getContext().getString(R.string.private_num);
} else if (presentation == Connection.PRESENTATION_PAYPHONE) {
name = mPhone.getContext().getString(R.string.payphone);
}
return name;
}
/**
* Brings up UI to handle the various error conditions that
* can occur when first initializing the in-call UI.
* This is called from onResume() if we encountered
* an error while processing our initial Intent.
*
* @param status one of the InCallInitStatus error codes.
* @return true if need reset the InCallInitStatus, false if needn't
*/
private boolean handleStartupError(InCallInitStatus status) {
if (DBG) log("handleStartupError(): status = " + status);
// NOTE that the regular Phone UI is in an uninitialized state at
// this point, so we don't ever want the user to see it.
// That means:
// - Any cases here that need to go to some other activity should
// call startActivity() AND immediately call endInCallScreenSession
// on this one.
// - Any cases here that bring up a Dialog must ensure that the
// Dialog handles both OK *and* cancel by calling endInCallScreenSession.
// Activity. (See showGenericErrorDialog() for an example.)
switch (status) {
case POWER_OFF:
// Radio is explictly powered off.
// TODO: This UI is ultra-simple for 1.0. It would be nicer
// to bring up a Dialog instead with the option "turn on radio
// now". If selected, we'd turn the radio on, wait for
// network registration to complete, and then make the call.
showGenericErrorDialog(R.string.incall_error_power_off, true);
break;
case EMERGENCY_ONLY:
// Only emergency numbers are allowed, but we tried to dial
// a non-emergency number.
// (This state is currently unused; see comments above.)
showGenericErrorDialog(R.string.incall_error_emergency_only, true);
break;
case OUT_OF_SERVICE:
// No network connection.
showGenericErrorDialog(R.string.incall_error_out_of_service, true);
break;
case PHONE_NOT_IN_USE:
// This error is handled directly in onResume() (by bailing
// out of the activity.) We should never see it here.
Log.w(LOG_TAG,
"handleStartupError: unexpected PHONE_NOT_IN_USE status");
break;
case NO_PHONE_NUMBER_SUPPLIED:
// The supplied Intent didn't contain a valid phone number.
// TODO: Need UI spec for this failure case; for now just
// show a generic error.
showGenericErrorDialog(R.string.incall_error_no_phone_number_supplied, true);
break;
case CALL_FAILED:
// We couldn't successfully place the call; there was some
// failure in the telephony layer.
// TODO: Need UI spec for this failure case; for now just
// show a generic error.
showGenericErrorDialog(R.string.incall_error_call_failed, true);
break;
case NO_IN_3G:
handleFallBack(DEFAULT_FALLBACK,-1);
break;
case ALREADY_IN_3GCALL:
Toast txtToast = Toast.makeText(this, R.string.incall_error_dialvt_in_3gcall, Toast.LENGTH_LONG);
txtToast.show();
break;
case ALREADY_IN_2GCALL:
showGenericErrorDialog(R.string.incall_error_dialvt_in_2gcall, true);
return false;
//break;
case CALL_FAILED_FDN_ONLY:
showGenericErrorDialog(R.string.callFailed_fdn_only, true);
break;
case AIRPLANE_MODE_ON:
showGenericErrorDialog(R.string.incall_error_aireplane_mode_on, true);
break;
default:
Log.w(LOG_TAG, "handleStartupError: unexpected status code " + status);
showGenericErrorDialog(R.string.incall_error_call_failed, true);
break;
}
return true;
}
/**
* Utility function to bring up a generic "error" dialog, and then bail
* out of the in-call UI when the user hits OK (or the BACK button.)
*/
private void showGenericErrorDialog(int resid, boolean isStartupError) {
CharSequence msg = getResources().getText(resid);
if (DBG) log("showGenericErrorDialog('" + msg + "')...");
// create the clicklistener and cancel listener as needed.
DialogInterface.OnClickListener clickListener;
OnCancelListener cancelListener;
if (isStartupError) {
clickListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
bailOutAfterErrorDialog();
}};
cancelListener = new OnCancelListener() {
public void onCancel(DialogInterface dialog) {
bailOutAfterErrorDialog();
}};
} else {
clickListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
delayedCleanupAfterDisconnect();
}};
cancelListener = new OnCancelListener() {
public void onCancel(DialogInterface dialog) {
delayedCleanupAfterDisconnect();
}};
}
// TODO: Consider adding a setTitle() call here (with some generic
// "failure" title?)
mGenericErrorDialog = new AlertDialog.Builder(this)
.setMessage(msg)
.setPositiveButton(R.string.ok, clickListener)
.setOnCancelListener(cancelListener)
.create();
// When the dialog is up, completely hide the in-call UI
// underneath (which is in a partially-constructed state).
mGenericErrorDialog.getWindow().addFlags(
WindowManager.LayoutParams.FLAG_DIM_BEHIND);
mGenericErrorDialog.show();
}
private void bailOutAfterErrorDialog() {
if (mGenericErrorDialog != null) {
if (DBG) log("bailOutAfterErrorDialog: DISMISSING mGenericErrorDialog.");
mGenericErrorDialog.dismiss();
mGenericErrorDialog = null;
}
if (DBG) log("bailOutAfterErrorDialog(): end InCallScreen session...");
endInCallScreenSession();
}
/**
* Dismisses (and nulls out) all persistent Dialogs managed
* by the InCallScreen. Useful if (a) we're about to bring up
* a dialog and want to pre-empt any currently visible dialogs,
* or (b) as a cleanup step when the Activity is going away.
*/
private void dismissAllDialogs() {
if (DBG) log("dismissAllDialogs()...");
// Note it's safe to dismiss() a dialog that's already dismissed.
// (Even if the AlertDialog object(s) below are still around, it's
// possible that the actual dialog(s) may have already been
// dismissed by the user.)
if (mGenericErrorDialog != null) {
if (VDBG) log("- DISMISSING mGenericErrorDialog.");
mGenericErrorDialog.dismiss();
mGenericErrorDialog = null;
}
if (mWaitPromptDialog != null) {
if (VDBG) log("- DISMISSING mWaitPromptDialog.");
mWaitPromptDialog.dismiss();
mWaitPromptDialog = null;
}
if (mWildPromptDialog != null) {
if (VDBG) log("- DISMISSING mWildPromptDialog.");
mWildPromptDialog.dismiss();
mWildPromptDialog = null;
}
if (mCallLostDialog != null) {
if (VDBG) log("- DISMISSING mCallLostDialog.");
mCallLostDialog.dismiss();
mCallLostDialog = null;
}
if (mPausePromptDialog != null) {
if (DBG) log("- DISMISSING mPausePromptDialog.");
mPausePromptDialog.dismiss();
mPausePromptDialog = null;
}
if (mFallBackDialog != null) {
if (DBG) log("- DISMISSING mFallBackDialog.");
mFallBackDialog.dismiss();
mFallBackDialog = null;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.invideocallscreen_option, menu);
mOptionMenu = menu;
mCameraSwitch_item = menu.findItem(R.id.video_camera_switch);
mViewSwitch_item = menu.findItem(R.id.video_view_switch);
mCamera_item = menu.findItem(R.id.video_camera);
mSpeaker_item = menu.findItem(R.id.video_speaker);
mBluetooth_item = menu.findItem(R.id.video_bluetooth);
mMute_item = menu.findItem(R.id.video_mute);
mCamera_brightness_item = menu.findItem(R.id.camera_brightness);
mCamera_contrast_item = menu.findItem(R.id.camera_contrast);
mStartrecord_item = menu.findItem(R.id.video_record);
mStoprecord_item = menu.findItem(R.id.video_stop_record);
if (VDBG) log("onPrepareOptionsMenu: MEDIA_MOUNTED: " + (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)));
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
mbExternalStorageAvail = true;
}
updateOptionMenu();
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
updateAudioMenu();
if (mPhone.getState() == Phone.State.RINGING) {
if (mOptionMenu != null) {
mOptionMenu.setGroupVisible(R.id.grp_incall, false);
}
} else if (mPhone.getState() == Phone.State.OFFHOOK) {
if (mOptionMenu != null) {
mOptionMenu.setGroupVisible(R.id.grp_incoming, false);
mOptionMenu.setGroupVisible(R.id.grp_incall, true);
}
boolean isCodecStart = (mp.getCodecState() == MediaPhone.CodecState.CODEC_START);
if (mStartrecord_item != null) {
mStartrecord_item.setEnabled(isCodecStart && mbExternalStorageAvail && !mbEnableRecord);
mStartrecord_item.setVisible(!mbEnableRecord);
}
if (mStoprecord_item != null) {
mStoprecord_item.setEnabled(isCodecStart && mbEnableRecord);
mStoprecord_item.setVisible(mbEnableRecord);
}
} else {
if (mOptionMenu != null) {
mOptionMenu.setGroupVisible(R.id.grp_incoming, false);
}
}
return true;
}
private void updateOptionMenu() {
log("updateOptionMenu");
if (mPhone.getState() == Phone.State.OFFHOOK) {
if (mOptionMenu != null) {
mOptionMenu.setGroupVisible(R.id.grp_incoming, false);
mOptionMenu.setGroupVisible(R.id.grp_incall, true);
}
boolean isCodecStart = (mp.getCodecState() == MediaPhone.CodecState.CODEC_START);
if (mCamera_item != null) {
mCamera_item.setTitle(mbEnableCamera?R.string.video_camera_close:R.string.video_camera_open);
mCamera_item.setEnabled(isCodecStart);
}
if (mViewSwitch_item != null) {
mViewSwitch_item.setEnabled(isCodecStart);
}
if (mCameraSwitch_item != null) {
mCameraSwitch_item.setEnabled(mbEnableCamera&&isCodecStart);
}
if (mCamera_brightness_item != null) {
mCamera_brightness_item.setEnabled(mbEnableCamera&&isCodecStart);
}
if (mCamera_contrast_item != null) {
mCamera_contrast_item.setEnabled(mbEnableCamera&&isCodecStart);
}
if (mStartrecord_item != null) {
mStartrecord_item.setEnabled(isCodecStart && mbExternalStorageAvail && !mbEnableRecord);
mStartrecord_item.setVisible(!mbEnableRecord);
}
if (mStoprecord_item != null) {
mStoprecord_item.setEnabled(isCodecStart && mbEnableRecord);
mStoprecord_item.setVisible(mbEnableRecord);
}
updateAudioMenu();
}
}
private void updateAudioMenu() {
boolean isCodecStart = (mp.getCodecState() == MediaPhone.CodecState.CODEC_START);
if (mSpeaker_item != null) {
mSpeaker_item.setTitle(PhoneUtils.isSpeakerOn(this)?R.string.video_speaker_close:R.string.video_speaker_open);
mSpeaker_item.setEnabled(isCodecStart);
}
if (mMute_item != null) {
mMute_item.setTitle(PhoneUtils.getMute()?R.string.video_unmute:R.string.video_mute);
mMute_item.setEnabled(isCodecStart);
}
if (mBluetooth_item != null) {
boolean bBtAvail = isBluetoothAvailable();
boolean bBtConnected = bBtAvail&&isBluetoothAudioConnected();
mBluetooth_item.setTitle(bBtConnected?R.string.video_bluetooth_close:R.string.video_bluetooth_open);
mBluetooth_item.setEnabled(bBtAvail&&isCodecStart);
}
}
/**
* Returns true whenever any one of the options from the menu is selected.
* Code changes to support dialpad options
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
boolean dismissMenuImmediate = true;
switch (item.getItemId()) {
case R.id.video_falldown:
internalFallBack();
break;
case R.id.video_camera:
onCameraCloseClick();
break;
case R.id.video_camera_switch:
onCameraSwitchClick();
break;
case R.id.video_view_switch:
onViewSwitchClick();
break;
case R.id.video_speaker:
if (VDBG) log("onClick: Speaker...");
onSpeakerClick();
dismissMenuImmediate = false;
break;
case R.id.video_bluetooth:
if (VDBG) log("onClick: Bluetooth...");
onBluetoothClick();
dismissMenuImmediate = false;
break;
case R.id.video_mute:
if (VDBG) log("onClick: Mute...");
onMuteClick();
dismissMenuImmediate = false;
break;
case R.id.brightness:
updateAdjustMenu(R.id.brightness_adjustmenu);
break;
case R.id.camera_brightness:
updateAdjustMenu(R.id.camera_brightness_adjustmenu);
break;
case R.id.camera_contrast:
updateAdjustMenu(R.id.camera_contrast_adjustmenu);
break;
case R.id.video_start_record_both:
enableRecord(true, 0);
break;
case R.id.video_start_record_audio:
enableRecord(true, 1);
break;
case R.id.video_stop_record:
enableRecord(false, 0);
break;
}
return true;
}
/* package */ void requestUpdateMuteIndication() {
if (VDBG) log("requestUpdateMuteIndication()...");
// No need to look at the current state here; any UI elements that
// care about the bluetooth state (i.e. the CallCard) get
// the necessary state directly from PhoneApp.showBluetoothIndication().
mHandler.removeMessages(REQUEST_UPDATE_MUTE_INDICATION);
mHandler.sendEmptyMessage(REQUEST_UPDATE_MUTE_INDICATION);
}
private void getSysConfig(){
mSysBrightness = getBrightness();
}
private void setSysConfig(){
setBrightness(mSysBrightness);
}
private void setMyConfig(){
setBrightness(mMyBrightness);
}
private int getBrightness(){
int brightness = 0;
try {
brightness = Settings.System.getInt(mPhone.getContext().getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS);
if (VDBG) log("getBrightness(), brightness: " + brightness);
} catch (SettingNotFoundException snfe) {
brightness = MAXIMUM_BACKLIGHT;
}
return brightness;
}
private void setBrightness(int brightness) {
try {
IPowerManager power = IPowerManager.Stub.asInterface(
ServiceManager.getService("power"));
if (power != null) {
if (VDBG) log("setBrightness(), brightness" + brightness);
power.setBacklightBrightness(brightness);
}
} catch (RemoteException doe) {
}
}
private int getCameraBrightness(){
/*mParameters = mCamera.getParameters();
int brightness = Integer.parseInt(mParameters.get("brightness"));*/
int brightness = mp.getCameraParam("brightness");
if (VDBG) log("getCameraBrightness(), brightness: " + brightness);
return brightness;
}
private void setCameraBrightness(int brightness) {
mp.setCameraParam("brightness", brightness);
}
private int getCameraContrast(){
/*mParameters = mCamera.getParameters();
int contrast = Integer.parseInt(mParameters.get("contrast"));*/
int contrast = mp.getCameraParam("contrast");
if (VDBG) log("getCameraContrast(), contrast: " + contrast);
return contrast;
}
private void setCameraContrast(int contrast) {
mp.setCameraParam("contrast", contrast);
}
private void updateAdjustMenu(int id){
mBrightnessAdjustMenu.setVisibility(View.GONE);
mCameraBrightnessAdjustMenu.setVisibility(View.GONE);
mCameraContrastAdjustMenu.setVisibility(View.GONE);
if (id == R.id.brightness_adjustmenu){
//mMyBrightness = getBrightness();
mBrightnessAdjustMenu.setProgress(mMyBrightness);
mBrightnessAdjustMenu.setVisibility(View.VISIBLE);
} else if(id == R.id.camera_brightness_adjustmenu) {
mMyCameraBrightness = getCameraBrightness();
mCameraBrightnessAdjustMenu.setProgress(mMyCameraBrightness);
mCameraBrightnessAdjustMenu.setVisibility(View.VISIBLE);
} else if(id == R.id.camera_contrast_adjustmenu) {
mMyCameraContrast = getCameraContrast();
mCameraContrastAdjustMenu.setProgress(mMyCameraContrast);
mCameraContrastAdjustMenu.setVisibility(View.VISIBLE);
}
}
public void onProgressChanged(AdjustMenuView adjustMenu, float progress, int rank, boolean fromUser)
{
if (VDBG) log("onProgressChanged(), progress: " + progress + ", rank: " + rank);
if (adjustMenu == mBrightnessAdjustMenu) {
if (VDBG) log("onProgressChanged(), Brightness");
adjustMenu.setIndication(Integer.toString(rank));
setBrightness((int)progress);
mMyBrightness = (int)progress;
} else if (adjustMenu == mCameraBrightnessAdjustMenu) {
if (VDBG) log("onProgressChanged(), Camera Brightness");
adjustMenu.setIndication(Integer.toString(rank));
setCameraBrightness((int)progress);
mMyCameraBrightness = (int)progress;
} else if (adjustMenu == mCameraContrastAdjustMenu) {
if (VDBG) log("onProgressChanged(), Contrast");
adjustMenu.setIndication(Integer.toString(rank));
setCameraContrast((int)progress);
mMyCameraContrast = (int)progress;
}
}
private void onMuteClick() {
if (VDBG) log("onMuteClick()...");
//TS for compile
boolean newMuteState = !PhoneUtils.getMute();
PhoneUtils.setMute(newMuteState);
mp.controlLocalAudio(!newMuteState, null);
updateAudioMenu();
}
private void onSpeakerClick() {
if (VDBG) log("onSpeakerClick()...");
// TODO: Turning on the speaker seems to enable the mic
// whether or not the "mute" feature is active!
// Not sure if this is an feature of the telephony API
// that I need to handle specially, or just a bug.
boolean newSpeakerState = !PhoneUtils.isSpeakerOn(this);
if (newSpeakerState && isBluetoothAvailable() && isBluetoothAudioConnected()) {
disconnectBluetoothAudio();
}
PhoneUtils.turnOnSpeaker(this, newSpeakerState, true);
updateAudioMenu();
}
private void onBluetoothClick() {
if (VDBG) log("onBluetoothClick()...");
if (isBluetoothAvailable()) {
// Toggle the bluetooth audio connection state:
if (isBluetoothAudioConnected()) {
disconnectBluetoothAudio();
} else {
// Manually turn the speaker phone off, instead of allowing the
// Bluetooth audio routing handle it. This ensures that the rest
// of the speakerphone code is executed, and reciprocates the
// menuSpeaker code above in onClick(). The onClick() code
// disconnects the active bluetooth headsets when the
// speakerphone is turned on.
if (PhoneUtils.isSpeakerOn(this)) {
PhoneUtils.turnOnSpeaker(this, false, true);
}
connectBluetoothAudio();
}
updateAudioMenu();
} else {
// Bluetooth isn't available; the "Audio" button shouldn't have
// been enabled in the first place!
Log.w(LOG_TAG, "Got onBluetoothClick, but bluetooth is unavailable");
}
}
private void onViewSwitchClick() {
if (VDBG) log("onViewSwitchClick()...");
if (ENABLE_MEDIAPHONE){
if (!mp.lockCamera()) {
return;
}
}
if (mScreenLayoutConfig == ScreenLayoutConfig.LOCAL_LARGE_REMOTE_SMALL){
mScreenLayoutConfig = ScreenLayoutConfig.LOCAL_SMALL_REMOTE_LARGE;
} else{
mScreenLayoutConfig = ScreenLayoutConfig.LOCAL_LARGE_REMOTE_SMALL;
}
updateViewConfig();
if (ENABLE_MEDIAPHONE){
mp.stopDownLink();
}
if (ENABLE_MEDIAPHONE){
mp.setLocalDisplay(mLocalVideoPhoneView.getHolder().getSurface());
mp.setRemoteDisplay(mRemoteVideoPhoneView.getHolder().getSurface());
mp.startDownLink();
mp.unlockCamera();
}
if (VDBG) log("onViewSwitchClick() end");
}
private void onCameraCloseClick() {
if (VDBG) log("onCameraCloseClick()...");
if (!checkCameraFlag()){
if(!setCameraFlag(true)) {
Log.e(LOG_TAG, "onCameraCloseClick(), setCameraFlag failed");
return;
}
}
if (ENABLE_MEDIAPHONE){
if ((mCamera == null)||(!mp.lockCamera())) {
return;
}
mp.stopUpLink();
}
// forcely reinitialize the camera info, in order to enalbe fake camera
Camera.CameraInfo info = new Camera.CameraInfo();
mCamera.getCameraInfo(2, info);
closeCamera();
if (mbEnableCamera) {
mRealCameraType = mCameraType;
mCameraType = CameraType.FAKE_CAMERA;
mp.setSubtitutePic(getSubstitutePic(true));
} else {
mCameraType = mRealCameraType;
}
createCamera();
mbEnableCamera = !mbEnableCamera;
if (ENABLE_MEDIAPHONE){
mp.setCamera(mCamera);
mp.startUpLink();
mp.controlLocalVideo(mbEnableCamera, true, null);
mp.unlockCamera();
}
updateOptionMenu();
}
private void onCameraSwitchClick() {
if (VDBG) log("onCameraSwitchClick()...");
if (ENABLE_MEDIAPHONE){
if ( (mCamera == null)||(!mp.lockCamera())) {
return;
}
}
if (mCameraType == CameraType.BOTTOM_CAMERA){
mCameraType = CameraType.FRONT_CAMERA;
}
else if (mCameraType == CameraType.FRONT_CAMERA){
mCameraType = CameraType.BOTTOM_CAMERA;
}
else {
log("onCameraSwitchClick(), mCameraType is illegal: " + mCameraType);
return;
}
if (ENABLE_MEDIAPHONE){
mp.stopUpLink();
closeCamera();
}
createCamera();
if (ENABLE_MEDIAPHONE){
mp.setCamera(mCamera);
mp.startUpLink();
mp.unlockCamera();
}
}
private String getRecordFileName(){
File base = null;
String root = Environment.getExternalStorageDirectory().getPath();
base = new File(root + DEFAULT_STORE_SUBDIR);
if (!base.isDirectory() && !base.mkdir()) {
Log.e(LOG_TAG, "Recording File aborted - can't create base directory " + base.getPath());
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat("'videocall'-yyyyMMddHHmmss");
String fn = sdf.format(new Date());
fn = base.getPath() + File.separator + fn + DEFAULT_RECORD_SUFFIX;
StatFs stat = null;
stat = new StatFs(base.getPath());
long available_size = stat.getBlockSize() * ((long)stat.getAvailableBlocks() - 4);
if (available_size < MINIMUM_START_FREE_SIZE){
Log.e(LOG_TAG, "Recording File aborted - not enough free space");
Toast txtToast = Toast.makeText(getWindow().getContext(), getString(R.string.storage_is_full), Toast.LENGTH_LONG);
txtToast.show();
return null;
}
File outFile = new File(fn);
try{
if (outFile.exists()){
outFile.delete();
}
boolean bRet = outFile.createNewFile();
if (!bRet) {
Log.e(LOG_TAG, "getRecordFileName, fn: " + fn + ", failed");
return null;
}
} catch (SecurityException e){
Log.e(LOG_TAG, "getRecordFileName, fn: " + fn + ", " + e);
return null;
} catch (IOException e){
Log.e(LOG_TAG, "getRecordFileName, fn: " + fn + ", " + e);
return null;
}
return outFile.getAbsolutePath();
}
private boolean isSpaceEnough(){
StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
long available_size = stat.getBlockSize() * ((long)stat.getAvailableBlocks() - 4);
Log.e(LOG_TAG, "free space: " + available_size);
return (available_size > MINIMUM_FREE_SIZE);
}
private void enableRecord(boolean bEnable, int type){
if (bEnable == mbEnableRecord){
Log.e(LOG_TAG, "Error, enableRecord(" + bEnable + ", " + type + ") called when mbEnableRecord: " + mbEnableRecord);
return;
}
log("enableRecord() bEnable : " + bEnable + ", type: " + type);
if (bEnable) {
mRecordFile = getRecordFileName();
if (mRecordFile == null){
return;
} else{
log("enableRecord(), fn: " + mRecordFile);
}
}
mbEnableRecord = !mbEnableRecord;
if (ENABLE_MEDIAPHONE){
try{
mp.enableRecord(mbEnableRecord, type, mRecordFile);
} catch (Exception e) {
Log.e(LOG_TAG, "enableRecord failed, " + e);
return;
}
}
if (!bEnable) {
Toast.makeText(this, getString(R.string.prompt_record_finish) + mRecordFile, Toast.LENGTH_LONG).show();
mRecordFile = null;
}
if (mStartrecord_item != null) {
mStartrecord_item.setVisible(!mbEnableRecord);
}
if (mStoprecord_item != null) {
mStoprecord_item.setVisible(mbEnableRecord);
}
mHandler.removeMessages(CHECK_FREESPACE);
if (bEnable) {
mHandler.sendEmptyMessageDelayed(CHECK_FREESPACE, 10 * 1000);
}
}
private void getVideoPhoneConfig() {
if (VDBG) log("getVideoPhoneConfig()...");
ScreenLayoutConfig tempScreenLayout;
FallBackConfig tempFallBack;
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
String layout = mPrefs.getString(VideoPhoneSetting.KEY_SCREEN_LAYOUT, getString(R.string.videophone_setting_layout_defaultvalue));
String fallback = mPrefs.getString(VideoPhoneSetting.KEY_FALLBACK, getString(R.string.videophone_setting_fallback_defaultvalue));
mbShowSubstitutePic = mPrefs.getBoolean(VideoPhoneSetting.KEY_STATIC_IMAGE, true);
mLocalSubstitutePic = mPrefs.getString(VideoPhoneSetting.KEY_LOCAL_STATIC_IMAGE_PATH, VideoPhoneSetting.DEF_SUBSTITUTE_IMAGE_PATH);
mRemoteSubstitutePic = mPrefs.getString(VideoPhoneSetting.KEY_REMOTE_STATIC_IMAGE_PATH, VideoPhoneSetting.DEF_SUBSTITUTE_IMAGE_PATH);
/*if (VDBG)*/ log("getVideoPhoneConfig() layout : " + layout);
/*if (VDBG)*/ log("getVideoPhoneConfig() fallback : " + fallback);
/*if (VDBG)*/ log("getVideoPhoneConfig() subtitute : " + mbShowSubstitutePic + ", local: " + mLocalSubstitutePic + ", remote: " + mRemoteSubstitutePic);
if (layout.equals("1")){
tempScreenLayout = ScreenLayoutConfig.LOCAL_SMALL_REMOTE_LARGE;
} else{
tempScreenLayout = ScreenLayoutConfig.LOCAL_LARGE_REMOTE_SMALL;
}
if (fallback.equals("1")){
tempFallBack = FallBackConfig.FALLBACK_ASK;
} else if (fallback.equals("2")){
tempFallBack = FallBackConfig.FALLBACK_ALWAYS;
} else {
tempFallBack = FallBackConfig.FALLBACK_NEVER;
}
mFallBackConfig = tempFallBack;
if (tempScreenLayout != mScreenLayoutConfig){
if (VDBG) log("getVideoPhoneConfig() layout change: " + layout);
mScreenLayoutConfig = tempScreenLayout;
updateViewConfig();
}
}
private void createCamera() {
if (mCamera == null) {
// If the activity is paused and resumed, camera device has been
// released and we need to open the camera.
mCamera = Camera.open(getCamerID());
{
Camera.Parameters params = mCamera.getParameters();
params.setSensorRotation(getSensorRotation());
params.set("sensororientation", 1);
params.set("videodatatype", "1");
mCamera.setParameters(params);
}
Log.d(LOG_TAG, "createCamera(), mCamera: " + mCamera);
mCamera.unlock();
}
}
private boolean lockCamera(long msec) {
try {
mCamera.lock();
} catch (Exception e) {
Log.e(LOG_TAG, "lockCamera() failed");
if (msec > 0) {
try {
Log.e(LOG_TAG, "lockCamera() wait: " + msec);
wait(msec);
} catch (Exception f) {
Log.e(LOG_TAG, "lockCamera() wait failed");
}
return lockCamera(0);
}
return false;
}
return true;
}
private void closeCamera() {
Log.v(LOG_TAG, "closeCamera");
mHandler.removeMessages(DELAYED_CREATE_CAMERA);
if (mCamera == null) {
Log.d(LOG_TAG, "already stopped.");
return;
}
mCamera.lock();
mCamera.release();
mCamera = null;
mPreviewing = false;
}
private void closeCamera(long msec) {
Log.v(LOG_TAG, "closeCamera, msec: " + msec);
mHandler.removeMessages(DELAYED_CREATE_CAMERA);
if (mCamera == null) {
Log.d(LOG_TAG, "already stopped.");
return;
}
if (lockCamera(msec)) {
mCamera.release();
mCamera = null;
mPreviewing = false;
}
}
private void startCameraPreview() {
if (mStartPreviewThread != null){
try {
mStartPreviewThread.join();
} catch (InterruptedException ex) {
log("mStartPreviewThread.quit() exception " + ex);
}
}
mStartPreviewThread = new Thread(new Runnable() {
public void run() {
Log.d(LOG_TAG, "mStartPreviewThread start. ");
try {
mStartPreviewFail = false;
mCamera = Camera.open(getCamerID());
synchronized(mCameraLock) {
Camera.Parameters params = mCamera.getParameters();
params.setSensorRotation(getSensorRotation());
params.set("sensororientation", 1);
params.set("videodatatype", "1");
params.setPreviewSize(176, 144);
params.setPreviewFrameRate(10);
mCamera.setParameters(params);
Log.d(LOG_TAG, "createCamera(), mCamera: " + mCamera);
mp.setCamera(mCamera);
if (mSurfaceReadyCount == 2) {
mCamera.setPreviewDisplay(mLocalVideoPhoneView.getHolder());
mCamera.startPreview();
}
mCamera.unlock();
}
mbEnableCamera = true;
mbInitialCamera = true;
mHandler.removeMessages(INITIALED_CAMERA);
mHandler.sendEmptyMessage(INITIALED_CAMERA);
}catch (Exception e) {
mStartPreviewFail = true;
Log.e(LOG_TAG, "mStartPreviewFail, " + e);
synchronized(mCameraLock) {
closeCamera();
}
mHandler.removeMessages(DELAYED_CREATE_CAMERA);
mHandler.sendEmptyMessageDelayed(DELAYED_CREATE_CAMERA, 500);
}
Log.d(LOG_TAG, "mStartPreviewThread end. ");
}
});
mStartPreviewThread.start();
}
public void onVideoSizeChanged(MediaPhone mp, int width, int height)
{
}
public boolean onError(MediaPhone mp, int what, Object extra)
{
return true;
}
public boolean onMediaInfo(MediaPhone mp, int what, Object extra)
{
return true;
}
public boolean onCallEvent(MediaPhone mp, int what, Object extra)
{
log("onCallEvent(), what: " + what);
switch(what){
case MediaPhone.MEDIA_CALLEVENT_CAMERAOPEN:
mHandler.removeMessages(MEDIAPHONE_CAMERA_OPEN);
mHandler.sendEmptyMessage(MEDIAPHONE_CAMERA_OPEN);
break;
case MediaPhone.MEDIA_CALLEVENT_CAMERACLOSE:
mHandler.removeMessages(MEDIAPHONE_CAMERA_CLOSE);
mHandler.sendEmptyMessage(MEDIAPHONE_CAMERA_CLOSE);
break;
case MediaPhone.MEDIA_CALLEVENT_STRING:
mHandler.removeMessages(MEDIAPHONE_STRING);
Message.obtain(mHandler, MEDIAPHONE_STRING, extra).sendToTarget();
break;
case MediaPhone.MEDIA_CALLEVENT_CODEC_START:
mHandler.removeMessages(MEDIAPHONE_CODEC_START);
mHandler.sendEmptyMessage(MEDIAPHONE_CODEC_START);
break;
case MediaPhone.MEDIA_CALLEVENT_CODEC_CLOSE:
mHandler.removeMessages(MEDIAPHONE_CODEC_CLOSE);
mHandler.sendEmptyMessage(MEDIAPHONE_CODEC_CLOSE);
break;
case MediaPhone.MEDIA_CALLEVENT_CODEC_OPEN:
if (InCallScreenMode.FALL_BACK == mInCallScreenMode) {
setInCallScreenMode(InCallScreenMode.UNDEFINED);
}
break;
case MediaPhone.MEDIA_CALLEVENT_CODEC_SET_PARAM_ENCODER:
if (mCamera != null) {
synchronized(mCameraLock) {
if (mCamera != null) {
try {
mCamera.lock();
if (mCamera.previewEnabled()) {
log("onCallEvent(), MEDIA_CALLEVENT_CODEC_SET_PARAM_ENCODER: true");
mCamera.unlock();
return true;
}
mCamera.unlock();
}catch (Exception e) {
Log.e(LOG_TAG, "onCallEvent, MEDIA_CALLEVENT_CODEC_SET_PARAM_ENCODER, camera exception " + e);
return true;
}
}
}
}
log("onCallEvent(), MEDIA_CALLEVENT_CODEC_SET_PARAM_ENCODER: false");
mHandler.removeMessages(MEDIAPHONE_CAMERA_FAIL);
mHandler.sendEmptyMessage(MEDIAPHONE_CAMERA_FAIL);
return false;
case MediaPhone.MEDIA_CALLEVENT_MEDIA_START:
if (mbMediaStarted) {
break;
}
mbMediaStarted = true;
mCallTime.reset();
mCallTime.periodicUpdateTimer();
mCCTime = SystemClock.elapsedRealtime();
if (mBeginTime.getText().length() <= 0) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
mBeginTime.setText(getString(R.string.video_label_begintime) + sdf.format(new Date()));
log("ACTIVE, mBeginTime: " + mBeginTime.getText());
}
mHandler.removeMessages(MEDIAPHONE_MEDIA_START);
mHandler.sendEmptyMessage(MEDIAPHONE_MEDIA_START);
break;
/* case MediaPhone.MEDIA_CALLEVENT_CODEC_OPEN:
// update timer field
//if (DBG) log("displayMainCallStatus: start periodicUpdateTimer");
mCallTime.reset();
mCallTime.periodicUpdateTimer();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
mBeginTime.setText(sdf.format(new Date()));
break;
case MediaPhone.MEDIA_CALLEVENT_CODEC_CLOSE:
// Stop getting timer ticks from this call
mCallTime.cancelTimer();
TextView lable_beginTime = (TextView) findViewById(R.id.label_begintime);
lable_beginTime.setVisibility(View.VISIBLE);
mBeginTime.setVisibility(View.VISIBLE);
break;*/
}
return true;
}
void handleFallBack(String number, int cause){
log("handleFallBack(), number: " + number + ", cause: " + cause + ", mbIncoming: " + mbIncoming);
if (mbIncoming || (number == null))
return;
if( (!number.equals(getCurrentNumber())) && (!number.equals(DEFAULT_FALLBACK)))
return;
if (mFallBackConfig == FallBackConfig.FALLBACK_ALWAYS){
if (cause == -1) {
showToast(getString(R.string.videophone_notin3g_always_fallback));
} else {
showToast(getString(R.string.videophone_always_fallback));
}
intenalFallBack();
} else if (mFallBackConfig == FallBackConfig.FALLBACK_NEVER){
if (cause == -1) {
showToast(getString(R.string.videophone_notin3g_never_fallback));
} else {
showToast(getString(R.string.videophone_never_fallback));
}
finish();
} else {
if (!mIsForegroundActivity) {
finish();
return;
}
setInCallScreenMode(InCallScreenMode.FALL_BACK);
showFallBackMenu(cause);
}
}
void handleVideoCallFail(String number, int cause){
log("handleVideoCallFail(), number: " + number + ", cause: " + cause + ", mbIncoming: " + mbIncoming
+ ", mbHangupByUser: " + mbHangupByUser);
if (mbIncoming || (number == null))
return;
if ((getCurrentNumber() != null) && (!number.equals(getCurrentNumber())))
return;
if (!mbHangupByUser) {
// 16&31&255 is normally ended
if ((cause != 16) && (cause != 31) && (cause != 255)){
Toast txtToast = Toast.makeText(getWindow().getContext(), causeToString(cause), Toast.LENGTH_LONG);
txtToast.show();
} else {
log("handleVideoCallFail(), mbEverConnected: " + mbEverConnected);
// if hasn't connected, also notify user
if (!mbEverConnected) {
Toast txtToast = Toast.makeText(getWindow().getContext(), causeToString(3), Toast.LENGTH_LONG);
txtToast.show();
}
}
}
// if h324 hasn't completely released last time(it also means connection is active now), need hangup call and notify user
if (cause == 1000) {
PhoneUtils.hangup(mCM);
} else {
onDisconnect();
}
}
String causeToString(int cause){
log("causeToString: " + cause);
switch(cause){
case 1000:
return getString(R.string.videophone_failcause_1000);
case 1:
case 22:
case 28:
return getString(R.string.videophone_failcause_1);
case 3:
case 6:
case 18:
case 21:
case 29:
case 38:
case 41:
case 43:
case 49:
case 81:
return getString(R.string.videophone_failcause_3);
case 8:
case 55:
return getString(R.string.videophone_failcause_8);
case 17:
return getString(R.string.videophone_failcause_17);
case 19:
return getString(R.string.videophone_failcause_19);
case 27:
return getString(R.string.videophone_failcause_27);
case 34:
case 42:
case 44:
return getString(R.string.videophone_failcause_34);
case 63:
return getString(R.string.videophone_failcause_63);
case 79:
return getString(R.string.videophone_failcause_79);
// fall back cause:
case 47:
return getString(R.string.videophone_failcause_47);
case 50:
case 57:
return getString(R.string.videophone_failcause_57);
case 58:
return getString(R.string.videophone_failcause_58);
case 88:
return getString(R.string.videophone_failcause_88);
case -1:
return getString(R.string.videophone_failcause_minus_1);
default:
return getString(R.string.videophone_failcause_default);
}
}
void showFallBackMenu(int cause){
final CharSequence[] items = getResources().getTextArray(R.array.videophone_fallback_menu);
mFallBackDialog = new Dialog(this);
mFallBackDialog.setTitle(getString(R.string.videophone_fallback_title));
mFallBackDialog.setContentView(R.xml.videophone_fallback);
TextView causeView = (TextView)mFallBackDialog.findViewById(R.id.FallBackCause);
causeView.setText(causeToString(cause));
mFallBackList = (ListView)mFallBackDialog.findViewById(R.id.FallBackList);
mFallBackList.setAdapter(new ArrayAdapter<CharSequence>(this,
android.R.layout.simple_list_item_1, items));
mFallBackList.setItemsCanFocus(false);
mFallBackList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
mFallBackList.setOnItemClickListener(this);
mFallBackDialog.setOnKeyListener(new OnKeyListener(){
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
finish();
return true;
case KeyEvent.KEYCODE_SEARCH:
log("KEYCODE_SEARCH");
return true;
}
}
return false;
}
});
mFallBackDialog.show();
mActiveDlg = mFallBackDialog;
}
private void internalRetryVideoCall(){
this.finish();
mLastAction = LastAction.ACTION_VIDEOCALL;
}
private void intenalFallBack(){
this.finish();
mLastAction = LastAction.ACTION_VOICECALL;
}
public void onItemClick(AdapterView parent, View v, int position, long id)
{
if (parent == (AdapterView)mFallBackList){
final CharSequence[] items = getResources().getTextArray(R.array.videophone_fallback_menu);
showToast(items[(int) id].toString());
if (mActiveDlg != null){
mActiveDlg.dismiss();
mActiveDlg = null;
}
// choose "video call"
if (0 == position){
internalRetryVideoCall();
/*if (okToRetryVideoCall()){
internalRetryVideoCall();
} else {
this.finish();
}*/
} else if (1 == position){ // choose "voice call"
intenalFallBack();
} else if (2 == position){
finish();
}
setInCallScreenMode(InCallScreenMode.UNDEFINED);
}
}
private void showToast(String str){
Toast txtToast = Toast.makeText(getWindow().getContext(), str, Toast.LENGTH_SHORT);
txtToast.show();
}
public boolean isTouchUiEnabled() {
return false;
}
private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
if (VDBG) log("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. We do so
// only if the okToDialDTMFTones() conditions pass.
if (okToDialDTMFTones()) {
return mDialer.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 onBackPressed() {
if (DBG) log("onBackPressed()...");
// To consume this BACK press, the code here should just do
// something and return. Otherwise, call super.onBackPressed() to
// get the default implementation (which simply finishes the
// current activity.)
if (!mRingingCall.isIdle()) {
// While an incoming call is ringing, BACK behaves just like
// ENDCALL: it stops the ringing and rejects the current call.
// (This is only enabled on some platforms, though.)
if (getResources().getBoolean(R.bool.allow_back_key_to_reject_incoming_call)) {
if (DBG) log("BACK key while ringing: reject the call");
internalHangupRingingCall();
// Don't consume the key; instead let the BACK event *also*
// get handled normally by the framework (which presumably
// will cause us to exit out of this activity.)
super.onBackPressed();
return;
} else {
// The BACK key is disabled; don't reject the call, but
// *do* consume the keypress (otherwise we'll exit out of
// this activity.)
if (DBG) log("BACK key while ringing: ignored");
return;
}
}
// BACK is also used to exit out of any "special modes" of the
// in-call UI:
if (mDialer.isOpened()) {
mDialer.closeDialer(true); // do the "closing" animation
return;
}
// Nothing special to do. Fall back to the default behavior.
//super.onBackPressed();
moveTaskToBack(true);
}
/**
* Determines when we can dial DTMF tones.
*/
private boolean okToDialDTMFTones() {
final boolean hasRingingCall = !mRingingCall.isIdle();
final Call.State fgCallState = mForegroundCall.getState();
// We're allowed to send DTMF tones when there's an ACTIVE
// foreground call, and not when an incoming call is ringing
// (since DTMF tones are useless in that state), or if the
// Manage Conference UI is visible (since the tab interferes
// with the "Back to call" button.)
// We can also dial while in ALERTING state because there are
// some connections that never update to an ACTIVE state (no
// indication from the network).
boolean canDial =
(fgCallState == Call.State.ACTIVE || fgCallState == Call.State.ALERTING)
&& !hasRingingCall;
if (VDBG) log ("[okToDialDTMFTones] foreground state: " + fgCallState +
", ringing state: " + hasRingingCall +
", result: " + canDial);
return canDial;
}
/**
* @return true if the in-call DTMF dialpad should be available to the
* user, given the current state of the phone and the in-call UI.
* (This is used to control the visibility of the dialer's
* onscreen handle, if applicable, and the enabledness of the "Show
* dialpad" onscreen button or menu item.)
*/
/* package */ boolean okToShowDialpad() {
// The dialpad is available only when it's OK to dial DTMF
// tones given the current state of the current call.
return okToDialDTMFTones();
}
/**
* Overriden to track relevant focus changes.
*
* If a key is down and some time later the focus changes, we may
* NOT recieve the keyup event; logically the keyup event has not
* occured in this window. This issue is fixed by treating a focus
* changed event as an interruption to the keydown, making sure
* that any code that needs to be run in onKeyUp is ALSO run here.
*
* Note, this focus change event happens AFTER the in-call menu is
* displayed, so mIsMenuDisplayed should always be correct by the
* time this method is called in the framework, please see:
* {@link onCreatePanelView}, {@link onOptionsMenuClosed}
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
// the dtmf tones should no longer be played
if (VDBG) log("onWindowFocusChanged(" + hasFocus + ")...");
mHasFocus = hasFocus;
if (!hasFocus && mDialer != null) {
if (VDBG) log("- onWindowFocusChanged: faking onDialerKeyUp()...");
mDialer.onDialerKeyUp(null);
}
}
private void onShowHideDialpad() {
if (VDBG) log("onShowHideDialpad()...");
if (mDialer.isOpened()) {
mDialer.closeDialer(true); // do the "closing" animation
} else {
mDialer.openDialer(true); // do the "opening" animation
}
if (ENABLE_DIALPANEL_HANDLER){
mDialer.setHandleVisible(true);
} else {
mDialer.setHandleVisible(false);
}
}
/**
* Updates the visibility of the DTMF dialpad (and its onscreen
* "handle", if applicable), based on the current state of the phone
* and/or the current InCallScreenMode.
*/
private void updateDialpadVisibility() {
if (VDBG) log("updateDialpadVisibility()...");
//
// (1) The dialpad itself:
//
// If an incoming call is ringing, make sure the dialpad is
// closed. (We do this to make sure we're not covering up the
// "incoming call" UI, and especially to make sure that the "touch
// lock" overlay won't appear.)
if (mPhone.getState() == Phone.State.RINGING) {
mDialer.closeDialer(false); // don't do the "closing" animation
// Also, clear out the "history" of DTMF digits you may have typed
// into the previous call (so you don't see the previous call's
// digits if you answer this call and then bring up the dialpad.)
//
// TODO: it would be more precise to do this when you *answer* the
// incoming call, rather than as soon as it starts ringing, but
// the InCallScreen doesn't keep enough state right now to notice
// that specific transition in onPhoneStateChanged().
mDialer.clearDigits();
}
//
// (2) The onscreen "handle":
//
// The handle is visible only if it's OK to actually open the
// dialpad. (Note this is meaningful only on platforms that use a
// SlidingDrawer as a container for the dialpad.)
if (ENABLE_DIALPANEL_HANDLER){
mDialer.setHandleVisible(okToShowDialpad());
} else {
mDialer.setHandleVisible(false);
}
//
// (3) The main in-call panel (containing the CallCard):
//
// On some platforms(*) we need to hide the CallCard (which is a
// child of mInCallPanel) while the dialpad is visible.
//
// (*) We need to do this when using the dialpad from the
// InCallTouchUi widget, but not when using the
// SlidingDrawer-based dialpad, because the SlidingDrawer itself
// is opaque.)
if (!mDialer.usingSlidingDrawer()) {
if (VDBG) log("isDialerOpened(): " + isDialerOpened() + ", mInCallScreenMode: " + mInCallScreenMode);
/* if (isDialerOpened()) {
mInCallPanel.setVisibility(View.GONE);
} else {
// Dialpad is dismissed; bring back the CallCard if
// it's supposed to be visible.
mInCallPanel.setVisibility(View.VISIBLE);
}*/
}
}
/**
* @return true if the DTMF dialpad is currently visible.
*/
/* package */ boolean isDialerOpened() {
return (mDialer != null && mDialer.isOpened());
}
//
// Bluetooth helper methods.
//
// - BluetoothAdapter is the Bluetooth system service. If
// getDefaultAdapter() returns null
// then the device is not BT capable. Use BluetoothDevice.isEnabled()
// to see if BT is enabled on the device.
//
// - BluetoothHeadset is the API for the control connection to a
// Bluetooth Headset. This lets you completely connect/disconnect a
// headset (which we don't do from the Phone UI!) but also lets you
// get the address of the currently active headset and see whether
// it's currently connected.
//
// - BluetoothHandsfree is the API to control the audio connection to
// a bluetooth headset. We use this API to switch the headset on and
// off when the user presses the "Bluetooth" button.
// Our BluetoothHandsfree instance (mBluetoothHandsfree) is created
// by the PhoneApp and will be null if the device is not BT capable.
//
/**
* @return true if the Bluetooth on/off switch in the UI should be
* available to the user (i.e. if the device is BT-capable
* and a headset is connected.)
*/
/* package */ boolean isBluetoothAvailable() {
if (VDBG) log("isBluetoothAvailable()...");
if (mBluetoothHandsfree == null) {
// Device is not BT capable.
if (VDBG) log(" ==> FALSE (not BT capable)");
return false;
}
// There's no need to ask the Bluetooth system service if BT is enabled:
//
// BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
// if ((adapter == null) || !adapter.isEnabled()) {
// if (DBG) log(" ==> FALSE (BT not enabled)");
// return false;
// }
// if (DBG) log(" - BT enabled! device name " + adapter.getName()
// + ", address " + adapter.getAddress());
//
// ...since we already have a BluetoothHeadset instance. We can just
// call isConnected() on that, and assume it'll be false if BT isn't
// enabled at all.
// Check if there's a connected headset, using the BluetoothHeadset API.
boolean isConnected = false;
if (mBluetoothHeadset != null) {
if (VDBG) log(" - headset state = " + mBluetoothHeadset.getState(null));
BluetoothDevice headset = mBluetoothHeadset.getCurrentHeadset();
if (VDBG) log(" - headset address: " + headset);
if (headset != null) {
isConnected = mBluetoothHeadset.isConnected(headset);
if (VDBG) log(" - isConnected: " + isConnected);
}
}
if (VDBG) log(" ==> " + isConnected);
return isConnected;
}
/**
* @return true if a BT device is available, and its audio is currently connected.
*/
/* package */ boolean isBluetoothAudioConnected() {
if (mBluetoothHandsfree == null) {
if (VDBG) log("isBluetoothAudioConnected: ==> FALSE (null mBluetoothHandsfree)");
return false;
}
boolean isAudioOn = mBluetoothHandsfree.isAudioOn();
if (VDBG) log("isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn);
return isAudioOn;
}
/**
* Helper method used to control the state of the green LED in the
* "Bluetooth" menu item.
*
* @return true if a BT device is available and its audio is currently connected,
* <b>or</b> if we issued a BluetoothHandsfree.userWantsAudioOn()
* call within the last 5 seconds (which presumably means
* that the BT audio connection is currently being set
* up, and will be connected soon.)
*/
/* package */ boolean isBluetoothAudioConnectedOrPending() {
if (isBluetoothAudioConnected()) {
if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (really connected)");
return true;
}
// If we issued a userWantsAudioOn() call "recently enough", even
// if BT isn't actually connected yet, let's still pretend BT is
// on. This is how we make the green LED in the menu item turn on
// right away.
if (mBluetoothConnectionPending) {
long timeSinceRequest =
SystemClock.elapsedRealtime() - mBluetoothConnectionRequestTime;
if (timeSinceRequest < 5000 /* 5 seconds */) {
if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (requested "
+ timeSinceRequest + " msec ago)");
return true;
} else {
if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE (request too old: "
+ timeSinceRequest + " msec ago)");
mBluetoothConnectionPending = false;
return false;
}
}
if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE");
return false;
}
/**
* Posts a message to our handler saying to update the onscreen UI
* based on a bluetooth headset state change.
*/
/* package */ void requestUpdateBluetoothIndication() {
if (VDBG) log("requestUpdateBluetoothIndication()...");
// No need to look at the current state here; any UI elements that
// care about the bluetooth state (i.e. the CallCard) get
// the necessary state directly from PhoneApp.showBluetoothIndication().
mHandler.removeMessages(REQUEST_UPDATE_BLUETOOTH_INDICATION);
mHandler.sendEmptyMessage(REQUEST_UPDATE_BLUETOOTH_INDICATION);
}
private void dumpBluetoothState() {
log("============== dumpBluetoothState() =============");
log("= isBluetoothAvailable: " + isBluetoothAvailable());
log("= isBluetoothAudioConnected: " + isBluetoothAudioConnected());
log("= isBluetoothAudioConnectedOrPending: " + isBluetoothAudioConnectedOrPending());
log("= PhoneApp.showBluetoothIndication: "
+ PhoneApp.getInstance().showBluetoothIndication());
log("=");
if (mBluetoothHandsfree != null) {
log("= BluetoothHandsfree.isAudioOn: " + mBluetoothHandsfree.isAudioOn());
if (mBluetoothHeadset != null) {
BluetoothDevice headset = mBluetoothHeadset.getCurrentHeadset();
log("= BluetoothHeadset.getCurrentHeadset: " + headset);
if (headset != null) {
log("= BluetoothHeadset.isConnected: "
+ mBluetoothHeadset.isConnected(headset));
}
} else {
log("= mBluetoothHeadset is null");
}
} else {
log("= mBluetoothHandsfree is null; device is not BT capable");
}
}
/* package */ void connectBluetoothAudio() {
if (VDBG) log("connectBluetoothAudio()...");
if (mBluetoothHandsfree != null) {
mBluetoothHandsfree.userWantsAudioOn();
}
// Watch out: The bluetooth connection doesn't happen instantly;
// the userWantsAudioOn() call returns instantly but does its real
// work in another thread. Also, in practice the BT connection
// takes longer than MENU_DISMISS_DELAY to complete(!) so we need
// a little trickery here to make the menu item's green LED update
// instantly.
// (See isBluetoothAudioConnectedOrPending() above.)
mBluetoothConnectionPending = true;
mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
}
/* package */ void disconnectBluetoothAudio() {
if (VDBG) log("disconnectBluetoothAudio()...");
if (mBluetoothHandsfree != null) {
mBluetoothHandsfree.userWantsAudioOff();
}
mBluetoothConnectionPending = false;
}
/**
* Called any time the DTMF dialpad is opened.
* @see DTMFTwelveKeyDialer.onDialerOpen()
*/
/* package */ void onDialerOpen() {
if (DBG) log("onDialerOpen()...");
// Update any other onscreen UI elements that depend on the dialpad.
updateDialpadVisibility();
// This counts as explicit "user activity".
PhoneApp.getInstance().pokeUserActivity();
}
/**
* Called any time the DTMF dialpad is closed.
* @see DTMFTwelveKeyDialer.onDialerClose()
*/
/* package */ void onDialerClose() {
if (DBG) log("onDialerClose()...");
final PhoneApp app = PhoneApp.getInstance();
// Update the visibility of the dialpad itself (and any other
// onscreen UI elements that depend on it.)
updateDialpadVisibility();
// This counts as explicit "user activity".
app.getInstance().pokeUserActivity();
}
private boolean isIn3GNetwork() {
int radiotech = mPhone.getServiceState().getRadioTechnology();
if (DBG) log("isIn3GNetwork()..., radiotech: " + radiotech);
return ((radiotech == ServiceState.RADIO_TECHNOLOGY_UMTS)
|| (radiotech == ServiceState.RADIO_TECHNOLOGY_HSDPA)
|| (radiotech == ServiceState.RADIO_TECHNOLOGY_HSUPA)
|| (radiotech == ServiceState.RADIO_TECHNOLOGY_HSPA));
}
private boolean okToRetryVideoCall() {
return isIn3GNetwork();
}
MediaPhone getMediaPhone(){
if (DBG) log("getMediaPhone()..., mp: " + mp);
return mp;
}
// enable/disable fake camera
private boolean setCameraFlag(boolean bSet) {
boolean bRet = false;
String oldValue = SystemProperties.get("gsm.camera.vt");
SystemProperties.set("gsm.camera.vt", bSet?"1":"0");
String newValue = SystemProperties.get("gsm.camera.vt");
log("setCameraFlag(), bSet: " + bSet + ", oldValue: " + oldValue + ", newValue: " + newValue);
if (bSet) {
bRet = newValue.equals("1");
} else {
bRet = newValue.equals("0");
}
log("setCameraFlag(), bRet: " + bRet);
return bRet;
}
private boolean checkCameraFlag() {
boolean bRet = false;
String newValue = SystemProperties.get("gsm.camera.vt");
bRet = newValue.equals("1");
log("checkCameraFlag(), bRet: " + bRet);
return bRet;
}
private int getCamerID(){
int iRet = 0;
if (mCameraType == CameraType.BOTTOM_CAMERA){
iRet = 0;
} else if (mCameraType == CameraType.FRONT_CAMERA) {
iRet = 1;
} else if (mCameraType == CameraType.FAKE_CAMERA) {
iRet = 2;
} else {
iRet = 0;
}
if (DBG) log("getCamerID(), iRet: " + iRet);
return iRet;
}
private int getSensorRotation(){
if (mCameraType == CameraType.FRONT_CAMERA){
return 270;
}
return 90;
}
private void switchToSpeaker(){
final PhoneApp app = PhoneApp.getInstance();
// open speaker in default
if (!PhoneUtils.isSpeakerOn(this)){
if (!app.isHeadsetPlugged()){
if (!isBluetoothAudioConnected()) {
PhoneUtils.turnOnSpeaker(this, true, true);
updateAudioMenu();
}
}
}
}
private boolean isAudioInCall(){
String line = null;
try{
FileReader fr = new FileReader("/sys/class/modem/status");
//FileReader fr = new FileReader("/data/status");
BufferedReader br = new BufferedReader(fr);
log("<isAudioInCall>");
line = br.readLine();
log(line);
}catch (Exception e) {
Log.e(LOG_TAG, "isAudioInCall failed, " + e);
}
if (null == line)
return false;
return line.startsWith("incall:1", 0);
}
private void displayStartTime() {
log("displayStartTime() mBeginTime: " + mBeginTime.getText());
if (!mWaitCD && (mBeginTime.getText().length() > 0)){
//TextView lable_beginTime = (TextView) findViewById(R.id.label_begintime);
//lable_beginTime.setVisibility(View.VISIBLE);
mBeginTime.setVisibility(View.VISIBLE);
}
}
private String getCurrentNumber() {
Phone.State state = mPhone.getState();
Call call;
if (state == Phone.State.RINGING){
call = mPhone.getRingingCall();
} else if (state == Phone.State.OFFHOOK){
call = mPhone.getForegroundCall();
} else {
return null;
}
Connection conn = call.getEarliestConnection();
String number = conn.getAddress();
log("getCurrentNumber(), number: " + number);
return number;
}
}