| /* |
| * 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.app.SearchManager; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.graphics.PorterDuff; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.support.v4.app.Fragment; |
| import android.support.v7.widget.SearchView; |
| import android.support.v7.widget.Toolbar.LayoutParams; |
| import android.telecom.Call; |
| import android.telephony.PhoneNumberUtils; |
| import android.util.Log; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.MenuInflater; |
| |
| import com.android.car.app.CarDrawerActivity; |
| import com.android.car.app.CarDrawerAdapter; |
| import com.android.car.app.DrawerItemViewHolder; |
| import com.android.car.dialer.telecom.PhoneLoader; |
| 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.List; |
| |
| import static android.provider.Contacts.Intents.SEARCH_SUGGESTION_CLICKED; |
| import static android.support.v7.widget.Toolbar.LayoutParams.MATCH_PARENT; |
| |
| /** |
| * Main activity for the Dialer app. Displays different fragments depending on call and |
| * connectivity status: |
| * <ul> |
| * <li>OngoingCallFragment |
| * <li>NoHfpFragment |
| * <li>DialerFragment |
| * <li>StrequentFragment |
| * </ul> |
| */ |
| public class TelecomActivity extends CarDrawerActivity implements |
| DialerFragment.DialerBackButtonListener { |
| private static final String TAG = "Em.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 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; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| |
| if (vdebug()) { |
| Log.d(TAG, "onCreate"); |
| } |
| getWindow().getDecorView().setBackgroundColor(getColor(R.color.phone_theme)); |
| setTitle(getString(R.string.phone_app_name)); |
| |
| 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 |
| public boolean onCreateOptionsMenu(Menu menu) { |
| if (vdebug()) { |
| Log.d(TAG, "onCreateOptionsMenu"); |
| } |
| MenuInflater inflater = getMenuInflater(); |
| inflater.inflate(R.menu.options_menu, menu); |
| |
| SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); |
| MenuItem searchItem = menu.findItem(R.id.search); |
| searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { |
| @Override |
| public boolean onMenuItemActionExpand(MenuItem item) { |
| return true; |
| } |
| |
| @Override |
| public boolean onMenuItemActionCollapse(MenuItem item) { |
| // The back arrow on the search view causes this to trigger. It isn't really a back |
| // at all so we can't use the back stack and instead we just set the speed dial |
| // fragment manually. |
| showSpeedDialFragment(); |
| return true; |
| } |
| }); |
| |
| SearchView searchView = (SearchView) searchItem.getActionView(); |
| searchView.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT)); |
| |
| searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); |
| mSearchView = searchView; |
| |
| return super.onCreateOptionsMenu(menu); |
| } |
| |
| @Override |
| protected void onDestroy() { |
| super.onDestroy(); |
| if (vdebug()) { |
| Log.d(TAG, "onDestroy"); |
| } |
| mUiBluetoothMonitor.tearDown(); |
| mUiCallManager = null; |
| } |
| |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| mUiCallManager.removeListener(mCarCallListener); |
| mUiBluetoothMonitor.removeListener(mBluetoothListener); |
| } |
| |
| @Override |
| public void onSaveInstanceState(Bundle outState) { |
| if (mCurrentFragment != null) { |
| outState.putString(FRAGMENT_CLASS_KEY, mCurrentFragmentName); |
| } |
| super.onSaveInstanceState(outState); |
| } |
| |
| @Override |
| protected void onNewIntent(Intent i) { |
| super.onNewIntent(i); |
| setIntent(i); |
| } |
| |
| @Override |
| protected void onResume() { |
| if (vdebug()) { |
| Log.d(TAG, "onResume"); |
| } |
| super.onResume(); |
| |
| // Update the current fragment before handling the intent so that any UI updates in |
| // handleIntent() is not overridden by updateCurrentFragment(). |
| updateCurrentFragment(); |
| handleIntent(); |
| |
| mUiCallManager.addListener(mCarCallListener); |
| 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; |
| |
| if (vdebug()) { |
| Log.d(TAG, "handleIntent, intent: " + intent + ", action: " + action); |
| } |
| |
| if (action == null || action.length() == 0) { |
| return; |
| } |
| |
| UiCall ringingCall; |
| switch (action) { |
| case ACTION_ANSWER_CALL: |
| ringingCall = mUiCallManager.getCallWithState(Call.STATE_RINGING); |
| if (ringingCall == null) { |
| Log.e(TAG, "Unable to answer ringing call. There is none."); |
| } else { |
| mUiCallManager.answerCall(ringingCall); |
| } |
| break; |
| |
| case ACTION_END_CALL: |
| ringingCall = mUiCallManager.getCallWithState(Call.STATE_RINGING); |
| if (ringingCall == null) { |
| Log.e(TAG, "Unable to end ringing call. There is none."); |
| } else { |
| mUiCallManager.disconnectCall(ringingCall); |
| } |
| break; |
| |
| case Intent.ACTION_DIAL: |
| String number = PhoneNumberUtils.getNumberFromIntent(intent, this); |
| if (!(mCurrentFragment instanceof NoHfpFragment)) { |
| showDialerWithNumber(number); |
| } |
| break; |
| |
| case SEARCH_SUGGESTION_CLICKED: |
| Uri contactUri = intent.getData(); |
| showContactDetailFragment(contactUri); |
| break; |
| |
| default: |
| // Do nothing. |
| } |
| |
| setIntent(null); |
| } |
| |
| /** |
| * Will switch to the drawer or no-hfp fragment as necessary. |
| */ |
| private void updateCurrentFragment() { |
| if (vdebug()) { |
| 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); |
| } else if (!mUiBluetoothMonitor.isBluetoothPaired() && callEmpty) { |
| showNoHfpFragment(R.string.bluetooth_unpaired); |
| } else if (!mUiBluetoothMonitor.isHfpConnected() && callEmpty) { |
| showNoHfpFragment(R.string.no_hfp); |
| } else { |
| UiCall ongoingCall = mUiCallManager.getPrimaryCall(); |
| |
| if (vdebug()) { |
| Log.d(TAG, "ongoingCall: " + ongoingCall + ", mCurrentFragment: " |
| + mCurrentFragment); |
| } |
| |
| if (ongoingCall == null && mCurrentFragment instanceof OngoingCallFragment) { |
| showSpeedDialFragment(); |
| } else if (ongoingCall != null) { |
| showOngoingCallFragment(); |
| } else if (DialerFragment.class.getSimpleName().equals(mCurrentFragmentName)) { |
| showDialer(); |
| } else { |
| showSpeedDialFragment(); |
| } |
| } |
| |
| if (vdebug()) { |
| Log.d(TAG, "updateCurrentFragment: done"); |
| } |
| } |
| |
| private void showSpeedDialFragment() { |
| if (vdebug()) { |
| Log.d(TAG, "showSpeedDialFragment"); |
| } |
| |
| if (mCurrentFragment instanceof StrequentsFragment) { |
| return; |
| } |
| |
| if (mSpeedDialFragment == null) { |
| mSpeedDialFragment = StrequentsFragment.newInstance(mUiCallManager); |
| Bundle args = new Bundle(); |
| mSpeedDialFragment.setArguments(args); |
| } |
| |
| if (mCurrentFragment instanceof DialerFragment) { |
| setContentFragmentWithSlideAndDelayAnimation(mSpeedDialFragment); |
| } else { |
| setContentFragmentWithFadeAnimation(mSpeedDialFragment); |
| } |
| } |
| |
| private void showOngoingCallFragment() { |
| if (vdebug()) { |
| Log.d(TAG, "showOngoingCallFragment"); |
| } |
| if (mCurrentFragment instanceof OngoingCallFragment) { |
| closeDrawer(); |
| return; |
| } |
| |
| if (mOngoingCallFragment == null) { |
| mOngoingCallFragment = |
| OngoingCallFragment.newInstance(mUiCallManager, mUiBluetoothMonitor); |
| } |
| |
| setContentFragmentWithFadeAnimation(mOngoingCallFragment); |
| closeDrawer(); |
| } |
| |
| /** |
| * Displays the {@link DialerFragment} on top of the contents of the TelecomActivity. |
| */ |
| private void showDialer() { |
| if (vdebug()) { |
| Log.d(TAG, "showDialer"); |
| } |
| |
| if (mDialerFragmentOpened) { |
| return; |
| } |
| |
| if (mDialerFragment == null) { |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "showDialer: creating dialer"); |
| } |
| |
| mDialerFragment = DialerFragment.newInstance(mUiCallManager); |
| mDialerFragment.setDialerBackButtonListener(this); |
| } |
| |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "adding dialer to fragment backstack"); |
| } |
| |
| // Add the dialer fragment to the backstack so that it can be popped off to dismiss it. |
| 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) |
| .addToBackStack(DIALER_BACKSTACK) |
| .commitAllowingStateLoss(); |
| |
| mDialerFragmentOpened = true; |
| |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "done adding fragment to backstack"); |
| } |
| } |
| |
| /** |
| * 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. |
| getSupportFragmentManager().popBackStack(); |
| mDialerFragmentOpened = false; |
| } |
| } |
| |
| @Override |
| public void onDialerBackClick() { |
| maybeHideDialer(); |
| } |
| |
| private void showDialerWithNumber(String number) { |
| showDialer(); |
| mDialerFragment.setDialNumber(number); |
| } |
| |
| private void showNoHfpFragment(int stringResId) { |
| if (mCurrentFragment instanceof NoHfpFragment && stringResId == mLastNoHfpMessageId) { |
| return; |
| } |
| |
| mLastNoHfpMessageId = stringResId; |
| String errorMessage = getString(stringResId); |
| NoHfpFragment frag = new NoHfpFragment(); |
| frag.setErrorMessage(errorMessage); |
| setContentFragment(frag); |
| mCurrentFragment = frag; |
| } |
| |
| |
| private void showContactDetailFragment(Uri contactUri) { |
| ContactDetailsFragment fragment = ContactDetailsFragment.newInstance(contactUri); |
| setContentFragment(fragment); |
| mCurrentFragment = fragment; |
| } |
| |
| private void setContentFragmentWithSlideAndDelayAnimation(Fragment fragment) { |
| if (vdebug()) { |
| Log.d(TAG, "setContentFragmentWithSlideAndDelayAnimation, fragment: " + fragment); |
| } |
| setContentFragmentWithAnimations(fragment, |
| R.anim.telecom_slide_in_with_delay, R.anim.telecom_slide_out); |
| } |
| |
| private void setContentFragmentWithFadeAnimation(Fragment fragment) { |
| if (vdebug()) { |
| Log.d(TAG, "setContentFragmentWithFadeAnimation, fragment: " + fragment); |
| } |
| setContentFragmentWithAnimations(fragment, |
| R.anim.telecom_fade_in, R.anim.telecom_fade_out); |
| } |
| |
| private final CallListener mCarCallListener = new UiCallManager.CallListener() { |
| @Override |
| public void onCallAdded(UiCall call) { |
| if (vdebug()) { |
| Log.d(TAG, "onCallAdded"); |
| } |
| updateCurrentFragment(); |
| } |
| |
| @Override |
| public void onCallRemoved(UiCall call) { |
| if (vdebug()) { |
| Log.d(TAG, "onCallRemoved"); |
| } |
| updateCurrentFragment(); |
| } |
| |
| @Override |
| public void onStateChanged(UiCall call, int state) { |
| if (vdebug()) { |
| Log.d(TAG, "onStateChanged"); |
| } |
| updateCurrentFragment(); |
| } |
| |
| @Override |
| public void onCallUpdated(UiCall call) { |
| if (vdebug()) { |
| Log.d(TAG, "onCallUpdated"); |
| } |
| updateCurrentFragment(); |
| } |
| }; |
| |
| private void setContentFragment(Fragment fragment) { |
| getSupportFragmentManager().beginTransaction() |
| .replace(getContentContainerId(), fragment) |
| .commit(); |
| } |
| |
| private static boolean vdebug() { |
| return Log.isLoggable(TAG, Log.DEBUG); |
| } |
| |
| @Override |
| protected CarDrawerAdapter getRootAdapter() { |
| return new DialerRootAdapter(); |
| } |
| |
| class CallLogAdapter extends CarDrawerAdapter { |
| private List<CallLogListingTask.CallLogItem> mItems; |
| |
| public CallLogAdapter(int titleResId, List<CallLogListingTask.CallLogItem> items) { |
| super(TelecomActivity.this, true /* showDisabledListOnEmpty */); |
| setTitle(getString(titleResId)); |
| mItems = items; |
| } |
| |
| @Override |
| protected boolean usesSmallLayout(int position) { |
| return false; |
| } |
| |
| @Override |
| protected int getActualItemCount() { |
| return mItems.size(); |
| } |
| |
| @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); |
| } |
| |
| @Override |
| public void onItemClick(int position) { |
| closeDrawer(); |
| mUiCallManager.safePlaceCall(mItems.get(position).mNumber, false); |
| } |
| } |
| |
| private class DialerRootAdapter extends CarDrawerAdapter { |
| private static final int ITEM_DIAL = 0; |
| private static final int ITEM_CALLLOG_ALL = 1; |
| private static final int ITEM_CALLLOG_MISSED = 2; |
| private static final int ITEM_MAX = 3; |
| |
| DialerRootAdapter() { |
| super(TelecomActivity.this, false /* showDisabledListOnEmpty */); |
| setTitle(getString(R.string.phone_app_name)); |
| } |
| |
| @Override |
| protected int getActualItemCount() { |
| return ITEM_MAX; |
| } |
| |
| @Override |
| public void populateViewHolder(DrawerItemViewHolder holder, int position) { |
| final int iconColor = getResources().getColor(R.color.car_tint); |
| int textResId, iconResId; |
| switch (position) { |
| case ITEM_DIAL: |
| textResId = R.string.calllog_dial_number; |
| iconResId = R.drawable.ic_drawer_dialpad; |
| break; |
| case ITEM_CALLLOG_ALL: |
| textResId = R.string.calllog_all; |
| iconResId = R.drawable.ic_drawer_history; |
| break; |
| case ITEM_CALLLOG_MISSED: |
| textResId = R.string.calllog_missed; |
| iconResId = R.drawable.ic_drawer_call_missed; |
| break; |
| default: |
| Log.wtf(TAG, "Unexpected position: " + position); |
| return; |
| } |
| holder.getTitle().setText(textResId); |
| Drawable drawable = getDrawable(iconResId); |
| drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN); |
| holder.getIcon().setImageDrawable(drawable); |
| if (position > 0) { |
| drawable = getDrawable(R.drawable.ic_chevron_right); |
| drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN); |
| holder.getRightIcon().setImageDrawable(drawable); |
| } |
| } |
| |
| @Override |
| public void onItemClick(int position) { |
| switch (position) { |
| case ITEM_DIAL: |
| closeDrawer(); |
| showDialer(); |
| break; |
| case ITEM_CALLLOG_ALL: |
| loadCallHistoryAsync(PhoneLoader.CALL_TYPE_ALL, R.string.calllog_all); |
| break; |
| case ITEM_CALLLOG_MISSED: |
| loadCallHistoryAsync(PhoneLoader.CALL_TYPE_MISSED, R.string.calllog_missed); |
| break; |
| default: |
| Log.w(TAG, "Invalid position in ROOT menu! " + position); |
| } |
| } |
| } |
| |
| private void loadCallHistoryAsync(final int callType, final int titleResId) { |
| showLoadingProgressBar(true); |
| // Warning: much callbackiness! |
| // First load up the call log cursor using the PhoneLoader so that happens in a |
| // background thread. TODO: Why isn't PhoneLoader using a LoaderManager? |
| PhoneLoader.registerCallObserver(callType, this, |
| (loader, data) -> { |
| // This callback runs on the thread that created the loader which is |
| // the ui thread so spin off another async task because we still need |
| // to pull together all the data along with the contact photo. |
| CallLogListingTask task = new CallLogListingTask(TelecomActivity.this, data, |
| (items) -> { |
| showLoadingProgressBar(false); |
| switchToAdapter(new CallLogAdapter(titleResId, items)); |
| }); |
| task.execute(); |
| }); |
| } |
| } |