/* * Copyright (C) 2018 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.dialer.main.impl; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.app.KeyguardManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.provider.CallLog.Calls; import android.provider.ContactsContract.QuickContact; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.view.View; import android.widget.ImageView; import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; import com.android.dialer.animation.AnimUtils; import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.calllog.CallLogAdapter; import com.android.dialer.app.calllog.CallLogFragment; import com.android.dialer.app.calllog.CallLogFragment.CallLogFragmentListener; import com.android.dialer.app.calllog.CallLogNotificationsService; import com.android.dialer.app.calllog.IntentProvider; import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment; import com.android.dialer.app.list.DragDropController; import com.android.dialer.app.list.OldSpeedDialFragment; import com.android.dialer.app.list.OnDragDropListener; import com.android.dialer.app.list.OnListFragmentScrolledListener; import com.android.dialer.app.list.PhoneFavoriteSquareTileView; import com.android.dialer.app.list.RemoveView; import com.android.dialer.callcomposer.CallComposerActivity; import com.android.dialer.calldetails.CallDetailsActivity; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.CallSpecificAppData; import com.android.dialer.common.FragmentUtils.FragmentUtilListener; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.UiListener; import com.android.dialer.compat.CompatUtils; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.constants.ActivityRequestCodes; import com.android.dialer.contactsfragment.ContactsFragment; import com.android.dialer.contactsfragment.ContactsFragment.Header; import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; import com.android.dialer.database.CallLogQueryHandler; import com.android.dialer.database.Database; import com.android.dialer.dialpadview.DialpadFragment; import com.android.dialer.dialpadview.DialpadFragment.DialpadListener; import com.android.dialer.dialpadview.DialpadFragment.LastOutgoingCallCallback; import com.android.dialer.dialpadview.DialpadFragment.OnDialpadQueryChangedListener; import com.android.dialer.duo.DuoComponent; import com.android.dialer.interactions.PhoneNumberInteraction; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.dialer.main.MainActivityPeer; import com.android.dialer.main.impl.bottomnav.BottomNavBar; import com.android.dialer.main.impl.bottomnav.BottomNavBar.OnBottomNavTabSelectedListener; import com.android.dialer.main.impl.bottomnav.BottomNavBar.TabIndex; import com.android.dialer.main.impl.toolbar.MainToolbar; import com.android.dialer.postcall.PostCall; import com.android.dialer.precall.PreCall; import com.android.dialer.searchfragment.list.NewSearchFragment.SearchFragmentListener; import com.android.dialer.smartdial.util.SmartDialPrefix; import com.android.dialer.storage.StorageComponent; import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.TransactionSafeActivity; import com.android.voicemail.VoicemailComponent; import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.TimeUnit; /** * OldMainActivityPeer which implements all of the old fragments we know and love <3 * *
TODO(calderwoodra): Deprecate this class when we launch NewmainActivityPeer.
*/
public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListener {
private static final String KEY_SAVED_LANGUAGE_CODE = "saved_language_code";
private static final String KEY_CURRENT_TAB = "current_tab";
private static final String KEY_LAST_TAB = "last_tab";
/** Action and extra to let the activity know which tab to open up to. */
private static final String ACTION_SHOW_TAB = "ACTION_SHOW_TAB";
private static final String EXTRA_SHOW_TAB = "EXTRA_SHOW_TAB";
private final MainActivity mainActivity;
// Contacts
private MainOnContactSelectedListener onContactSelectedListener;
// Dialpad and Search
private MainDialpadFragmentHost dialpadFragmentHostInterface;
private MainSearchController searchController;
private MainOnDialpadQueryChangedListener onDialpadQueryChangedListener;
private MainDialpadListener dialpadListener;
private MainSearchFragmentListener searchFragmentListener;
// Action Mode
private MainCallLogAdapterOnActionModeStateChangedListener
callLogAdapterOnActionModeStateChangedListener;
// Call Log
private MainCallLogHost callLogHostInterface;
private MainCallLogFragmentListener callLogFragmentListener;
private MainOnListFragmentScrolledListener onListFragmentScrolledListener;
// Speed Dial
private MainOnPhoneNumberPickerActionListener onPhoneNumberPickerActionListener;
private MainOldSpeedDialFragmentHost oldSpeedDialFragmentHost;
/** Language the device was in last time {@link #onSaveInstanceState(Bundle)} was called. */
private String savedLanguageCode;
private LastTabController lastTabController;
private BottomNavBar bottomNav;
private View snackbarContainer;
private UiListener
*
*
* This mainly entails:
*
*
*
*
* @see CallLogFragmentListener
* @see CallLogQueryHandler.Listener
* @see OnBottomNavTabSelectedListener
*/
private static final class MainCallLogFragmentListener
implements CallLogFragmentListener,
CallLogQueryHandler.Listener,
OnBottomNavTabSelectedListener {
private final CallLogQueryHandler callLogQueryHandler;
private final Context context;
private final BottomNavBar bottomNavBar;
private final Toolbar toolbar;
private @TabIndex int currentTab = TabIndex.SPEED_DIAL;
private long timeSelected = -1;
private boolean activityIsAlive;
MainCallLogFragmentListener(
Context context,
ContentResolver contentResolver,
BottomNavBar bottomNavBar,
Toolbar toolbar) {
callLogQueryHandler = new CallLogQueryHandler(context, contentResolver, this);
this.context = context;
this.bottomNavBar = bottomNavBar;
this.toolbar = toolbar;
}
@Override
public void updateTabUnreadCounts() {
callLogQueryHandler.fetchMissedCallsUnreadCount();
callLogQueryHandler.fetchVoicemailUnreadCount();
}
@Override
public void showMultiSelectRemoveView(boolean show) {
bottomNavBar.setVisibility(show ? View.GONE : View.VISIBLE);
toolbar.setVisibility(show ? View.GONE : View.VISIBLE);
}
@Override
public void onVoicemailStatusFetched(Cursor statusCursor) {
// TODO(calderwoodra): handle this when voicemail is implemented
}
@Override
public void onVoicemailUnreadCountFetched(Cursor cursor) {
if (activityIsAlive) {
bottomNavBar.setNotificationCount(TabIndex.VOICEMAIL, cursor.getCount());
}
cursor.close();
}
@Override
public void onMissedCallsUnreadCountFetched(Cursor cursor) {
if (activityIsAlive) {
bottomNavBar.setNotificationCount(TabIndex.CALL_LOG, cursor.getCount());
}
cursor.close();
}
@Override
public boolean onCallsFetched(Cursor combinedCursor) {
// Return false; did not take ownership of cursor
return false;
}
@Override
public void onSpeedDialSelected() {
setCurrentTab(TabIndex.SPEED_DIAL);
}
@Override
public void onCallLogSelected() {
setCurrentTab(TabIndex.CALL_LOG);
}
@Override
public void onContactsSelected() {
setCurrentTab(TabIndex.CONTACTS);
}
@Override
public void onVoicemailSelected() {
setCurrentTab(TabIndex.VOICEMAIL);
}
private void markMissedCallsAsReadAndRemoveNotification() {
callLogQueryHandler.markMissedCallsAsRead();
CallLogNotificationsService.cancelAllMissedCalls(context);
}
private void setCurrentTab(@TabIndex int tabIndex) {
if (currentTab == TabIndex.CALL_LOG && tabIndex != TabIndex.CALL_LOG) {
markMissedCallsAsReadAndRemoveNotification();
}
currentTab = tabIndex;
timeSelected = System.currentTimeMillis();
}
public void onActivityResume() {
activityIsAlive = true;
callLogQueryHandler.fetchVoicemailStatus();
callLogQueryHandler.fetchMissedCallsUnreadCount();
// Reset the tab on resume to restart the timer
setCurrentTab(bottomNavBar.getSelectedTab());
}
/** Should be called when {@link Activity#onStop()} is called. */
public void onActivityStop(boolean changingConfigurations, boolean keyguardLocked) {
activityIsAlive = false;
if (viewedCallLogTabPastTimeThreshold() && !changingConfigurations && !keyguardLocked) {
markMissedCallsAsReadAndRemoveNotification();
}
}
/**
* Returns true if the user has been (and still is) on the history tab for long than the
* threshold.
*/
private boolean viewedCallLogTabPastTimeThreshold() {
return currentTab == TabIndex.CALL_LOG
&& timeSelected != -1
&& System.currentTimeMillis() - timeSelected > TimeUnit.SECONDS.toMillis(3);
}
}
/** @see OnListFragmentScrolledListener */
private static final class MainOnListFragmentScrolledListener
implements OnListFragmentScrolledListener {
private final View parentLayout;
MainOnListFragmentScrolledListener(View parentLayout) {
this.parentLayout = parentLayout;
}
@Override
public void onListFragmentScrollStateChange(int scrollState) {
DialerUtils.hideInputMethod(parentLayout);
}
@Override
public void onListFragmentScroll(
int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO: No-op for now. This should eventually show/hide the actionBar based on
// interactions with the ListsFragments.
}
}
/** @see OnPhoneNumberPickerActionListener */
private static final class MainOnPhoneNumberPickerActionListener
implements OnPhoneNumberPickerActionListener {
private final TransactionSafeActivity activity;
MainOnPhoneNumberPickerActionListener(TransactionSafeActivity activity) {
this.activity = activity;
}
@Override
public void onPickDataUri(
Uri dataUri, boolean isVideoCall, CallSpecificAppData callSpecificAppData) {
PhoneNumberInteraction.startInteractionForPhoneCall(
activity, dataUri, isVideoCall, callSpecificAppData);
}
@Override
public void onPickPhoneNumber(
String phoneNumber, boolean isVideoCall, CallSpecificAppData callSpecificAppData) {
if (phoneNumber == null) {
// Invalid phone number, but let the call go through so that InCallUI can show
// an error message.
phoneNumber = "";
}
PreCall.start(
activity,
new CallIntentBuilder(phoneNumber, callSpecificAppData)
.setIsVideoCall(isVideoCall)
.setAllowAssistedDial(callSpecificAppData.getAllowAssistedDialing()));
}
@Override
public void onHomeInActionBarSelected() {
// TODO(calderwoodra): investigate if we need to exit search here
// PhoneNumberPickerFragment#onOptionsItemSelected
}
}
/**
* Handles the callbacks for {@link OldSpeedDialFragment} and drag/drop logic for drag to remove.
*
* @see OldSpeedDialFragment.HostInterface
* @see OnDragDropListener
*/
private static final class MainOldSpeedDialFragmentHost
implements OldSpeedDialFragment.HostInterface, OnDragDropListener {
private final BottomNavBar bottomNavBar;
private final ImageView dragShadowOverlay;
private final RemoveView removeView;
private final View searchViewContainer;
private final MainToolbar toolbar;
// TODO(calderwoodra): Use this for drag and drop
@SuppressWarnings("unused")
private DragDropController dragDropController;
MainOldSpeedDialFragmentHost(
BottomNavBar bottomNavBar,
ImageView dragShadowOverlay,
RemoveView removeView,
View searchViewContainer,
MainToolbar toolbar) {
this.bottomNavBar = bottomNavBar;
this.dragShadowOverlay = dragShadowOverlay;
this.removeView = removeView;
this.searchViewContainer = searchViewContainer;
this.toolbar = toolbar;
}
@Override
public void setDragDropController(DragDropController dragDropController) {
removeView.setDragDropController(dragDropController);
}
@Override
public void showAllContactsTab() {
bottomNavBar.selectTab(TabIndex.CONTACTS);
}
@Override
public ImageView getDragShadowOverlay() {
return dragShadowOverlay;
}
@Override
public void setHasFrequents(boolean hasFrequents) {
toolbar.showClearFrequents(hasFrequents);
}
@Override
public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {
showRemoveView(true);
}
@Override
public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {}
@Override
public void onDragFinished(int x, int y) {
showRemoveView(false);
}
@Override
public void onDroppedOnRemove() {}
private void showRemoveView(boolean show) {
if (show) {
AnimUtils.crossFadeViews(removeView, searchViewContainer, 300);
} else {
AnimUtils.crossFadeViews(searchViewContainer, removeView, 300);
}
}
}
/**
* Implementation of {@link OnBottomNavTabSelectedListener} that handles logic for showing each of
* the main tabs.
*/
private static final class MainBottomNavBarBottomNavTabListener
implements OnBottomNavTabSelectedListener {
private static final String SPEED_DIAL_TAG = "speed_dial";
private static final String CALL_LOG_TAG = "call_log";
private static final String CONTACTS_TAG = "contacts";
private static final String VOICEMAIL_TAG = "voicemail";
private final FragmentManager fragmentManager;
private final Context context;
@TabIndex private int selectedTab = -1;
private MainBottomNavBarBottomNavTabListener(Context context, FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
this.context = context;
}
@Override
public void onSpeedDialSelected() {
if (selectedTab != TabIndex.SPEED_DIAL) {
Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_FAVORITE);
selectedTab = TabIndex.SPEED_DIAL;
}
hideAllFragments();
Fragment fragment = fragmentManager.findFragmentByTag(SPEED_DIAL_TAG);
if (fragment == null) {
fragmentManager
.beginTransaction()
.add(R.id.fragment_container, new OldSpeedDialFragment(), SPEED_DIAL_TAG)
.commit();
} else {
fragmentManager.beginTransaction().show(fragment).commit();
}
}
@Override
public void onCallLogSelected() {
if (selectedTab != TabIndex.CALL_LOG) {
Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_CALL_LOG);
selectedTab = TabIndex.CALL_LOG;
}
hideAllFragments();
CallLogFragment fragment = (CallLogFragment) fragmentManager.findFragmentByTag(CALL_LOG_TAG);
if (fragment == null) {
fragmentManager
.beginTransaction()
.add(R.id.fragment_container, new CallLogFragment(), CALL_LOG_TAG)
.commit();
} else {
fragmentManager.beginTransaction().show(fragment).commit();
}
}
@Override
public void onContactsSelected() {
if (selectedTab != TabIndex.CONTACTS) {
Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_CONTACTS);
selectedTab = TabIndex.CONTACTS;
}
hideAllFragments();
ContactsFragment fragment =
(ContactsFragment) fragmentManager.findFragmentByTag(CONTACTS_TAG);
if (fragment == null) {
fragmentManager
.beginTransaction()
.add(
R.id.fragment_container,
ContactsFragment.newInstance(Header.ADD_CONTACT),
CONTACTS_TAG)
.commit();
} else {
fragmentManager.beginTransaction().show(fragment).commit();
}
}
@Override
public void onVoicemailSelected() {
if (selectedTab != TabIndex.VOICEMAIL) {
Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_VOICEMAIL);
selectedTab = TabIndex.VOICEMAIL;
}
hideAllFragments();
VisualVoicemailCallLogFragment fragment =
(VisualVoicemailCallLogFragment) fragmentManager.findFragmentByTag(VOICEMAIL_TAG);
if (fragment == null) {
fragmentManager
.beginTransaction()
.add(R.id.fragment_container, new VisualVoicemailCallLogFragment(), VOICEMAIL_TAG)
.commit();
} else {
fragmentManager.beginTransaction().show(fragment).commit();
}
}
private void hideAllFragments() {
android.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
if (fragmentManager.findFragmentByTag(SPEED_DIAL_TAG) != null) {
transaction.hide(fragmentManager.findFragmentByTag(SPEED_DIAL_TAG));
}
if (fragmentManager.findFragmentByTag(CALL_LOG_TAG) != null) {
// Old CallLogFragment
transaction.hide(fragmentManager.findFragmentByTag(CALL_LOG_TAG));
}
if (fragmentManager.findFragmentByTag(CONTACTS_TAG) != null) {
transaction.hide(fragmentManager.findFragmentByTag(CONTACTS_TAG));
}
if (fragmentManager.findFragmentByTag(VOICEMAIL_TAG) != null) {
// Old VisualVoicemailFragment
transaction.hide(fragmentManager.findFragmentByTag(VOICEMAIL_TAG));
}
transaction.commit();
}
}
private static final class LastTabController {
private final Context context;
private final BottomNavBar bottomNavBar;
private final boolean isEnabled;
private final boolean canShowVoicemailTab;
LastTabController(Context context, BottomNavBar bottomNavBar, boolean canShowVoicemailTab) {
this.context = context;
this.bottomNavBar = bottomNavBar;
isEnabled = ConfigProviderBindings.get(context).getBoolean("last_tab_enabled", false);
this.canShowVoicemailTab = canShowVoicemailTab;
}
/** Sets the last tab if the feature is enabled, otherwise defaults to speed dial. */
void selectLastTab() {
@TabIndex int tabIndex = TabIndex.SPEED_DIAL;
if (isEnabled) {
tabIndex =
StorageComponent.get(context)
.unencryptedSharedPrefs()
.getInt(KEY_LAST_TAB, TabIndex.SPEED_DIAL);
}
// If the voicemail tab cannot be shown, default to showing speed dial
if (tabIndex == TabIndex.VOICEMAIL && !canShowVoicemailTab) {
tabIndex = TabIndex.SPEED_DIAL;
}
bottomNavBar.selectTab(tabIndex);
}
void onActivityStop() {
StorageComponent.get(context)
.unencryptedSharedPrefs()
.edit()
.putInt(KEY_LAST_TAB, bottomNavBar.getSelectedTab())
.apply();
}
}
}