/*
* Copyright (C) 2019 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.ui;
import android.app.SearchManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.provider.CallLog;
import android.telecom.Call;
import android.telephony.PhoneNumberUtils;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
import com.android.car.apps.common.util.Themes;
import com.android.car.dialer.R;
import com.android.car.dialer.log.L;
import com.android.car.dialer.notification.NotificationService;
import com.android.car.dialer.telecom.UiCallManager;
import com.android.car.dialer.ui.activecall.InCallActivity;
import com.android.car.dialer.ui.common.DialerBaseFragment;
import com.android.car.dialer.ui.common.OnItemClickedListener;
import com.android.car.dialer.ui.dialpad.DialpadFragment;
import com.android.car.dialer.ui.search.ContactResultsFragment;
import com.android.car.dialer.ui.settings.DialerSettingsActivity;
import com.android.car.dialer.ui.warning.OverlayFragment;
import com.android.car.ui.baselayout.Insets;
import com.android.car.ui.baselayout.InsetsChangedListener;
import com.android.car.ui.core.CarUi;
import com.android.car.ui.toolbar.MenuItem;
import com.android.car.ui.toolbar.ToolbarController;
import java.util.List;
import java.util.stream.Collectors;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Main activity for the Dialer app. It hosts most of the fragments for the app.
*
*
Start {@link InCallActivity} if there are ongoing calls
*
*
Based on call and connectivity status, it will choose the right page to display.
*/
@AndroidEntryPoint(FragmentActivity.class)
public class TelecomActivity extends Hilt_TelecomActivity implements
DialerBaseFragment.DialerFragmentParent, InsetsChangedListener {
private static final String TAG = "CD.TelecomActivity";
@Inject SharedPreferences mSharedPreferences;
@Inject UiCallManager mUiCallManager;
private LiveData> mOngoingCallListLiveData;
private LiveData mRefreshUiLiveData;
// View objects for this activity.
private TelecomPageTab.Factory mTabFactory;
private ToolbarController mCarUiToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
L.d(TAG, "onCreate");
setContentView(R.layout.telecom_activity);
mCarUiToolbar = CarUi.requireToolbar(this);
setupTabLayout();
TelecomActivityViewModel viewModel = new ViewModelProvider(this).get(
TelecomActivityViewModel.class);
mRefreshUiLiveData = viewModel.getRefreshTabsLiveData();
mRefreshUiLiveData.observe(this, v -> refreshUi());
LiveData hasHfpDeviceConnectedLiveData = viewModel.hasHfpDeviceConnected();
hasHfpDeviceConnectedLiveData.observe(this, hasHfpDeviceConnected -> {
if (!Boolean.TRUE.equals(hasHfpDeviceConnected)) {
new OverlayFragment().show(getSupportFragmentManager(), null);
}
});
MutableLiveData toolbarTitleMode = viewModel.getToolbarTitleMode();
toolbarTitleMode.setValue(Themes.getAttrInteger(this, R.attr.toolbarTitleMode));
mOngoingCallListLiveData = viewModel.getOngoingCallListLiveData();
mOngoingCallListLiveData.observe(this, list -> maybeStartInCallActivity(list));
handleIntent();
}
@Override
protected void onNewIntent(Intent i) {
super.onNewIntent(i);
setIntent(i);
handleIntent();
}
private void handleIntent() {
Intent intent = getIntent();
String action = intent != null ? intent.getAction() : null;
L.d(TAG, "handleIntent, intent: %s, action: %s", intent, action);
if (action == null || action.length() == 0) {
return;
}
String number;
switch (action) {
case Intent.ACTION_DIAL:
number = PhoneNumberUtils.getNumberFromIntent(intent, this);
showDialPadFragment(number);
break;
case Intent.ACTION_CALL:
number = PhoneNumberUtils.getNumberFromIntent(intent, this);
mUiCallManager.placeCall(number);
break;
case Intent.ACTION_SEARCH:
String searchQuery = intent.getStringExtra(SearchManager.QUERY);
navigateToContactResultsFragment(searchQuery);
break;
case Intent.ACTION_VIEW:
if (CallLog.Calls.CONTENT_TYPE.equals(intent.getType())) {
showTabPage(TelecomPageTab.Page.CALL_HISTORY);
NotificationService.readAllMissedCall(this);
}
break;
default:
// Do nothing.
}
setIntent(null);
// This is to start the incall activity when user taps on the dialer launch icon rapidly
maybeStartInCallActivity(mOngoingCallListLiveData.getValue());
}
private void setupTabLayout() {
boolean[] tabSelectedListenerEnabled = new boolean[] { false };
OnItemClickedListener onTabSelected = tab -> {
if (tabSelectedListenerEnabled[0]) {
Fragment fragment = tab.getFragment();
setContentFragment(fragment, tab.getFragmentTag());
}
};
boolean wasContentFragmentRestored = false;
mTabFactory = new TelecomPageTab.Factory(this, onTabSelected, getSupportFragmentManager());
List tabs = mTabFactory.recreateTabs(getBaseContext(), false);
mCarUiToolbar.setTabs(tabs.stream()
.map(TelecomPageTab::getToolbarTab)
.collect(Collectors.toList()));
for (int i = 0; i < tabs.size(); i++) {
if (tabs.get(i).wasFragmentRestored()) {
mCarUiToolbar.selectTab(i);
wasContentFragmentRestored = true;
}
}
// Select the starting tab and set up the fragment for it.
if (!wasContentFragmentRestored) {
int startTabIndex = mTabFactory.getTabIndex(getTabFromSharedPreference());
mCarUiToolbar.selectTab(startTabIndex);
TelecomPageTab startTab = tabs.get(startTabIndex);
setContentFragment(startTab.getFragment(), startTab.getFragmentTag());
}
tabSelectedListenerEnabled[0] = true;
}
private void refreshUi() {
L.v(TAG, "Refresh ui");
List tabs = mTabFactory.recreateTabs(getBaseContext(), true);
mCarUiToolbar.setTabs(tabs.stream()
.map(TelecomPageTab::getToolbarTab)
.collect(Collectors.toList()));
String startTab = getTabFromSharedPreference();
showTabPage(startTab);
}
/**
* Switch to {@link DialpadFragment} and set the given number as dialed number.
*/
private void showDialPadFragment(String number) {
int dialpadTabIndex = showTabPage(TelecomPageTab.Page.DIAL_PAD);
if (dialpadTabIndex == -1) {
return;
}
TelecomPageTab dialpadTab = mTabFactory.getTab(dialpadTabIndex);
Fragment fragment = dialpadTab.getFragment();
if (fragment instanceof DialpadFragment) {
((DialpadFragment) fragment).setDialedNumber(number);
} else {
L.w(TAG, "Current tab is not a dialpad fragment!");
}
}
private int showTabPage(@TelecomPageTab.Page String tabPage) {
int tabIndex = mTabFactory.getTabIndex(tabPage);
if (tabIndex == -1) {
L.w(TAG, "Page %s is not a tab.", tabPage);
return -1;
}
getSupportFragmentManager().executePendingTransactions();
while (isBackNavigationAvailable()) {
getSupportFragmentManager().popBackStackImmediate();
}
mCarUiToolbar.selectTab(tabIndex);
return tabIndex;
}
private void setContentFragment(Fragment fragment, String fragmentTag) {
L.d(TAG, "setContentFragment: %s", fragment);
getSupportFragmentManager().executePendingTransactions();
while (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStackImmediate();
}
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, fragment, fragmentTag)
.addToBackStack(fragmentTag)
.commit();
}
@Override
public void pushContentFragment(@NonNull Fragment topContentFragment, String fragmentTag) {
L.d(TAG, "pushContentFragment: %s", topContentFragment);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, topContentFragment, fragmentTag)
.addToBackStack(fragmentTag)
.commit();
}
@Override
public boolean onNavigateUp() {
if (isBackNavigationAvailable()) {
onBackPressed();
return true;
}
return super.onNavigateUp();
}
@Override
public void onBackPressed() {
// By default onBackPressed will pop all the fragments off the backstack and then finish
// the activity. We want to finish the activity while there is still one fragment on the
// backstack, because we use onBackStackChanged() to set up our fragments.
if (isBackNavigationAvailable()) {
super.onBackPressed();
} else {
finishAfterTransition();
}
}
/**
* Handles the click action on the menu items.
*/
public void onMenuItemClicked(MenuItem item) {
switch (item.getId()) {
case R.id.menu_item_search:
Intent searchIntent = new Intent(getApplicationContext(), TelecomActivity.class);
searchIntent.setAction(Intent.ACTION_SEARCH);
startActivity(searchIntent);
break;
case R.id.menu_item_setting:
Intent settingsIntent = new Intent(getApplicationContext(),
DialerSettingsActivity.class);
startActivity(settingsIntent);
break;
}
}
private void navigateToContactResultsFragment(String query) {
Fragment topFragment = getSupportFragmentManager().findFragmentById(
R.id.fragment_container);
// Top fragment is ContactResultsFragment, update search query
if (topFragment instanceof ContactResultsFragment) {
((ContactResultsFragment) topFragment).setSearchQuery(query);
return;
}
ContactResultsFragment fragment = ContactResultsFragment.newInstance(query);
pushContentFragment(fragment, ContactResultsFragment.FRAGMENT_TAG);
}
private void maybeStartInCallActivity(List callList) {
if (callList == null || callList.isEmpty()) {
return;
}
L.d(TAG, "Start InCallActivity");
Intent launchIntent = new Intent(getApplicationContext(), InCallActivity.class);
startActivity(launchIntent);
}
/**
* If the back button on action bar is available to navigate up.
*/
private boolean isBackNavigationAvailable() {
return getSupportFragmentManager().getBackStackEntryCount() > 1;
}
private String getTabFromSharedPreference() {
String key = getResources().getString(R.string.pref_start_page_key);
String defaultValue = getResources().getString(R.string.tab_config_default_value);
return mSharedPreferences.getString(key, defaultValue);
}
@Override
public void onCarUiInsetsChanged(Insets insets) {
// Do nothing, this is just a marker that we will handle the insets in fragments.
// This is only necessary because the fragments are not immediately added to the
// activity when calling .commit()
}
}