| /* |
| * 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(); |
| } |
| }; |
| } |