blob: a083b80273bd8465cddeb42c374af0c49cf16df0 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.dialer;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.telecom.Call;
import android.telecom.CallAudioState;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.Transformation;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.car.apps.common.CircleBitmapDrawable;
import com.android.car.apps.common.FabDrawable;
import com.android.car.dialer.telecom.TelecomUtils;
import com.android.car.dialer.telecom.UiCall;
import com.android.car.dialer.telecom.UiCallManager;
import com.android.car.dialer.telecom.UiCallManager.CallListener;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class OngoingCallFragment extends Fragment {
private static final String TAG = "Em.OngoingCall";
private static final HashMap<Integer, Character> mDialpadButtonMap = new HashMap<>();
static {
mDialpadButtonMap.put(R.id.one, '1');
mDialpadButtonMap.put(R.id.two, '2');
mDialpadButtonMap.put(R.id.three, '3');
mDialpadButtonMap.put(R.id.four, '4');
mDialpadButtonMap.put(R.id.five, '5');
mDialpadButtonMap.put(R.id.six, '6');
mDialpadButtonMap.put(R.id.seven, '7');
mDialpadButtonMap.put(R.id.eight, '8');
mDialpadButtonMap.put(R.id.nine, '9');
mDialpadButtonMap.put(R.id.zero, '0');
mDialpadButtonMap.put(R.id.star, '*');
mDialpadButtonMap.put(R.id.pound, '#');
}
private UiCall mPrimaryCall;
private UiCall mSecondaryCall;
private UiCall mLastRemovedCall;
private UiCallManager mUiCallManager;
private Handler mHandler;
private View mRingingCallControls;
private View mActiveCallControls;
private ImageButton mEndCallButton;
private ImageButton mUnholdCallButton;
private ImageButton mMuteButton;
private ImageButton mToggleDialpadButton;
private ImageButton mSwapButton;
private ImageButton mMergeButton;
private ImageButton mAnswerCallButton;
private ImageButton mRejectCallButton;
private TextView mNameTextView;
private TextView mSecondaryNameTextView;
private TextView mStateTextView;
private TextView mSecondaryStateTextView;
private ImageView mLargeContactPhotoView;
private ImageView mSmallContactPhotoView;
private View mDialpadContainer;
private View mSecondaryCallContainer;
private View mSecondaryCallControls;
private LinearLayout mRotaryDialpad;
private List<View> mDialpadViews;
private String mLoadedNumber;
private CharSequence mCallInfoLabel;
private UiBluetoothMonitor mUiBluetoothMonitor;
private final Interpolator
mAccelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
private final Interpolator mAccelerateInterpolator = new AccelerateInterpolator(10);
public static OngoingCallFragment newInstance(
UiCallManager callManager, UiBluetoothMonitor btMonitor) {
OngoingCallFragment fragment = new OngoingCallFragment();
fragment.mUiCallManager = callManager;
fragment.mUiBluetoothMonitor = btMonitor;
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler();
}
@Override
public void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(mUpdateDurationRunnable);
mHandler.removeCallbacks(mStopDtmfToneRunnable);
mHandler = null;
mLoadedNumber = null;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.ongoing_call, container, false);
mRingingCallControls = view.findViewById(R.id.ringing_call_controls);
mActiveCallControls = view.findViewById(R.id.active_call_controls);
mEndCallButton = (ImageButton) view.findViewById(R.id.end_call);
mUnholdCallButton = (ImageButton) view.findViewById(R.id.unhold_call);
mMuteButton = (ImageButton) view.findViewById(R.id.mute);
mToggleDialpadButton = (ImageButton) view.findViewById(R.id.toggle_dialpad);
mDialpadContainer = view.findViewById(R.id.dialpad_container);
mNameTextView = (TextView) view.findViewById(R.id.name);
mSecondaryNameTextView = (TextView) view.findViewById(R.id.name_secondary);
mStateTextView = (TextView) view.findViewById(R.id.info);
mSecondaryStateTextView = (TextView) view.findViewById(R.id.info_secondary);
mLargeContactPhotoView = (ImageView) view.findViewById(R.id.large_contact_photo);
mSmallContactPhotoView = (ImageView) view.findViewById(R.id.small_contact_photo);
mSecondaryCallContainer = view.findViewById(R.id.secondary_call_container);
mSecondaryCallControls = view.findViewById(R.id.secondary_call_controls);
mRotaryDialpad = (LinearLayout) view.findViewById(R.id.rotary_dialpad);
mSwapButton = (ImageButton) view.findViewById(R.id.swap);
mMergeButton = (ImageButton) view.findViewById(R.id.merge);
mAnswerCallButton = (ImageButton) view.findViewById(R.id.answer_call_button);
mRejectCallButton = (ImageButton) view.findViewById(R.id.reject_call_button);
boolean hasTouch = getResources().getBoolean(R.bool.has_touch);
View dialPadContainer = hasTouch ? mDialpadContainer : mRotaryDialpad;
mDialpadViews = Arrays.asList(
dialPadContainer.findViewById(R.id.one),
dialPadContainer.findViewById(R.id.two),
dialPadContainer.findViewById(R.id.three),
dialPadContainer.findViewById(R.id.four),
dialPadContainer.findViewById(R.id.five),
dialPadContainer.findViewById(R.id.six),
dialPadContainer.findViewById(R.id.seven),
dialPadContainer.findViewById(R.id.eight),
dialPadContainer.findViewById(R.id.nine),
dialPadContainer.findViewById(R.id.zero),
dialPadContainer.findViewById(R.id.pound),
dialPadContainer.findViewById(R.id.star)
);
if (hasTouch) {
// In touch screen, we need to adjust the InCall card for the narrow screen to show the
// full dial pad.
for (View dialpadView : mDialpadViews) {
dialpadView.setOnTouchListener(mDialpadTouchListener);
dialpadView.setOnKeyListener(mDialpadKeyListener);
}
} else {
for (View dialpadView : mDialpadViews) {
dialpadView.setOnKeyListener(mDialpadKeyListener);
}
mToggleDialpadButton.setImageResource(R.drawable.ic_rotary_dialpad);
}
setDialPadFocusability(!hasTouch);
setInCallControllerFocusability(!hasTouch);
mAnswerCallButton.setOnClickListener((unusedView) -> {
UiCall call = mUiCallManager.getCallWithState(Call.STATE_RINGING);
if (call == null) {
Log.w(TAG, "There is no incoming call to answer.");
return;
}
mUiCallManager.answerCall(call);
});
Context context = getContext();
FabDrawable answerCallDrawable = new FabDrawable(context);
answerCallDrawable.setFabAndStrokeColor(getResources().getColor(R.color.phone_call));
mAnswerCallButton.setBackground(answerCallDrawable);
mRejectCallButton.setOnClickListener((unusedView) -> {
UiCall call = mUiCallManager.getCallWithState(Call.STATE_RINGING);
if (call == null) {
Log.w(TAG, "There is no incoming call to reject.");
return;
}
mUiCallManager.rejectCall(call, false, null);
});
mEndCallButton.setOnClickListener((unusedView) -> {
UiCall call = mUiCallManager.getPrimaryCall();
if (call == null) {
Log.w(TAG, "There is no active call to end.");
return;
}
mUiCallManager.disconnectCall(call);
});
FabDrawable endCallDrawable = new FabDrawable(context);
endCallDrawable.setFabAndStrokeColor(getResources().getColor(R.color.phone_end_call));
mEndCallButton.setBackground(endCallDrawable);
mUnholdCallButton.setOnClickListener((unusedView) -> {
UiCall call = mUiCallManager.getPrimaryCall();
if (call == null) {
Log.w(TAG, "There is no active call to unhold.");
return;
}
mUiCallManager.unholdCall(call);
});
FabDrawable unholdCallDrawable = new FabDrawable(context);
unholdCallDrawable.setFabAndStrokeColor(getResources().getColor(R.color.phone_call));
mUnholdCallButton.setBackground(unholdCallDrawable);
mMuteButton.setOnClickListener((unusedView) -> {
if (mUiCallManager.getMuted()) {
mUiCallManager.setMuted(false);
} else {
mUiCallManager.setMuted(true);
}
});
mSwapButton.setOnClickListener((unusedView) -> {
UiCall call = mUiCallManager.getPrimaryCall();
if (call == null) {
Log.w(TAG, "There is no active call to hold.");
return;
}
if (call.getState() == Call.STATE_HOLDING) {
mUiCallManager.unholdCall(call);
} else {
mUiCallManager.holdCall(call);
}
});
mMergeButton.setOnClickListener((unusedView) -> {
UiCall call = mUiCallManager.getPrimaryCall();
UiCall secondarycall = mUiCallManager.getSecondaryCall();
if (call == null || mSecondaryCall == null) {
Log.w(TAG, "There aren't two call to merge.");
return;
}
mUiCallManager.conference(call, secondarycall);
});
mToggleDialpadButton.setOnClickListener((unusedView) -> {
if (mToggleDialpadButton.isActivated()) {
closeDialpad();
} else {
openDialpad(true /*animate*/);
}
});
mUiCallManager.addListener(mCallListener);
updateCalls();
updateRotaryFocus();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
mUiCallManager.removeListener(mCallListener);
}
@Override
public void onStart() {
super.onStart();
trySpeakerAudioRouteIfNecessary();
}
private void rebindViews() {
mHandler.removeCallbacks(mUpdateDurationRunnable);
// Toggle the visibility between the active call controls, ringing call controls,
// and no controls.
CharSequence disconnectCauseLabel = mLastRemovedCall == null ?
null : mLastRemovedCall.getDisconnectCause();
if (mPrimaryCall == null && !TextUtils.isEmpty(disconnectCauseLabel)) {
closeDialpad();
setStateText(disconnectCauseLabel);
return;
} else if (mPrimaryCall == null || mPrimaryCall.getState() == Call.STATE_DISCONNECTED) {
closeDialpad();
setStateText(getString(R.string.call_state_call_ended));
mRingingCallControls.setVisibility(View.GONE);
mActiveCallControls.setVisibility(View.GONE);
return;
} else if (mPrimaryCall.getState() == Call.STATE_RINGING) {
mRingingCallControls.setVisibility(View.VISIBLE);
mActiveCallControls.setVisibility(View.GONE);
} else {
mRingingCallControls.setVisibility(View.GONE);
mActiveCallControls.setVisibility(View.VISIBLE);
}
// Show the primary contact photo in the large ImageView on the right if there is no
// secondary call. Otherwise, show it in the small ImageView that is inside the card.
Context context = getContext();
final ContentResolver cr = context.getContentResolver();
final String primaryNumber = mPrimaryCall.getNumber();
// Don't reload the image if the number is the same.
if ((primaryNumber != null && !primaryNumber.equals(mLoadedNumber))
|| (primaryNumber == null && mLoadedNumber != null)) {
BitmapWorkerTask.BitmapRunnable runnable = new BitmapWorkerTask.BitmapRunnable() {
@Override
public void run() {
if (mBitmap != null) {
Resources r = mSmallContactPhotoView.getResources();
mSmallContactPhotoView.setImageDrawable(
new CircleBitmapDrawable(r, mBitmap));
mLargeContactPhotoView.setImageBitmap(mBitmap);
mLargeContactPhotoView.clearColorFilter();
} else {
mSmallContactPhotoView.setImageResource(R.drawable.logo_avatar);
mLargeContactPhotoView.setImageResource(R.drawable.ic_avatar_bg);
}
if (mSecondaryCall != null) {
BitmapWorkerTask.BitmapRunnable secondCallContactPhotoHandler =
new BitmapWorkerTask.BitmapRunnable() {
@Override
public void run() {
if (mBitmap != null) {
mLargeContactPhotoView.setImageBitmap(mBitmap);
} else {
mLargeContactPhotoView.setImageResource(
R.drawable.logo_avatar);
}
}
};
BitmapWorkerTask.loadBitmap(
cr, mLargeContactPhotoView, mSecondaryCall.getNumber(),
secondCallContactPhotoHandler);
int scrimColor = getResources().getColor(
R.color.phone_secondary_call_scrim);
mLargeContactPhotoView.setColorFilter(scrimColor);
}
mLoadedNumber = primaryNumber;
}
};
BitmapWorkerTask.loadBitmap(cr, mLargeContactPhotoView, primaryNumber, runnable);
}
if (mSecondaryCall != null) {
mSecondaryCallContainer.setVisibility(View.VISIBLE);
if (mPrimaryCall.getState() == Call.STATE_ACTIVE
&& mSecondaryCall.getState() == Call.STATE_HOLDING) {
mSecondaryCallControls.setVisibility(View.VISIBLE);
} else {
mSecondaryCallControls.setVisibility(View.GONE);
}
} else {
mSecondaryCallContainer.setVisibility(View.GONE);
mSecondaryCallControls.setVisibility(View.GONE);
}
String displayName = TelecomUtils.getDisplayName(context, mPrimaryCall);
mNameTextView.setText(displayName);
mNameTextView.setVisibility(TextUtils.isEmpty(displayName) ? View.GONE : View.VISIBLE);
if (mSecondaryCall != null) {
mSecondaryNameTextView.setText(
TelecomUtils.getDisplayName(context, mSecondaryCall));
}
switch (mPrimaryCall.getState()) {
case Call.STATE_NEW:
// Since the content resolver call is only cached when a contact is found,
// this should only be called once on a new call to avoid jank.
// TODO: consider moving TelecomUtils.getTypeFromNumber into a CursorLoader
String number = mPrimaryCall.getNumber();
mCallInfoLabel = TelecomUtils.getTypeFromNumber(context, number);
case Call.STATE_CONNECTING:
case Call.STATE_DIALING:
case Call.STATE_SELECT_PHONE_ACCOUNT:
case Call.STATE_HOLDING:
case Call.STATE_DISCONNECTED:
mHandler.removeCallbacks(mUpdateDurationRunnable);
String callInfoText = TelecomUtils.getCallInfoText(context,
mPrimaryCall, mCallInfoLabel);
setStateText(callInfoText);
break;
case Call.STATE_ACTIVE:
if (mUiBluetoothMonitor.isHfpConnected()) {
mHandler.post(mUpdateDurationRunnable);
}
break;
case Call.STATE_RINGING:
Log.w(TAG, "There should not be a ringing call in the ongoing call fragment.");
break;
default:
Log.w(TAG, "Unhandled call state: " + mPrimaryCall.getState());
}
if (mSecondaryCall != null) {
mSecondaryStateTextView.setText(
TelecomUtils.callStateToUiString(context, mSecondaryCall.getState()));
}
// If it is a voicemail call, open the dialpad (with no animation).
if (primaryNumber != null && primaryNumber.equals(
TelecomUtils.getVoicemailNumber(context))) {
if (getResources().getBoolean(R.bool.has_touch)) {
openDialpad(false /*animate*/);
mToggleDialpadButton.setVisibility(View.GONE);
} else {
mToggleDialpadButton.setVisibility(View.VISIBLE);
mToggleDialpadButton.requestFocus();
}
} else {
mToggleDialpadButton.setVisibility(View.VISIBLE);
}
// Handle the holding case.
if (mPrimaryCall.getState() == Call.STATE_HOLDING) {
mEndCallButton.setVisibility(View.GONE);
mUnholdCallButton.setVisibility(View.VISIBLE);
mMuteButton.setVisibility(View.INVISIBLE);
mToggleDialpadButton.setVisibility(View.INVISIBLE);
} else {
mEndCallButton.setVisibility(View.VISIBLE);
mUnholdCallButton.setVisibility(View.GONE);
mMuteButton.setVisibility(View.VISIBLE);
mToggleDialpadButton.setVisibility(View.VISIBLE);
}
}
private void setStateText(CharSequence stateText) {
mStateTextView.setText(stateText);
mStateTextView.setVisibility(TextUtils.isEmpty(stateText) ? View.GONE : View.VISIBLE);
}
private void updateCalls() {
mPrimaryCall = mUiCallManager.getPrimaryCall();
if (mPrimaryCall != null && mPrimaryCall.getState() == Call.STATE_RINGING) {
// TODO: update when notifications will work
}
mSecondaryCall = mUiCallManager.getSecondaryCall();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Primary call: " + mPrimaryCall + "\tSecondary call:" + mSecondaryCall);
}
rebindViews();
}
/**
* If the phone is using bluetooth:
* * Do nothing
* If the phone is not using bluetooth:
* * If the phone supports bluetooth, use it.
* * If the phone doesn't support bluetooth and support speaker, use speaker
* * Otherwise, do nothing. Hopefully no phones won't have bt or speaker.
*/
private void trySpeakerAudioRouteIfNecessary() {
if (mUiCallManager == null) {
return;
}
int supportedAudioRouteMask = mUiCallManager.getSupportedAudioRouteMask();
boolean supportsBluetooth = (supportedAudioRouteMask & CallAudioState.ROUTE_BLUETOOTH) != 0;
boolean supportsSpeaker = (supportedAudioRouteMask & CallAudioState.ROUTE_SPEAKER) != 0;
boolean isUsingBluetooth =
mUiCallManager.getAudioRoute() == CallAudioState.ROUTE_BLUETOOTH;
if (supportsBluetooth && !isUsingBluetooth) {
mUiCallManager.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH);
} else if (!supportsBluetooth && supportsSpeaker) {
mUiCallManager.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
}
}
private void openDialpad(boolean animate) {
if (mToggleDialpadButton.isActivated()) {
return;
}
mToggleDialpadButton.setActivated(true);
if (getResources().getBoolean(R.bool.has_touch)) {
// This array of of size 2 because getLocationOnScreen returns (x,y) coordinates.
int[] location = new int[2];
mToggleDialpadButton.getLocationOnScreen(location);
// The dialpad should be aligned with the right edge of mToggleDialpadButton.
int startingMargin = location[1] + mToggleDialpadButton.getWidth();
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) mDialpadContainer.getLayoutParams();
if (layoutParams.getMarginStart() != startingMargin) {
layoutParams.setMarginStart(startingMargin);
mDialpadContainer.setLayoutParams(layoutParams);
}
Animation anim = new DialpadAnimation(getContext(), false /* reverse */, animate);
mDialpadContainer.startAnimation(anim);
} else {
final int toggleButtonImageOffset = getResources().getDimensionPixelSize(
R.dimen.in_call_toggle_button_image_offset);
final int muteButtonLeftMargin =
((LinearLayout.LayoutParams) mMuteButton.getLayoutParams()).leftMargin;
mEndCallButton.animate()
.alpha(0)
.setStartDelay(0)
.setDuration(384)
.setInterpolator(mAccelerateDecelerateInterpolator)
.withEndAction(() -> {
mEndCallButton.setVisibility(View.INVISIBLE);
mEndCallButton.setFocusable(false);
}).start();
mMuteButton.animate()
.alpha(0)
.setStartDelay(0)
.setDuration(240)
.setInterpolator(mAccelerateDecelerateInterpolator)
.withEndAction(() -> {
mMuteButton.setVisibility(View.INVISIBLE);
mMuteButton.setFocusable(false);
}).start();
mToggleDialpadButton.animate()
.setStartDelay(0)
.translationX(-(mEndCallButton.getWidth() + muteButtonLeftMargin
+ mMuteButton.getWidth() + toggleButtonImageOffset))
.setDuration(480)
.setInterpolator(mAccelerateDecelerateInterpolator)
.start();
mRotaryDialpad.setTranslationX(
-(mEndCallButton.getWidth() + muteButtonLeftMargin + toggleButtonImageOffset));
mRotaryDialpad.animate()
.translationX(-(mEndCallButton.getWidth() + muteButtonLeftMargin
+ mMuteButton.getWidth() + toggleButtonImageOffset))
.setDuration(320)
.setInterpolator(mAccelerateDecelerateInterpolator)
.setStartDelay(240)
.withStartAction(() -> {
mRotaryDialpad.setVisibility(View.VISIBLE);
int delay = 0;
for (View dialpadView : mDialpadViews) {
dialpadView.setAlpha(0);
dialpadView.animate()
.alpha(1)
.setDuration(160)
.setStartDelay(delay)
.setInterpolator(mAccelerateInterpolator)
.start();
delay += 10;
}
}).start();
}
}
private void closeDialpad() {
if (!mToggleDialpadButton.isActivated()) {
return;
}
mToggleDialpadButton.setActivated(false);
if (getResources().getBoolean(R.bool.has_touch)) {
Animation anim = new DialpadAnimation(getContext(), true /* reverse */);
mDialpadContainer.startAnimation(anim);
} else {
final int toggleButtonImageOffset = getResources().getDimensionPixelSize(
R.dimen.in_call_toggle_button_image_offset);
final int muteButtonLeftMargin =
((LinearLayout.LayoutParams) mMuteButton.getLayoutParams()).leftMargin;
mRotaryDialpad.animate()
.setStartDelay(0)
.translationX(-(mEndCallButton.getWidth()
+ muteButtonLeftMargin + toggleButtonImageOffset))
.setDuration(320)
.setInterpolator(mAccelerateDecelerateInterpolator)
.withStartAction(() -> {
int delay = 0;
for (int i = mDialpadViews.size() - 1; i >= 0; i--) {
View dialpadView = mDialpadViews.get(i);
dialpadView.animate()
.alpha(0)
.setDuration(160)
.setStartDelay(delay)
.setInterpolator(mAccelerateInterpolator)
.start();
delay += 10;
}
}).withEndAction(() -> {
mRotaryDialpad.setVisibility(View.GONE);
mRotaryDialpad.setTranslationX(0);
}).start();
mToggleDialpadButton.animate()
.translationX(0)
.setDuration(480)
.setStartDelay(80)
.setInterpolator(mAccelerateDecelerateInterpolator)
.start();
mMuteButton.animate()
.alpha(1)
.setDuration(176)
.setInterpolator(mAccelerateDecelerateInterpolator)
.setStartDelay(384)
.withStartAction(() -> {
mMuteButton.setVisibility(View.VISIBLE);
mMuteButton.setFocusable(true);
}).start();
mEndCallButton.animate()
.alpha(1)
.setDuration(320)
.setInterpolator(mAccelerateDecelerateInterpolator)
.setStartDelay(240)
.withStartAction(() -> {
mEndCallButton.setVisibility(View.VISIBLE);
mEndCallButton.setFocusable(true);
}).start();
}
}
private void updateRotaryFocus() {
boolean hasTouch = getResources().getBoolean(R.bool.has_touch);
if (mPrimaryCall != null && !hasTouch) {
if (mPrimaryCall.getState() == Call.STATE_RINGING) {
mRingingCallControls.requestFocus();
} else {
mActiveCallControls.requestFocus();
}
}
}
private void setInCallControllerFocusability(boolean focusable) {
mSwapButton.setFocusable(focusable);
mMergeButton.setFocusable(focusable);
mAnswerCallButton.setFocusable(focusable);
mRejectCallButton.setFocusable(focusable);
mEndCallButton.setFocusable(focusable);
mUnholdCallButton.setFocusable(focusable);
mMuteButton.setFocusable(focusable);
mToggleDialpadButton.setFocusable(focusable);
}
private void setDialPadFocusability(boolean focusable) {
for (View dialPadView : mDialpadViews) {
dialPadView.setFocusable(focusable);
}
}
private final View.OnTouchListener mDialpadTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Character digit = mDialpadButtonMap.get(v.getId());
if (digit == null) {
Log.w(TAG, "Unknown dialpad button pressed.");
return false;
}
if (event.getAction() == MotionEvent.ACTION_DOWN) {
v.setPressed(true);
mUiCallManager.playDtmfTone(mPrimaryCall, digit);
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
v.setPressed(false);
v.performClick();
mUiCallManager.stopDtmfTone(mPrimaryCall);
return true;
}
return false;
}
};
private final View.OnKeyListener mDialpadKeyListener = new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Character digit = mDialpadButtonMap.get(v.getId());
if (digit == null) {
Log.w(TAG, "Unknown dialpad button pressed.");
return false;
}
if (event.getKeyCode() != KeyEvent.KEYCODE_DPAD_CENTER) {
return false;
}
if (event.getAction() == KeyEvent.ACTION_DOWN) {
v.setPressed(true);
mUiCallManager.playDtmfTone(mPrimaryCall, digit);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
v.setPressed(false);
mUiCallManager.stopDtmfTone(mPrimaryCall);
return true;
}
return false;
}
};
private final Runnable mUpdateDurationRunnable = new Runnable() {
@Override
public void run() {
if (mPrimaryCall.getState() != Call.STATE_ACTIVE) {
return;
}
String callInfoText = TelecomUtils.getCallInfoText(getContext(),
mPrimaryCall, mCallInfoLabel);
setStateText(callInfoText);
mHandler.postDelayed(this /* runnable */, DateUtils.SECOND_IN_MILLIS);
}
};
private final Runnable mStopDtmfToneRunnable = new Runnable() {
@Override
public void run() {
mUiCallManager.stopDtmfTone(mPrimaryCall);
}
};
private final class DialpadAnimation extends Animation {
private static final int DURATION = 300;
private static final float MAX_SCRIM_ALPHA = 0.6f;
private final int mStartingTranslation;
private final int mScrimColor;
private final boolean mReverse;
public DialpadAnimation(Context context, boolean reverse) {
this(context, reverse, true);
}
public DialpadAnimation(Context context, boolean reverse, boolean animate) {
setDuration(animate ? DURATION : 0);
setInterpolator(new AccelerateDecelerateInterpolator());
Resources res = context.getResources();
mStartingTranslation =
res.getDimensionPixelOffset(R.dimen.in_call_card_dialpad_translation_x);
mScrimColor = res.getColor(R.color.phone_theme);
mReverse = reverse;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (mReverse) {
interpolatedTime = 1f - interpolatedTime;
}
int translationX = (int) (mStartingTranslation * (1f - interpolatedTime));
mDialpadContainer.setTranslationX(translationX);
mDialpadContainer.setAlpha(interpolatedTime);
if (interpolatedTime == 0f) {
mDialpadContainer.setVisibility(View.GONE);
} else {
mDialpadContainer.setVisibility(View.VISIBLE);
}
float alpha = 255f * interpolatedTime * MAX_SCRIM_ALPHA;
mLargeContactPhotoView.setColorFilter(Color.argb((int) alpha, Color.red(mScrimColor),
Color.green(mScrimColor), Color.blue(mScrimColor)));
mSecondaryNameTextView.setAlpha(1f - interpolatedTime);
mSecondaryStateTextView.setAlpha(1f - interpolatedTime);
}
}
private final CallListener mCallListener = new CallListener() {
@Override
public void onCallAdded(UiCall call) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "on call added");
}
updateCalls();
trySpeakerAudioRouteIfNecessary();
}
@Override
public void onCallRemoved(UiCall call) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "on call removed");
}
mLastRemovedCall = call;
updateCalls();
}
@Override
public void onAudioStateChanged(boolean isMuted, int audioRoute,
int supportedAudioRouteMask) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "on audio state changed");
}
mMuteButton.setActivated(isMuted);
trySpeakerAudioRouteIfNecessary();
}
@Override
public void onStateChanged(UiCall call, int state) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onStateChanged");
}
updateCalls();
// this will reset the focus if any state of any call changes on pure rotary devices.
updateRotaryFocus();
}
@Override
public void onCallUpdated(UiCall call) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onCallUpdated");
}
updateCalls();
}
};
}