From 17d5ac7d0b429687765e940279726623f5f5f823 Mon Sep 17 00:00:00 2001 From: Anthony Chen Date: Wed, 26 Apr 2017 17:18:50 -0700 Subject: Clean up Dialer code. - Clean up the use of fragments. Create newInstance() methods that will set the appropriate variables via arguments. - Ensure that TelecomActivity does not hold onto Fragment instances as this could lead to wrong lifecycle events being triggered. - Restructure OngoingCallFragment so that methods are more concise - Switch to using onStop/onStart in TelecomActivity to handle multi-window. - Remove the usage of commitAllowingStateLoss. Use commitNow/commit where appropriate. - Properly hook up the tone sounds for dialing - Remove unused code Test: connected phone via bluetooth, made some calls, dialed number, ensure no crashes Change-Id: Ib70367a970b09abb1fcb2621f03f940c31e92468 --- .../android/car/dialer/ContactDetailsFragment.java | 23 +- src/com/android/car/dialer/DialerFragment.java | 145 +++++--- src/com/android/car/dialer/DialpadButton.java | 2 +- src/com/android/car/dialer/NoHfpFragment.java | 49 ++- .../android/car/dialer/OngoingCallFragment.java | 399 +++++++++++---------- src/com/android/car/dialer/StrequentsAdapter.java | 15 +- src/com/android/car/dialer/StrequentsFragment.java | 17 +- src/com/android/car/dialer/TelecomActivity.java | 235 ++++++------ 8 files changed, 469 insertions(+), 416 deletions(-) diff --git a/src/com/android/car/dialer/ContactDetailsFragment.java b/src/com/android/car/dialer/ContactDetailsFragment.java index a334f722..9feb00fd 100644 --- a/src/com/android/car/dialer/ContactDetailsFragment.java +++ b/src/com/android/car/dialer/ContactDetailsFragment.java @@ -48,8 +48,8 @@ import java.util.List; public class ContactDetailsFragment extends Fragment implements LoaderManager.LoaderCallbacks { private static final String TAG = "ContactDetailsFragment"; - private static final int DETAILS_QUERY_ID = 31415; - private static final int PHONE_QUERY_ID = 42; + private static final int DETAILS_LOADER_QUERY_ID = 1; + private static final int PHONE_LOADER_QUERY_ID = 2; private static final String KEY_URI = "uri"; private static final String[] CONTACT_DETAILS_PROJECTION = { @@ -64,10 +64,12 @@ public class ContactDetailsFragment extends Fragment public static ContactDetailsFragment newInstance(Uri uri, UiCallManager callManager) { ContactDetailsFragment fragment = new ContactDetailsFragment(); + fragment.mCallManager = callManager; + Bundle args = new Bundle(); args.putParcelable(KEY_URI, uri); fragment.setArguments(args); - fragment.mCallManager = callManager; + return fragment; } @@ -82,7 +84,7 @@ public class ContactDetailsFragment extends Fragment @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - getLoaderManager().initLoader(DETAILS_QUERY_ID, null, this); + getLoaderManager().initLoader(DETAILS_LOADER_QUERY_ID, null, this); } @Override @@ -91,7 +93,7 @@ public class ContactDetailsFragment extends Fragment Log.d(TAG, "onCreateLoader id=" + id); } - if (id != DETAILS_QUERY_ID) { + if (id != DETAILS_LOADER_QUERY_ID) { return null; } @@ -125,8 +127,6 @@ public class ContactDetailsFragment extends Fragment public TextView text; public ImageView rightIcon; - private int mViewType; - public ContactDetailViewHolder(View v) { super(v); card = v.findViewById(R.id.card); @@ -143,24 +143,17 @@ public class ContactDetailsFragment extends Fragment private static final int ID_HEADER = 1; private static final int ID_CONTENT = 2; - private final List mData = new ArrayList<>(); - private final Cursor mCursor; - private final String mContactName; - private final String mContactPhotoUri; private List> mPhoneNumbers = new ArrayList<>(); public ContactDetailsAdapter(Cursor cursor) { super(); - mCursor = cursor; int idColIdx = cursor.getColumnIndex(ContactsContract.Contacts._ID); String contactId = cursor.getString(idColIdx); int nameColIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); mContactName = cursor.getString(nameColIdx); - int photoColIdx = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI); - mContactPhotoUri = cursor.getString(photoColIdx); int hasPhoneColIdx = cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER); boolean hasPhoneNumber = Integer.parseInt(cursor.getString(hasPhoneColIdx)) > 0; @@ -169,7 +162,7 @@ public class ContactDetailsFragment extends Fragment } // Fetch the phone number from the contacts db using another loader. - getLoaderManager().initLoader(PHONE_QUERY_ID, null, + getLoaderManager().initLoader(PHONE_LOADER_QUERY_ID, null, new LoaderManager.LoaderCallbacks() { @Override public Loader onCreateLoader(int id, Bundle args) { diff --git a/src/com/android/car/dialer/DialerFragment.java b/src/com/android/car/dialer/DialerFragment.java index bf3197a5..642bf198 100644 --- a/src/com/android/car/dialer/DialerFragment.java +++ b/src/com/android/car/dialer/DialerFragment.java @@ -19,16 +19,18 @@ import android.content.Context; import android.media.AudioManager; import android.media.ToneGenerator; import android.os.Bundle; -import android.os.Handler; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.Log; +import android.util.SparseArray; +import android.util.SparseIntArray; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; + import com.android.car.apps.common.FabDrawable; import com.android.car.dialer.telecom.TelecomUtils; import com.android.car.dialer.telecom.UiCallManager; @@ -40,12 +42,14 @@ import com.android.car.dialer.telecom.UiCallManager.CallListener; public class DialerFragment extends Fragment { private static final String TAG = "Em.DialerFragment"; private static final String INPUT_ACTIVE_KEY = "INPUT_ACTIVE_KEY"; + private static final String DIAL_NUMBER_KEY = "DIAL_NUMBER_KEY"; + + private static final int TONE_LENGTH_MS = 150; private static final int TONE_RELATIVE_VOLUME = 80; - private static final int ABANDON_AUDIO_FOCUS_DELAY_MS = 250; private static final int MAX_DIAL_NUMBER = 20; - private static final int NO_TONE = -1; - private static final ArrayMap mToneMap = new ArrayMap<>(); + private static final SparseIntArray mToneMap = new SparseIntArray(); + private static final SparseArray mDialValueMap = new SparseArray<>(); static { mToneMap.put(KeyEvent.KEYCODE_1, ToneGenerator.TONE_DTMF_1); @@ -60,14 +64,25 @@ public class DialerFragment extends Fragment { mToneMap.put(KeyEvent.KEYCODE_0, ToneGenerator.TONE_DTMF_0); mToneMap.put(KeyEvent.KEYCODE_STAR, ToneGenerator.TONE_DTMF_S); mToneMap.put(KeyEvent.KEYCODE_POUND, ToneGenerator.TONE_DTMF_P); + + mDialValueMap.put(KeyEvent.KEYCODE_1, "1"); + mDialValueMap.put(KeyEvent.KEYCODE_2, "2"); + mDialValueMap.put(KeyEvent.KEYCODE_3, "3"); + mDialValueMap.put(KeyEvent.KEYCODE_4, "4"); + mDialValueMap.put(KeyEvent.KEYCODE_5, "5"); + mDialValueMap.put(KeyEvent.KEYCODE_6, "6"); + mDialValueMap.put(KeyEvent.KEYCODE_7, "7"); + mDialValueMap.put(KeyEvent.KEYCODE_8, "8"); + mDialValueMap.put(KeyEvent.KEYCODE_9, "9"); + mDialValueMap.put(KeyEvent.KEYCODE_0, "0"); + mDialValueMap.put(KeyEvent.KEYCODE_STAR, "*"); + mDialValueMap.put(KeyEvent.KEYCODE_POUND, "#"); } private Context mContext; private UiCallManager mUiCallManager; private final StringBuffer mNumber = new StringBuffer(MAX_DIAL_NUMBER); - private AudioManager mAudioManager; private ToneGenerator mToneGenerator; - private final Handler mHandler = new Handler(); private final Object mToneGeneratorLock = new Object(); private TextView mNumberView; private boolean mShowInput = true; @@ -87,28 +102,36 @@ public class DialerFragment extends Fragment { void onDialerBackClick(); } - public static DialerFragment newInstance(UiCallManager callManager) { + /** + * Creates a new instance of the {@link DialerFragment} and display the given number as the one + * to dial. + */ + static DialerFragment newInstance(UiCallManager callManager, + DialerBackButtonListener listener, @Nullable String dialNumber) { DialerFragment fragment = new DialerFragment(); fragment.mUiCallManager = callManager; - return fragment; - } + fragment.mBackListener = listener; - /** - * Sets the given {@link DialerBackButtonListener} to be notified whenever the back button - * on the dialer has been clicked. Passing {@code null} to this method will clear all listeners. - */ - public void setDialerBackButtonListener(DialerBackButtonListener listener) { - mBackListener = listener; + if (!TextUtils.isEmpty(dialNumber)) { + Bundle args = new Bundle(); + args.putString(DIAL_NUMBER_KEY, dialNumber); + fragment.setArguments(args); + } + + return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mAudioManager = - (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); if (savedInstanceState != null && savedInstanceState.containsKey(INPUT_ACTIVE_KEY)) { mShowInput = savedInstanceState.getBoolean(INPUT_ACTIVE_KEY); } + + Bundle args = getArguments(); + if (args != null) { + setDialNumber(args.getString(DIAL_NUMBER_KEY)); + } } @Override @@ -125,7 +148,7 @@ public class DialerFragment extends Fragment { Log.v(TAG, "onCreateView: inflated successfully"); } - view.findViewById(R.id.exit_dialer_button).setOnClickListener((unusedView) -> { + view.findViewById(R.id.exit_dialer_button).setOnClickListener(v -> { if (mBackListener != null) { mBackListener.onDialerBackClick(); } @@ -137,9 +160,10 @@ public class DialerFragment extends Fragment { Log.v(TAG, "mShowInput: " + mShowInput); } - View callButton = view.findViewById(R.id.call); FabDrawable answerCallDrawable = new FabDrawable(mContext); - answerCallDrawable.setFabAndStrokeColor(getResources().getColor(R.color.phone_call)); + answerCallDrawable.setFabAndStrokeColor(getContext().getColor(R.color.phone_call)); + + View callButton = view.findViewById(R.id.call); callButton.setBackground(answerCallDrawable); callButton.setVisibility(View.VISIBLE); callButton.setOnClickListener((unusedView) -> { @@ -151,16 +175,17 @@ public class DialerFragment extends Fragment { mUiCallManager.safePlaceCall(mNumber.toString(), false); } }); + View deleteButton = view.findViewById(R.id.delete); deleteButton.setVisibility(View.VISIBLE); - deleteButton.setOnClickListener((unusedView) -> { + deleteButton.setOnClickListener(v -> { if (mNumber.length() != 0) { mNumber.deleteCharAt(mNumber.length() - 1); mNumberView.setText(getFormattedNumber(mNumber.toString())); } }); - setupKeypad(view); + setupKeypadClickListeners(view); return view; } @@ -170,35 +195,47 @@ public class DialerFragment extends Fragment { * associated value to {@link #mNumber}. */ private class DialpadClickListener implements View.OnClickListener { - private String mValue; + private final int mTone; + private final String mValue; - public DialpadClickListener(String value) { - mValue = value; + DialpadClickListener(int keyCode) { + mTone = mToneMap.get(keyCode); + mValue = mDialValueMap.get(keyCode); } @Override public void onClick(View v) { mNumber.append(mValue); mNumberView.setText(getFormattedNumber(mNumber.toString())); + playTone(mTone); } - }; + } - /** - * Sets up the click listeners for all the dialpad buttons. - */ - private void setupKeypad(View parent) { - parent.findViewById(R.id.zero).setOnClickListener(new DialpadClickListener("0")); - parent.findViewById(R.id.one).setOnClickListener(new DialpadClickListener("1")); - parent.findViewById(R.id.two).setOnClickListener(new DialpadClickListener("2")); - parent.findViewById(R.id.three).setOnClickListener(new DialpadClickListener("3")); - parent.findViewById(R.id.four).setOnClickListener(new DialpadClickListener("4")); - parent.findViewById(R.id.five).setOnClickListener(new DialpadClickListener("5")); - parent.findViewById(R.id.six).setOnClickListener(new DialpadClickListener("6")); - parent.findViewById(R.id.seven).setOnClickListener(new DialpadClickListener("7")); - parent.findViewById(R.id.eight).setOnClickListener(new DialpadClickListener("8")); - parent.findViewById(R.id.nine).setOnClickListener(new DialpadClickListener("9")); - parent.findViewById(R.id.star).setOnClickListener(new DialpadClickListener("*")); - parent.findViewById(R.id.pound).setOnClickListener(new DialpadClickListener("#")); + private void setupKeypadClickListeners(View parent) { + parent.findViewById(R.id.zero).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_0)); + parent.findViewById(R.id.one).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_1)); + parent.findViewById(R.id.two).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_2)); + parent.findViewById(R.id.three).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_3)); + parent.findViewById(R.id.four).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_4)); + parent.findViewById(R.id.five).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_5)); + parent.findViewById(R.id.six).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_6)); + parent.findViewById(R.id.seven).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_7)); + parent.findViewById(R.id.eight).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_8)); + parent.findViewById(R.id.nine).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_9)); + parent.findViewById(R.id.star).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_STAR)); + parent.findViewById(R.id.pound).setOnClickListener( + new DialpadClickListener(KeyEvent.KEYCODE_POUND)); } @Override @@ -237,7 +274,7 @@ public class DialerFragment extends Fragment { mNumberView = null; } - public void setDialNumber(final String number) { + private void setDialNumber(final String number) { if (TextUtils.isEmpty(number)) { return; } @@ -256,6 +293,18 @@ public class DialerFragment extends Fragment { mNumberView.setText(getFormattedNumber(mNumber.toString())); } + private void playTone(int tone) { + synchronized (mToneGeneratorLock) { + if (mToneGenerator == null) { + Log.w(TAG, "playTone: mToneGenerator == null, tone: " + tone); + return; + } + + // Start the new tone (will stop any playing tone) + mToneGenerator.startTone(tone, TONE_LENGTH_MS); + } + } + private void stopTone() { synchronized (mToneGeneratorLock) { if (mToneGenerator == null) { @@ -263,17 +312,9 @@ public class DialerFragment extends Fragment { return; } mToneGenerator.stopTone(); - mHandler.postDelayed(mDelayedAbandonAudioFocusRunnable, ABANDON_AUDIO_FOCUS_DELAY_MS); } } - private final Runnable mDelayedAbandonAudioFocusRunnable = new Runnable() { - @Override - public void run() { - mAudioManager.abandonAudioFocus(null); - } - }; - private String getFormattedNumber(String number) { return TelecomUtils.getFormattedNumber(mContext, number); } diff --git a/src/com/android/car/dialer/DialpadButton.java b/src/com/android/car/dialer/DialpadButton.java index 28926f59..7e058c0b 100644 --- a/src/com/android/car/dialer/DialpadButton.java +++ b/src/com/android/car/dialer/DialpadButton.java @@ -31,7 +31,7 @@ public class DialpadButton extends FrameLayout { private String mNumberText; private String mLetterText; - private int mImageRes; + private int mImageRes = INVALID_IMAGE_RES; public DialpadButton(Context context) { super(context); diff --git a/src/com/android/car/dialer/NoHfpFragment.java b/src/com/android/car/dialer/NoHfpFragment.java index 16e736ae..295b4746 100644 --- a/src/com/android/car/dialer/NoHfpFragment.java +++ b/src/com/android/car/dialer/NoHfpFragment.java @@ -23,22 +23,63 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - +/** + * A fragment that informs the user that there is no bluetooth device attached that can make + * phone calls. + */ public class NoHfpFragment extends Fragment { + private static final String ERROR_MESSAGE_KEY = "ERROR_MESSAGE_KEY"; + + private TextView mErrorMessageView; private String mErrorMessage; - public void setErrorMessage(String message) { - mErrorMessage = message; + /** + * Returns an instance of the {@link NoHfpFragment} with the given error message as the one to + * display. + */ + static NoHfpFragment newInstance(String errorMessage) { + NoHfpFragment fragment = new NoHfpFragment(); + + Bundle args = new Bundle(); + args.putString(ERROR_MESSAGE_KEY, errorMessage); + fragment.setArguments(args); + + return fragment; + } + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + if (args != null) { + mErrorMessage = args.getString(ERROR_MESSAGE_KEY); + } + } + + /** + * Sets the given error message to be displayed. + */ + void setErrorMessage(String errorMessage) { + mErrorMessage = errorMessage; + + // If this method is called before the error message view is available, then no need to + // set the message. Instead, it will be set in onCreateView(). + if (mErrorMessageView != null && !TextUtils.isEmpty(mErrorMessage)) { + mErrorMessageView.setText(mErrorMessage); + } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.no_hfp, container, false); + mErrorMessageView = v.findViewById(R.id.error_string); + // If no error message is set, the default string from the layout will be used. if (!TextUtils.isEmpty(mErrorMessage)) { - ((TextView) v.findViewById(R.id.error_string)).setText(mErrorMessage); + mErrorMessageView.setText(mErrorMessage); } + return v; } } diff --git a/src/com/android/car/dialer/OngoingCallFragment.java b/src/com/android/car/dialer/OngoingCallFragment.java index 7e88f7be..87d4ebb0 100644 --- a/src/com/android/car/dialer/OngoingCallFragment.java +++ b/src/com/android/car/dialer/OngoingCallFragment.java @@ -27,20 +27,20 @@ import android.telecom.CallAudioState; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; +import android.util.SparseArray; 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; @@ -49,12 +49,15 @@ 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; +import java.util.Objects; +/** + * A fragment that displays information about an on-going call with options to hang up. + */ public class OngoingCallFragment extends Fragment { - private static final String TAG = "Em.OngoingCall"; - private static final HashMap mDialpadButtonMap = new HashMap<>(); + private static final String TAG = "OngoingCall"; + private static final SparseArray mDialpadButtonMap = new SparseArray<>(); static { mDialpadButtonMap.put(R.id.one, '1'); @@ -71,11 +74,10 @@ public class OngoingCallFragment extends Fragment { mDialpadButtonMap.put(R.id.pound, '#'); } - private UiCall mPrimaryCall; - private UiCall mSecondaryCall; + private final Handler mHandler = new Handler(); + private UiCall mLastRemovedCall; private UiCallManager mUiCallManager; - private Handler mHandler; private View mRingingCallControls; private View mActiveCallControls; private ImageButton mEndCallButton; @@ -95,17 +97,12 @@ public class OngoingCallFragment extends Fragment { private View mDialpadContainer; private View mSecondaryCallContainer; private View mSecondaryCallControls; - private List 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) { + static OngoingCallFragment newInstance(UiCallManager callManager, + UiBluetoothMonitor btMonitor) { OngoingCallFragment fragment = new OngoingCallFragment(); fragment.mUiCallManager = callManager; fragment.mUiBluetoothMonitor = btMonitor; @@ -115,7 +112,6 @@ public class OngoingCallFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mHandler = new Handler(); } @Override @@ -123,35 +119,17 @@ public class OngoingCallFragment extends Fragment { super.onDestroy(); mHandler.removeCallbacks(mUpdateDurationRunnable); mHandler.removeCallbacks(mStopDtmfToneRunnable); - mHandler = null; mLoadedNumber = null; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + 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); - 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); - - mDialpadViews = Arrays.asList( + initializeViews(view); + initializeClickListeners(); + + List dialpadViews = Arrays.asList( mDialpadContainer.findViewById(R.id.one), mDialpadContainer.findViewById(R.id.two), mDialpadContainer.findViewById(R.id.three), @@ -163,16 +141,58 @@ public class OngoingCallFragment extends Fragment { mDialpadContainer.findViewById(R.id.nine), mDialpadContainer.findViewById(R.id.zero), mDialpadContainer.findViewById(R.id.pound), - mDialpadContainer.findViewById(R.id.star) - ); + mDialpadContainer.findViewById(R.id.star)); // In touch screen, we need to adjust the InCall card for the narrow screen to show the // full dial pad. - for (View dialpadView : mDialpadViews) { + for (View dialpadView : dialpadViews) { dialpadView.setOnTouchListener(mDialpadTouchListener); dialpadView.setOnKeyListener(mDialpadKeyListener); } + mUiCallManager.addListener(mCallListener); + + updateCalls(); + + return view; + } + + private void initializeViews(View parent) { + mRingingCallControls = parent.findViewById(R.id.ringing_call_controls); + mActiveCallControls = parent.findViewById(R.id.active_call_controls); + mEndCallButton = parent.findViewById(R.id.end_call); + mUnholdCallButton = parent.findViewById(R.id.unhold_call); + mMuteButton = parent.findViewById(R.id.mute); + mToggleDialpadButton = parent.findViewById(R.id.toggle_dialpad); + mDialpadContainer = parent.findViewById(R.id.dialpad_container); + mNameTextView = parent.findViewById(R.id.name); + mSecondaryNameTextView = parent.findViewById(R.id.name_secondary); + mStateTextView = parent.findViewById(R.id.info); + mSecondaryStateTextView = parent.findViewById(R.id.info_secondary); + mLargeContactPhotoView = parent.findViewById(R.id.large_contact_photo); + mSmallContactPhotoView = parent.findViewById(R.id.small_contact_photo); + mSecondaryCallContainer = parent.findViewById(R.id.secondary_call_container); + mSecondaryCallControls = parent.findViewById(R.id.secondary_call_controls); + mSwapButton = parent.findViewById(R.id.swap); + mMergeButton = parent.findViewById(R.id.merge); + mAnswerCallButton = parent.findViewById(R.id.answer_call_button); + mRejectCallButton = parent.findViewById(R.id.reject_call_button); + + Context context = getContext(); + FabDrawable drawable = new FabDrawable(context); + drawable.setFabAndStrokeColor(context.getColor(R.color.phone_call)); + mAnswerCallButton.setBackground(drawable); + + drawable = new FabDrawable(context); + drawable.setFabAndStrokeColor(context.getColor(R.color.phone_end_call)); + mEndCallButton.setBackground(drawable); + + drawable = new FabDrawable(context); + drawable.setFabAndStrokeColor(context.getColor(R.color.phone_call)); + mUnholdCallButton.setBackground(drawable); + } + + private void initializeClickListeners() { mAnswerCallButton.setOnClickListener((unusedView) -> { UiCall call = mUiCallManager.getCallWithState(Call.STATE_RINGING); if (call == null) { @@ -181,10 +201,6 @@ public class OngoingCallFragment extends Fragment { } 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); @@ -203,9 +219,6 @@ public class OngoingCallFragment extends Fragment { } 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(); @@ -215,17 +228,9 @@ public class OngoingCallFragment extends Fragment { } 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); - } - }); + mMuteButton.setOnClickListener( + (unusedView) -> mUiCallManager.setMuted(!mUiCallManager.getMuted())); mSwapButton.setOnClickListener((unusedView) -> { UiCall call = mUiCallManager.getPrimaryCall(); @@ -242,13 +247,13 @@ public class OngoingCallFragment extends Fragment { mMergeButton.setOnClickListener((unusedView) -> { UiCall call = mUiCallManager.getPrimaryCall(); - UiCall secondarycall = mUiCallManager.getSecondaryCall(); - if (call == null || mSecondaryCall == null) { + UiCall secondaryCall = mUiCallManager.getSecondaryCall(); + if (call == null || secondaryCall == null) { Log.w(TAG, "There aren't two call to merge."); return; } - mUiCallManager.conference(call, secondarycall); + mUiCallManager.conference(call, secondaryCall); }); mToggleDialpadButton.setOnClickListener((unusedView) -> { @@ -258,12 +263,6 @@ public class OngoingCallFragment extends Fragment { openDialpad(true /*animate*/); } }); - - mUiCallManager.addListener(mCallListener); - - updateCalls(); - - return view; } @Override @@ -278,24 +277,32 @@ public class OngoingCallFragment extends Fragment { trySpeakerAudioRouteIfNecessary(); } - private void rebindViews() { + private void updateCalls() { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "updateCalls(); Primary call: " + mUiCallManager.getPrimaryCall() + + "; Secondary call:" + mUiCallManager.getSecondaryCall()); + } + 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)) { + UiCall primaryCall = mUiCallManager.getPrimaryCall(); + CharSequence disconnectCauseLabel = mLastRemovedCall == null + ? null : mLastRemovedCall.getDisconnectCause(); + if (primaryCall == null && !TextUtils.isEmpty(disconnectCauseLabel)) { closeDialpad(); setStateText(disconnectCauseLabel); return; - } else if (mPrimaryCall == null || mPrimaryCall.getState() == Call.STATE_DISCONNECTED) { + } + + if (primaryCall == null || primaryCall.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) { + } + + if (primaryCall.getState() == Call.STATE_RINGING) { mRingingCallControls.setVisibility(View.VISIBLE); mActiveCallControls.setVisibility(View.GONE); } else { @@ -303,85 +310,19 @@ public class OngoingCallFragment extends Fragment { 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); - } + loadContactPhotoForPrimaryNumber(primaryCall.getNumber()); - String displayName = TelecomUtils.getDisplayName(context, mPrimaryCall); + String displayName = TelecomUtils.getDisplayName(getContext(), primaryCall); mNameTextView.setText(displayName); mNameTextView.setVisibility(TextUtils.isEmpty(displayName) ? View.GONE : View.VISIBLE); - if (mSecondaryCall != null) { - mSecondaryNameTextView.setText( - TelecomUtils.getDisplayName(context, mSecondaryCall)); - } - - switch (mPrimaryCall.getState()) { + Context context = getContext(); + switch (primaryCall.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); + mCallInfoLabel = TelecomUtils.getTypeFromNumber(context, primaryCall.getNumber()); case Call.STATE_CONNECTING: case Call.STATE_DIALING: case Call.STATE_SELECT_PHONE_ACCOUNT: @@ -389,7 +330,7 @@ public class OngoingCallFragment extends Fragment { case Call.STATE_DISCONNECTED: mHandler.removeCallbacks(mUpdateDurationRunnable); String callInfoText = TelecomUtils.getCallInfoText(context, - mPrimaryCall, mCallInfoLabel); + primaryCall, mCallInfoLabel); setStateText(callInfoText); break; case Call.STATE_ACTIVE: @@ -401,17 +342,11 @@ public class OngoingCallFragment extends Fragment { 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())); + Log.w(TAG, "Unhandled call state: " + primaryCall.getState()); } // If it is a voicemail call, open the dialpad (with no animation). - if (primaryNumber != null && primaryNumber.equals( - TelecomUtils.getVoicemailNumber(context))) { + if (Objects.equals(primaryCall.getNumber(), TelecomUtils.getVoicemailNumber(context))) { openDialpad(false /*animate*/); mToggleDialpadButton.setVisibility(View.GONE); } else { @@ -419,7 +354,7 @@ public class OngoingCallFragment extends Fragment { } // Handle the holding case. - if (mPrimaryCall.getState() == Call.STATE_HOLDING) { + if (primaryCall.getState() == Call.STATE_HOLDING) { mEndCallButton.setVisibility(View.GONE); mUnholdCallButton.setVisibility(View.VISIBLE); mMuteButton.setVisibility(View.INVISIBLE); @@ -430,32 +365,100 @@ public class OngoingCallFragment extends Fragment { 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); + updateSecondaryCall(primaryCall, mUiCallManager.getSecondaryCall()); } - private void updateCalls() { - mPrimaryCall = mUiCallManager.getPrimaryCall(); - if (mPrimaryCall != null && mPrimaryCall.getState() == Call.STATE_RINGING) { - // TODO: update when notifications will work + private void updateSecondaryCall(UiCall primaryCall, UiCall secondaryCall) { + if (primaryCall == null || secondaryCall == null) { + mSecondaryCallContainer.setVisibility(View.GONE); + mSecondaryCallControls.setVisibility(View.GONE); + return; } - mSecondaryCall = mUiCallManager.getSecondaryCall(); - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Primary call: " + mPrimaryCall + "\tSecondary call:" + mSecondaryCall); + + mSecondaryCallContainer.setVisibility(View.VISIBLE); + + if (primaryCall.getState() == Call.STATE_ACTIVE + && secondaryCall.getState() == Call.STATE_HOLDING) { + mSecondaryCallControls.setVisibility(View.VISIBLE); + } else { + mSecondaryCallControls.setVisibility(View.GONE); } - rebindViews(); + + Context context = getContext(); + mSecondaryNameTextView.setText(TelecomUtils.getDisplayName(context, secondaryCall)); + mSecondaryStateTextView.setText( + TelecomUtils.callStateToUiString(context, secondaryCall.getState())); + + loadContactPhotoForSecondaryNumber(secondaryCall.getNumber()); } /** - * 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. + * Loads the contact photo associated with the given number and sets it in the views that + * correspond with a primary number. + */ + private void loadContactPhotoForPrimaryNumber(String primaryNumber) { + // Don't reload the image if the number is the same. + if (Objects.equals(primaryNumber, mLoadedNumber)) { + return; + } + + final ContentResolver cr = getContext().getContentResolver(); + BitmapWorkerTask.BitmapRunnable runnable = new BitmapWorkerTask.BitmapRunnable() { + @Override + public void run() { + if (mBitmap != null) { + Resources r = 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); + } + } + }; + mLoadedNumber = primaryNumber; + BitmapWorkerTask.loadBitmap(cr, mLargeContactPhotoView, primaryNumber, runnable); + } + + /** + * Loads the contact photo associated with the given number and sets it in the views that + * correspond to a secondary number. + */ + private void loadContactPhotoForSecondaryNumber(String secondaryNumber) { + BitmapWorkerTask.BitmapRunnable runnable = new BitmapWorkerTask.BitmapRunnable() { + @Override + public void run() { + if (mBitmap != null) { + mLargeContactPhotoView.setImageBitmap(mBitmap); + } else { + mLargeContactPhotoView.setImageResource(R.drawable.logo_avatar); + } + } + }; + + Context context = getContext(); + BitmapWorkerTask.loadBitmap(context.getContentResolver(), mLargeContactPhotoView, + secondaryNumber, runnable); + + int scrimColor = context.getColor(R.color.phone_secondary_call_scrim); + mLargeContactPhotoView.setColorFilter(scrimColor); + } + + private void setStateText(CharSequence stateText) { + mStateTextView.setText(stateText); + mStateTextView.setVisibility(TextUtils.isEmpty(stateText) ? View.GONE : View.VISIBLE); + } + + /** + * If the phone is using bluetooth, then do nothing. If the phone is not using bluetooth: + *

+ *

    + *
  1. If the phone supports bluetooth, use it. + *
  2. If the phone doesn't support bluetooth and support speaker, use speaker + *
  3. Otherwise, do nothing. Hopefully no phones won't have bt or speaker. + *
*/ private void trySpeakerAudioRouteIfNecessary() { if (mUiCallManager == null) { @@ -519,12 +522,12 @@ public class OngoingCallFragment extends Fragment { } if (event.getAction() == MotionEvent.ACTION_DOWN) { v.setPressed(true); - mUiCallManager.playDtmfTone(mPrimaryCall, digit); + mUiCallManager.playDtmfTone(mUiCallManager.getPrimaryCall(), digit); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { v.setPressed(false); v.performClick(); - mUiCallManager.stopDtmfTone(mPrimaryCall); + mUiCallManager.stopDtmfTone(mUiCallManager.getPrimaryCall()); return true; } @@ -547,11 +550,11 @@ public class OngoingCallFragment extends Fragment { if (event.getAction() == KeyEvent.ACTION_DOWN) { v.setPressed(true); - mUiCallManager.playDtmfTone(mPrimaryCall, digit); + mUiCallManager.playDtmfTone(mUiCallManager.getPrimaryCall(), digit); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { v.setPressed(false); - mUiCallManager.stopDtmfTone(mPrimaryCall); + mUiCallManager.stopDtmfTone(mUiCallManager.getPrimaryCall()); return true; } @@ -562,23 +565,21 @@ public class OngoingCallFragment extends Fragment { private final Runnable mUpdateDurationRunnable = new Runnable() { @Override public void run() { - if (mPrimaryCall.getState() != Call.STATE_ACTIVE) { + UiCall primaryCall = mUiCallManager.getPrimaryCall(); + if (primaryCall.getState() != Call.STATE_ACTIVE) { return; } String callInfoText = TelecomUtils.getCallInfoText(getContext(), - mPrimaryCall, mCallInfoLabel); + primaryCall, 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 Runnable mStopDtmfToneRunnable = + () -> mUiCallManager.stopDtmfTone(mUiCallManager.getPrimaryCall()); + private final class DialpadAnimation extends Animation { private static final int DURATION = 300; private static final float MAX_SCRIM_ALPHA = 0.6f; @@ -587,17 +588,16 @@ public class OngoingCallFragment extends Fragment { private final int mScrimColor; private final boolean mReverse; - public DialpadAnimation(Context context, boolean reverse) { + DialpadAnimation(Context context, boolean reverse) { this(context, reverse, true); } - public DialpadAnimation(Context context, boolean reverse, boolean animate) { + 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); + mStartingTranslation = context.getResources().getDimensionPixelOffset( + R.dimen.in_call_card_dialpad_translation_x); + mScrimColor = context.getColor(R.color.phone_theme); mReverse = reverse; } @@ -624,11 +624,10 @@ public class OngoingCallFragment extends Fragment { } private final CallListener mCallListener = new CallListener() { - @Override public void onCallAdded(UiCall call) { if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "on call added"); + Log.d(TAG, "onCallAdded(); call: " + call); } updateCalls(); trySpeakerAudioRouteIfNecessary(); @@ -637,7 +636,7 @@ public class OngoingCallFragment extends Fragment { @Override public void onCallRemoved(UiCall call) { if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "on call removed"); + Log.d(TAG, "onCallRemoved(); call: " + call); } mLastRemovedCall = call; updateCalls(); @@ -647,7 +646,9 @@ public class OngoingCallFragment extends Fragment { public void onAudioStateChanged(boolean isMuted, int audioRoute, int supportedAudioRouteMask) { if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "on audio state changed"); + Log.d(TAG, String.format("onAudioStateChanged(); isMuted: %b, audioRoute: %d, " + + " supportedAudioRouteMask: %d", isMuted, audioRoute, + supportedAudioRouteMask)); } mMuteButton.setActivated(isMuted); trySpeakerAudioRouteIfNecessary(); @@ -656,7 +657,7 @@ public class OngoingCallFragment extends Fragment { @Override public void onStateChanged(UiCall call, int state) { if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "onStateChanged"); + Log.d(TAG, "onStateChanged(); call: " + call + ", state: " + state); } updateCalls(); } @@ -664,7 +665,7 @@ public class OngoingCallFragment extends Fragment { @Override public void onCallUpdated(UiCall call) { if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "onCallUpdated"); + Log.d(TAG, "onCallUpdated(); call: " + call); } updateCalls(); } diff --git a/src/com/android/car/dialer/StrequentsAdapter.java b/src/com/android/car/dialer/StrequentsAdapter.java index 78f4d3e6..981fb977 100644 --- a/src/com/android/car/dialer/StrequentsAdapter.java +++ b/src/com/android/car/dialer/StrequentsAdapter.java @@ -40,12 +40,11 @@ import java.util.List; /** * Adapter class for populating Contact data as loaded from the DB to an AA GroupingRecyclerView. - * - *

It handles two types of contacts: - * + * It handles two types of contacts: + *

*

    - *
  • Strequent contacts (starred and/or frequent)
  • - *
  • Last call contact
  • + *
  • Strequent contacts (starred and/or frequent) + *
  • Last call contact *
*/ public class StrequentsAdapter extends RecyclerView.Adapter @@ -84,10 +83,6 @@ public class StrequentsAdapter extends RecyclerView.Adapter mStrequentsListener = listener; } - public void setFocusChangeListener(@Nullable View.OnFocusChangeListener listener) { - mFocusChangeListener = listener; - } - public void setLastCallCursor(@Nullable Cursor cursor) { Log.e("StrequentsAdapter", "cursor: " + cursor); Log.e("StrequentsAdapter", "count: " + (cursor == null ? 0 : cursor.getCount())); @@ -304,7 +299,7 @@ public class StrequentsAdapter extends RecyclerView.Adapter // If we set this to 0, getRelativeTime will return null and no relative time // will be displayed. long millis = column == -1 ? 0 : cursor.getLong(column); - StringBuffer secondaryText = new StringBuffer(); + StringBuilder secondaryText = new StringBuilder(); CharSequence relativeDate = getRelativeTime(millis); if (!isVoicemail) { CharSequence type = TelecomUtils.getTypeFromNumber(mContext, number); diff --git a/src/com/android/car/dialer/StrequentsFragment.java b/src/com/android/car/dialer/StrequentsFragment.java index 69279c82..d0918336 100644 --- a/src/com/android/car/dialer/StrequentsFragment.java +++ b/src/com/android/car/dialer/StrequentsFragment.java @@ -18,7 +18,6 @@ package com.android.car.dialer; import android.content.ContentResolver; import android.content.Context; import android.content.CursorLoader; -import android.content.Loader; import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Canvas; @@ -27,13 +26,13 @@ import android.os.Bundle; import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; -import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; + import com.android.car.dialer.telecom.PhoneLoader; import com.android.car.dialer.telecom.UiCallManager; import com.android.car.view.PagedListView; @@ -45,8 +44,8 @@ import com.android.car.view.PagedListView; public class StrequentsFragment extends Fragment { private static final String TAG = "Em.StrequentsFrag"; - public static final String KEY_MAX_CLICKS = "max_clicks"; - public static final int DEFAULT_MAX_CLICKS = 6; + private static final String KEY_MAX_CLICKS = "max_clicks"; + private static final int DEFAULT_MAX_CLICKS = 6; private UiCallManager mUiCallManager; private StrequentsAdapter mAdapter; @@ -85,7 +84,6 @@ public class StrequentsFragment extends Fragment { mListView = (PagedListView) view.findViewById(R.id.list_view); mListView.getLayoutManager().setOffsetRows(true); - Bundle args = getArguments(); mSpeedialCursorLoader = PhoneLoader.registerCallObserver(PhoneLoader.CALL_TYPE_SPEED_DIAL, mContext, (loader, cursor) -> { if (Log.isLoggable(TAG, Log.DEBUG)) { @@ -115,9 +113,10 @@ public class StrequentsFragment extends Fragment { false, new CallLogContentObserver(new Handler())); // Maximum number of forward acting clicks the user can perform - - int maxClicks = args.getInt(KEY_MAX_CLICKS, - DEFAULT_MAX_CLICKS /* G.maxForwardClicks.get() */); + Bundle args = getArguments(); + int maxClicks = args == null + ? DEFAULT_MAX_CLICKS + : args.getInt(KEY_MAX_CLICKS, DEFAULT_MAX_CLICKS /* G.maxForwardClicks.get() */); // We want to show one fewer page than max clicks to allow clicking on an item, // but, the first page is "free" since it doesn't take any clicks to show final int maxPages = maxClicks < 0 ? -1 : maxClicks; @@ -299,7 +298,7 @@ public class StrequentsFragment extends Fragment { RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); int bottom = child.getBottom() + lp.bottomMargin - + Math.round(ViewCompat.getTranslationY(child)); + + Math.round(child.getTranslationY()); int top = bottom - mDividerHeight; if (top >= c.getHeight() || top < 0) { diff --git a/src/com/android/car/dialer/TelecomActivity.java b/src/com/android/car/dialer/TelecomActivity.java index 33049ede..d59fcc23 100644 --- a/src/com/android/car/dialer/TelecomActivity.java +++ b/src/com/android/car/dialer/TelecomActivity.java @@ -22,6 +22,8 @@ import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; import android.support.v4.app.Fragment; import android.support.v7.widget.SearchView; import android.support.v7.widget.Toolbar.LayoutParams; @@ -57,29 +59,29 @@ import static android.support.v7.widget.Toolbar.LayoutParams.MATCH_PARENT; */ public class TelecomActivity extends CarDrawerActivity implements DialerFragment.DialerBackButtonListener { - private static final String TAG = "Em.TelecomActivity"; + private static final String TAG = "TelecomActivity"; private static final String ACTION_ANSWER_CALL = "com.android.car.dialer.ANSWER_CALL"; private static final String ACTION_END_CALL = "com.android.car.dialer.END_CALL"; + private static final String DIALER_BACKSTACK = "DialerBackstack"; - private static final String FRAGMENT_CLASS_KEY = "FRAGMENT_CLASS_KEY"; + private static final String CONTENT_FRAGMENT_TAG = "CONTENT_FRAGMENT_TAG"; + private static final String DIALER_FRAGMENT_TAG = "DIALER_FRAGMENT_TAG"; private final UiBluetoothMonitor.Listener mBluetoothListener = this::updateCurrentFragment; private UiCallManager mUiCallManager; private UiBluetoothMonitor mUiBluetoothMonitor; - private Fragment mCurrentFragment; - private String mCurrentFragmentName; - - private int mLastNoHfpMessageId; - private StrequentsFragment mSpeedDialFragment; - private Fragment mOngoingCallFragment; - - private DialerFragment mDialerFragment; - private boolean mDialerFragmentOpened; - - private SearchView mSearchView; + /** + * Whether or not it is safe to make transactions on the + * {@link android.support.v4.app.FragmentManager}. This variable prevents a possible exception + * when calling commit() on the FragmentManager. + * + *

The default value is {@code true} because it is only after + * {@link #onSaveInstanceState(Bundle)} that fragment commits are not allowed. + */ + private boolean mAllowFragmentCommits = true; @Override protected void onCreate(Bundle savedInstanceState) { @@ -93,14 +95,6 @@ public class TelecomActivity extends CarDrawerActivity implements mUiCallManager = new UiCallManager(this); mUiBluetoothMonitor = new UiBluetoothMonitor(this); - - if (savedInstanceState != null) { - mCurrentFragmentName = savedInstanceState.getString(FRAGMENT_CLASS_KEY); - } - - if (vdebug()) { - Log.d(TAG, "onCreate done, mCurrentFragmentName: " + mCurrentFragmentName); - } } @Override @@ -131,9 +125,7 @@ public class TelecomActivity extends CarDrawerActivity implements SearchView searchView = (SearchView) searchItem.getActionView(); searchView.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT)); - searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); - mSearchView = searchView; return super.onCreateOptionsMenu(menu); } @@ -149,17 +141,17 @@ public class TelecomActivity extends CarDrawerActivity implements } @Override - protected void onPause() { - super.onPause(); + protected void onStop() { + super.onStop(); mUiCallManager.removeListener(mCarCallListener); mUiBluetoothMonitor.removeListener(mBluetoothListener); } @Override public void onSaveInstanceState(Bundle outState) { - if (mCurrentFragment != null) { - outState.putString(FRAGMENT_CLASS_KEY, mCurrentFragmentName); - } + // A transaction can only be committed with this method prior to its containing activity + // saving its state. + mAllowFragmentCommits = false; super.onSaveInstanceState(outState); } @@ -170,11 +162,15 @@ public class TelecomActivity extends CarDrawerActivity implements } @Override - protected void onResume() { + protected void onStart() { if (vdebug()) { - Log.d(TAG, "onResume"); + Log.d(TAG, "onStart"); } - super.onResume(); + super.onStart(); + + // Fragment commits are not allowed once the Activity's state has been saved. Once + // onStart() has been called, the FragmentManager should now allow commits. + mAllowFragmentCommits = true; // Update the current fragment before handling the intent so that any UI updates in // handleIntent() is not overridden by updateCurrentFragment(). @@ -185,27 +181,6 @@ public class TelecomActivity extends CarDrawerActivity implements mUiBluetoothMonitor.addListener(mBluetoothListener); } - // TODO: move to base class. - private void setContentFragmentWithAnimations(Fragment fragment, int enter, int exit) { - if (vdebug()) { - Log.d(TAG, "setContentFragmentWithAnimations: " + fragment); - } - - maybeHideDialer(); - - getSupportFragmentManager().beginTransaction() - .setCustomAnimations(enter, exit) - .replace(getContentContainerId(), fragment) - .commitAllowingStateLoss(); - - mCurrentFragmentName = fragment.getClass().getSimpleName(); - mCurrentFragment = fragment; - - if (vdebug()) { - Log.d(TAG, "setContentFragmentWithAnimations, fragmentName:" + mCurrentFragmentName); - } - } - private void handleIntent() { Intent intent = getIntent(); String action = intent != null ? intent.getAction() : null; @@ -240,14 +215,15 @@ public class TelecomActivity extends CarDrawerActivity implements case Intent.ACTION_DIAL: String number = PhoneNumberUtils.getNumberFromIntent(intent, this); - if (!(mCurrentFragment instanceof NoHfpFragment)) { - showDialerWithNumber(number); + if (!(getCurrentFragment() instanceof NoHfpFragment)) { + showDialer(number); } break; case SEARCH_SUGGESTION_CLICKED: Uri contactUri = intent.getData(); - showContactDetailFragment(contactUri); + setContentFragment( + ContactDetailsFragment.newInstance(contactUri, mUiCallManager)); break; default: @@ -258,15 +234,13 @@ public class TelecomActivity extends CarDrawerActivity implements } /** - * Will switch to the drawer or no-hfp fragment as necessary. + * Updates the content fragment of this Activity based on the state of the application. */ private void updateCurrentFragment() { if (vdebug()) { - Log.d(TAG, "updateCurrentFragment"); + Log.d(TAG, "updateCurrentFragment()"); } - // TODO: do nothing when activity isFinishing() == true. - boolean callEmpty = mUiCallManager.getCalls().isEmpty(); if (!mUiBluetoothMonitor.isBluetoothEnabled() && callEmpty) { showNoHfpFragment(R.string.bluetooth_disabled); @@ -279,15 +253,13 @@ public class TelecomActivity extends CarDrawerActivity implements if (vdebug()) { Log.d(TAG, "ongoingCall: " + ongoingCall + ", mCurrentFragment: " - + mCurrentFragment); + + getCurrentFragment()); } - if (ongoingCall == null && mCurrentFragment instanceof OngoingCallFragment) { + if (ongoingCall == null && getCurrentFragment() instanceof OngoingCallFragment) { showSpeedDialFragment(); } else if (ongoingCall != null) { showOngoingCallFragment(); - } else if (DialerFragment.class.getSimpleName().equals(mCurrentFragmentName)) { - showDialer(); } else { showSpeedDialFragment(); } @@ -303,20 +275,15 @@ public class TelecomActivity extends CarDrawerActivity implements Log.d(TAG, "showSpeedDialFragment"); } - if (mCurrentFragment instanceof StrequentsFragment) { + if (!mAllowFragmentCommits || getCurrentFragment() instanceof StrequentsFragment) { return; } - if (mSpeedDialFragment == null) { - mSpeedDialFragment = StrequentsFragment.newInstance(mUiCallManager); - Bundle args = new Bundle(); - mSpeedDialFragment.setArguments(args); - } - - if (mCurrentFragment instanceof DialerFragment) { - setContentFragmentWithSlideAndDelayAnimation(mSpeedDialFragment); + Fragment fragment = StrequentsFragment.newInstance(mUiCallManager); + if (getCurrentFragment() instanceof DialerFragment) { + setContentFragmentWithSlideAndDelayAnimation(fragment); } else { - setContentFragmentWithFadeAnimation(mSpeedDialFragment); + setContentFragmentWithFadeAnimation(fragment); } } @@ -324,40 +291,39 @@ public class TelecomActivity extends CarDrawerActivity implements if (vdebug()) { Log.d(TAG, "showOngoingCallFragment"); } - if (mCurrentFragment instanceof OngoingCallFragment) { + if (!mAllowFragmentCommits || getCurrentFragment() instanceof OngoingCallFragment) { closeDrawer(); return; } - if (mOngoingCallFragment == null) { - mOngoingCallFragment = - OngoingCallFragment.newInstance(mUiCallManager, mUiBluetoothMonitor); + Fragment fragment = OngoingCallFragment.newInstance(mUiCallManager, mUiBluetoothMonitor); + setContentFragmentWithFadeAnimation(fragment); + closeDrawer(); + } + + private void showDialer() { + if (vdebug()) { + Log.d(TAG, "showDialer"); } - setContentFragmentWithFadeAnimation(mOngoingCallFragment); - closeDrawer(); + showDialer(null /* dialNumber */); } /** - * Displays the {@link DialerFragment} on top of the contents of the TelecomActivity. + * Displays the {@link DialerFragment} and initialize it with the given phone number. */ - private void showDialer() { + private void showDialer(@Nullable String dialNumber) { if (vdebug()) { - Log.d(TAG, "showDialer"); + Log.d(TAG, "showDialer with number: " + dialNumber); } - if (mDialerFragmentOpened) { + if (!mAllowFragmentCommits || + getSupportFragmentManager().findFragmentByTag(DIALER_FRAGMENT_TAG) != null) { return; } - if (mDialerFragment == null) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "showDialer: creating dialer"); - } - - mDialerFragment = DialerFragment.newInstance(mUiCallManager); - mDialerFragment.setDialerBackButtonListener(this); - } + Fragment fragment = + DialerFragment.newInstance(mUiCallManager, this /* listener */, dialNumber); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "adding dialer to fragment backstack"); @@ -367,11 +333,9 @@ public class TelecomActivity extends CarDrawerActivity implements getSupportFragmentManager().beginTransaction() .setCustomAnimations(R.anim.telecom_slide_in, R.anim.telecom_slide_out, R.anim.telecom_slide_in, R.anim.telecom_slide_out) - .add(getContentContainerId(), mDialerFragment) + .add(getContentContainerId(), fragment, DIALER_FRAGMENT_TAG) .addToBackStack(DIALER_BACKSTACK) - .commitAllowingStateLoss(); - - mDialerFragmentOpened = true; + .commit(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "done adding fragment to backstack"); @@ -382,10 +346,10 @@ public class TelecomActivity extends CarDrawerActivity implements * Checks if the dialpad fragment is opened and hides it if it is. */ private void maybeHideDialer() { - if (mDialerFragmentOpened) { - // Dismiss the dialer by removing it from the back stack. + // The dialer is the only fragment to be added to the back stack. Dismiss the dialer by + // removing it from the back stack. + if (getSupportFragmentManager().getBackStackEntryCount() > 0) { getSupportFragmentManager().popBackStack(); - mDialerFragmentOpened = false; } } @@ -394,30 +358,19 @@ public class TelecomActivity extends CarDrawerActivity implements maybeHideDialer(); } - private void showDialerWithNumber(String number) { - showDialer(); - mDialerFragment.setDialNumber(number); - } - - private void showNoHfpFragment(int stringResId) { - if (mCurrentFragment instanceof NoHfpFragment && stringResId == mLastNoHfpMessageId) { + private void showNoHfpFragment(@StringRes int stringResId) { + if (!mAllowFragmentCommits) { return; } - mLastNoHfpMessageId = stringResId; String errorMessage = getString(stringResId); - NoHfpFragment frag = new NoHfpFragment(); - frag.setErrorMessage(errorMessage); - setContentFragment(frag); - mCurrentFragment = frag; - } + Fragment currentFragment = getCurrentFragment(); - - private void showContactDetailFragment(Uri contactUri) { - ContactDetailsFragment fragment = - ContactDetailsFragment.newInstance(contactUri, mUiCallManager); - setContentFragment(fragment); - mCurrentFragment = fragment; + if (currentFragment instanceof NoHfpFragment) { + ((NoHfpFragment) currentFragment).setErrorMessage(errorMessage); + } else { + setContentFragment(NoHfpFragment.newInstance(errorMessage)); + } } private void setContentFragmentWithSlideAndDelayAnimation(Fragment fragment) { @@ -436,6 +389,41 @@ public class TelecomActivity extends CarDrawerActivity implements R.anim.telecom_fade_in, R.anim.telecom_fade_out); } + private void setContentFragmentWithAnimations(Fragment fragment, int enter, int exit) { + if (vdebug()) { + Log.d(TAG, "setContentFragmentWithAnimations: " + fragment); + } + + maybeHideDialer(); + getSupportFragmentManager().beginTransaction() + .setCustomAnimations(enter, exit) + .replace(getContentContainerId(), fragment, CONTENT_FRAGMENT_TAG) + .commitNow(); + } + + /** + * Sets the fragment that will be shown as the main content of this Activity. Note that this + * fragment is not always visible. In particular, the dialer fragment can show up on top of this + * fragment. + */ + private void setContentFragment(Fragment fragment) { + maybeHideDialer(); + getSupportFragmentManager().beginTransaction() + .replace(getContentContainerId(), fragment, CONTENT_FRAGMENT_TAG) + .commitNow(); + } + + /** + * Returns the fragment that is currently being displayed as the content view. Note that this + * is not necessarily the fragment that is visible. For example, the returned fragment + * could be the content, but the dial fragment is being displayed on top of it. Check for + * the existence of the dial fragment with the TAG {@link #DIALER_FRAGMENT_TAG}. + */ + @Nullable + private Fragment getCurrentFragment() { + return getSupportFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG); + } + private final CallListener mCarCallListener = new UiCallManager.CallListener() { @Override public void onCallAdded(UiCall call) { @@ -470,12 +458,6 @@ public class TelecomActivity extends CarDrawerActivity implements } }; - private void setContentFragment(Fragment fragment) { - getSupportFragmentManager().beginTransaction() - .replace(getContentContainerId(), fragment) - .commit(); - } - private static boolean vdebug() { return Log.isLoggable(TAG, Log.DEBUG); } @@ -506,9 +488,10 @@ public class TelecomActivity extends CarDrawerActivity implements @Override public void populateViewHolder(DrawerItemViewHolder holder, int position) { - holder.getTitle().setText(mItems.get(position).mTitle); - holder.getText().setText(mItems.get(position).mText); - holder.getIcon().setImageBitmap(mItems.get(position).mIcon); + CallLogListingTask.CallLogItem item = mItems.get(position); + holder.getTitle().setText(item.mTitle); + holder.getText().setText(item.mText); + holder.getIcon().setImageBitmap(item.mIcon); } @Override -- cgit v1.2.3