diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2018-02-02 22:24:34 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-02-02 22:24:34 +0000 |
commit | 056c1911137caad554cdfebb5a0b4a03e3df8c53 (patch) | |
tree | 28107d07ea8259a86bd32b1e8d550daad35bfac1 | |
parent | 7e5da60e990e153c8fd5ba4e146825bb8e9a2bd4 (diff) | |
parent | 3a398a5adf0feae1f6dafe629a6c7cc4a0a968b4 (diff) |
Merge "Split MainActivity into two peers, old and new to keep logic isolated."
5 files changed, 1020 insertions, 789 deletions
diff --git a/java/com/android/dialer/common/FragmentUtils.java b/java/com/android/dialer/common/FragmentUtils.java index 947a9b20a..c07d9a799 100644 --- a/java/com/android/dialer/common/FragmentUtils.java +++ b/java/com/android/dialer/common/FragmentUtils.java @@ -21,6 +21,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.v4.app.Fragment; +import com.android.dialer.main.MainActivityPeer; /** Utility methods for working with Fragments */ public class FragmentUtils { @@ -89,6 +90,11 @@ public class FragmentUtils { @SuppressWarnings("unchecked") // Casts are checked using runtime methods T parent = ((FragmentUtilListener) fragment.getActivity()).getImpl(callbackInterface); return parent; + } else if (fragment.getActivity() instanceof MainActivityPeer.PeerSupplier) { + MainActivityPeer peer = ((MainActivityPeer.PeerSupplier) fragment.getActivity()).getPeer(); + if (peer instanceof FragmentUtilListener) { + return ((FragmentUtilListener) peer).getImpl(callbackInterface); + } } return null; } diff --git a/java/com/android/dialer/main/MainActivityPeer.java b/java/com/android/dialer/main/MainActivityPeer.java new file mode 100644 index 000000000..6457b607b --- /dev/null +++ b/java/com/android/dialer/main/MainActivityPeer.java @@ -0,0 +1,44 @@ +/* + * 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; + +import android.content.Intent; +import android.os.Bundle; + +/** Interface for peers of MainActivity. */ +public interface MainActivityPeer { + + void onActivityCreate(Bundle saveInstanceState); + + void onActivityResume(); + + void onActivityStop(); + + void onNewIntent(Intent intent); + + void onActivityResult(int requestCode, int resultCode, Intent data); + + void onSaveInstanceState(Bundle bundle); + + boolean onBackPressed(); + + /** Supplies the MainActivityPeer */ + interface PeerSupplier { + + MainActivityPeer getPeer(); + } +} diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java index ca9d47843..1a79fbab3 100644 --- a/java/com/android/dialer/main/impl/MainActivity.java +++ b/java/com/android/dialer/main/impl/MainActivity.java @@ -16,113 +16,27 @@ 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.v4.app.FragmentTransaction; -import android.view.View; -import android.widget.ImageView; -import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; -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.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.callintent.CallIntentBuilder; -import com.android.dialer.callintent.CallSpecificAppData; -import com.android.dialer.calllog.ui.NewCallLogFragment; import com.android.dialer.common.Assert; -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.configprovider.ConfigProviderComponent; -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.interactions.PhoneNumberInteraction; import com.android.dialer.interactions.PhoneNumberInteraction.DisambigDialogDismissedListener; import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorCode; import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorListener; -import com.android.dialer.main.impl.BottomNavBar.OnBottomNavTabSelectedListener; -import com.android.dialer.main.impl.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.speeddial.SpeedDialFragment; -import com.android.dialer.storage.StorageComponent; -import com.android.dialer.telecom.TelecomUtil; -import com.android.dialer.util.DialerUtils; +import com.android.dialer.main.MainActivityPeer; import com.android.dialer.util.TransactionSafeActivity; -import com.android.dialer.voicemail.listui.NewVoicemailFragment; -import com.google.common.util.concurrent.ListenableFuture; -import java.util.concurrent.TimeUnit; /** This is the main activity for dialer. It hosts favorites, call log, search, dialpad, etc... */ // TODO(calderwoodra): Do not extend TransactionSafeActivity after new SpeedDial is launched public final class MainActivity extends TransactionSafeActivity - implements FragmentUtilListener, + implements MainActivityPeer.PeerSupplier, // TODO(calderwoodra): remove these 2 interfaces when we migrate to new speed dial fragment InteractionErrorListener, DisambigDialogDismissedListener { - 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"; - - private final MainOnContactSelectedListener onContactSelectedListener = - new MainOnContactSelectedListener(this); - private final MainDialpadFragmentHost dialpadFragmentHostInterface = - new MainDialpadFragmentHost(); - - private MainSearchController searchController; - private MainOnDialpadQueryChangedListener onDialpadQueryChangedListener; - private MainDialpadListener dialpadListener; - private MainSearchFragmentListener searchFragmentListener; - private MainCallLogAdapterOnActionModeStateChangedListener - callLogAdapterOnActionModeStateChangedListener; - private MainCallLogHost callLogHostInterface; - private MainCallLogFragmentListener callLogFragmentListener; - private MainOnListFragmentScrolledListener onListFragmentScrolledListener; - private MainOnPhoneNumberPickerActionListener onPhoneNumberPickerActionListener; - private MainOldSpeedDialFragmentHostInterface oldSpeedDialFragmentHostInterface; - private MainOnDragDropListener onDragDropListener; - - /** 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<String> getLastOutgoingCallListener; + private MainActivityPeer activePeer; /** * @param context Context of the application package implementing MainActivity class. @@ -138,164 +52,54 @@ public final class MainActivity extends TransactionSafeActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LogUtil.enterBlock("MainActivity.onCreate"); - setContentView(R.layout.main_activity); - initUiListeners(); - initLayout(savedInstanceState); - SmartDialPrefix.initializeNanpSettings(this); - } - - private void initUiListeners() { - getLastOutgoingCallListener = - DialerExecutorComponent.get(this) - .createUiListener(getFragmentManager(), "Query last phone number"); - } - - private void initLayout(Bundle savedInstanceState) { - snackbarContainer = findViewById(R.id.coordinator_layout); - - FloatingActionButton fab = findViewById(R.id.fab); - fab.setOnClickListener(v -> searchController.showDialpad(true)); - - MainToolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(findViewById(R.id.toolbar)); - - bottomNav = findViewById(R.id.bottom_nav_bar); - MainBottomNavBarBottomNavTabListener bottomNavTabListener = - new MainBottomNavBarBottomNavTabListener( - this, getFragmentManager(), getSupportFragmentManager()); - bottomNav.addOnTabSelectedListener(bottomNavTabListener); - - callLogFragmentListener = - new MainCallLogFragmentListener(this, getContentResolver(), bottomNav); - bottomNav.addOnTabSelectedListener(callLogFragmentListener); - - searchController = new MainSearchController(this, bottomNav, fab, toolbar); - toolbar.setSearchBarListener(searchController); - - onDialpadQueryChangedListener = new MainOnDialpadQueryChangedListener(searchController); - dialpadListener = new MainDialpadListener(this, searchController, getLastOutgoingCallListener); - searchFragmentListener = new MainSearchFragmentListener(searchController); - callLogAdapterOnActionModeStateChangedListener = - new MainCallLogAdapterOnActionModeStateChangedListener(); - callLogHostInterface = new MainCallLogHost(searchController, fab); - - onListFragmentScrolledListener = new MainOnListFragmentScrolledListener(snackbarContainer); - onPhoneNumberPickerActionListener = new MainOnPhoneNumberPickerActionListener(this); - oldSpeedDialFragmentHostInterface = - new MainOldSpeedDialFragmentHostInterface( - bottomNavTabListener, findViewById(R.id.contact_tile_drag_shadow_overlay)); - onDragDropListener = new MainOnDragDropListener(); - - lastTabController = new LastTabController(this, bottomNav); - - // Restore our view state if needed, else initialize as if the app opened for the first time - if (savedInstanceState != null) { - savedLanguageCode = savedInstanceState.getString(KEY_SAVED_LANGUAGE_CODE); - searchController.onRestoreInstanceState(savedInstanceState); - bottomNav.selectTab(savedInstanceState.getInt(KEY_CURRENT_TAB)); + if (ConfigProviderComponent.get(this) + .getConfigProvider() + .getBoolean("nui_peer_enabled", false)) { + activePeer = new NewMainActivityPeer(this); } else { - lastTabController.selectLastTab(); + activePeer = new OldMainActivityPeer(this); } + activePeer.onActivityCreate(savedInstanceState); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - lastTabController.selectLastTab(); + activePeer.onNewIntent(intent); } @Override protected void onResume() { super.onResume(); - callLogFragmentListener.onActivityResume(); - // Start the thread that updates the smart dial database if the activity is recreated with a - // language change. - boolean forceUpdate = !CompatUtils.getLocale(this).getISO3Language().equals(savedLanguageCode); - Database.get(this).getDatabaseHelper(this).startSmartDialUpdateThread(forceUpdate); - showPostCallPrompt(); + activePeer.onActivityResume(); } @Override protected void onStop() { super.onStop(); - lastTabController.onActivityStop(); - callLogFragmentListener.onActivityStop( - isChangingConfigurations(), getSystemService(KeyguardManager.class).isKeyguardLocked()); - } - - private void showPostCallPrompt() { - if (TelecomUtil.isInManagedCall(this)) { - // No prompt to show if the user is in a call - return; - } - - if (searchController.isInSearch()) { - // Don't show the prompt if we're in the search ui - return; - } - - PostCall.promptUserForMessageIfNecessary(this, snackbarContainer); + activePeer.onActivityStop(); } @Override protected void onSaveInstanceState(Bundle bundle) { super.onSaveInstanceState(bundle); - bundle.putString(KEY_SAVED_LANGUAGE_CODE, CompatUtils.getLocale(this).getISO3Language()); - bundle.putInt(KEY_CURRENT_TAB, bottomNav.getSelectedTab()); - searchController.onSaveInstanceState(bundle); + activePeer.onSaveInstanceState(bundle); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == ActivityRequestCodes.DIALTACTS_VOICE_SEARCH) { - searchController.onVoiceResults(resultCode, data); - } else { - LogUtil.e("MainActivity.onActivityResult", "Unknown request code: " + requestCode); - } + activePeer.onActivityResult(requestCode, resultCode, data); } @Override public void onBackPressed() { - if (searchController.onBackPressed()) { + if (activePeer.onBackPressed()) { return; } super.onBackPressed(); } - @Nullable - @Override - @SuppressWarnings("unchecked") // Casts are checked using runtime methods - public <T> T getImpl(Class<T> callbackInterface) { - if (callbackInterface.isInstance(onContactSelectedListener)) { - return (T) onContactSelectedListener; - } else if (callbackInterface.isInstance(onDialpadQueryChangedListener)) { - return (T) onDialpadQueryChangedListener; - } else if (callbackInterface.isInstance(dialpadListener)) { - return (T) dialpadListener; - } else if (callbackInterface.isInstance(dialpadFragmentHostInterface)) { - return (T) dialpadFragmentHostInterface; - } else if (callbackInterface.isInstance(searchFragmentListener)) { - return (T) searchFragmentListener; - } else if (callbackInterface.isInstance(callLogAdapterOnActionModeStateChangedListener)) { - return (T) callLogAdapterOnActionModeStateChangedListener; - } else if (callbackInterface.isInstance(callLogHostInterface)) { - return (T) callLogHostInterface; - } else if (callbackInterface.isInstance(callLogFragmentListener)) { - return (T) callLogFragmentListener; - } else if (callbackInterface.isInstance(onListFragmentScrolledListener)) { - return (T) onListFragmentScrolledListener; - } else if (callbackInterface.isInstance(onPhoneNumberPickerActionListener)) { - return (T) onPhoneNumberPickerActionListener; - } else if (callbackInterface.isInstance(oldSpeedDialFragmentHostInterface)) { - return (T) oldSpeedDialFragmentHostInterface; - } else if (callbackInterface.isInstance(onDragDropListener)) { - return (T) onDragDropListener; - } else { - return null; - } - } - @Override public void interactionError(@InteractionErrorCode int interactionErrorCode) { switch (interactionErrorCode) { @@ -318,583 +122,8 @@ public final class MainActivity extends TransactionSafeActivity // Don't do anything; the app will remain open with favorites tiles displayed. } - /** @see OnContactSelectedListener */ - private static final class MainOnContactSelectedListener implements OnContactSelectedListener { - - private final Context context; - - MainOnContactSelectedListener(Context context) { - this.context = context; - } - - @Override - public void onContactSelected(ImageView photo, Uri contactUri, long contactId) { - // TODO(calderwoodra): Add impression logging - QuickContact.showQuickContact( - context, photo, contactUri, QuickContact.MODE_LARGE, null /* excludeMimes */); - } - } - - /** @see OnDialpadQueryChangedListener */ - private static final class MainOnDialpadQueryChangedListener - implements OnDialpadQueryChangedListener { - - private final MainSearchController searchController; - - MainOnDialpadQueryChangedListener(MainSearchController searchController) { - this.searchController = searchController; - } - - @Override - public void onDialpadQueryChanged(String query) { - searchController.onDialpadQueryChanged(query); - } - } - - /** @see DialpadListener */ - private static final class MainDialpadListener implements DialpadListener { - - private final MainSearchController searchController; - private final Context context; - private final UiListener<String> listener; - - MainDialpadListener( - Context context, MainSearchController searchController, UiListener<String> uiListener) { - this.context = context; - this.searchController = searchController; - this.listener = uiListener; - } - - @Override - public void getLastOutgoingCall(LastOutgoingCallCallback callback) { - ListenableFuture<String> listenableFuture = - DialerExecutorComponent.get(context) - .backgroundExecutor() - .submit(() -> Calls.getLastOutgoingCall(context)); - listener.listen(context, listenableFuture, callback::lastOutgoingCall, throwable -> {}); - } - - @Override - public void onDialpadShown() { - searchController.onDialpadShown(); - } - - @Override - public void onCallPlacedFromDialpad() { - // TODO(calderwoodra): logging - } - } - - /** @see SearchFragmentListener */ - private static final class MainSearchFragmentListener implements SearchFragmentListener { - - private final MainSearchController searchController; - - MainSearchFragmentListener(MainSearchController searchController) { - this.searchController = searchController; - } - - @Override - public void onSearchListTouch() { - searchController.onSearchListTouch(); - } - - @Override - public void onCallPlacedFromSearch() { - // TODO(calderwoodra): logging - } - } - - /** @see DialpadFragment.HostInterface */ - private static final class MainDialpadFragmentHost implements DialpadFragment.HostInterface { - - @Override - public boolean onDialpadSpacerTouchWithEmptyQuery() { - // No-op, just let the clicks fall through to the search list - return false; - } - } - - /** @see CallLogAdapter.OnActionModeStateChangedListener */ - // TODO(a bug): handle multiselect mode - private static final class MainCallLogAdapterOnActionModeStateChangedListener - implements CallLogAdapter.OnActionModeStateChangedListener { - - @Override - public void onActionModeStateChanged(boolean isEnabled) {} - - @Override - public boolean isActionModeStateEnabled() { - return false; - } - } - - /** @see CallLogFragment.HostInterface */ - private static final class MainCallLogHost implements CallLogFragment.HostInterface { - - private final MainSearchController searchController; - private final FloatingActionButton fab; - - MainCallLogHost(MainSearchController searchController, FloatingActionButton fab) { - this.searchController = searchController; - this.fab = fab; - } - - @Override - public void showDialpad() { - searchController.showDialpad(true); - } - - @Override - public void enableFloatingButton(boolean enabled) { - if (enabled) { - fab.show(); - } else { - fab.hide(); - } - } - } - - /** - * Handles the logic for callbacks from: - * - * <ul> - * <li>{@link CallLogFragment} - * <li>{@link CallLogQueryHandler} - * <li>{@link BottomNavBar} - * </ul> - * - * This mainly entails: - * - * <ul> - * <li>Handling querying for missed calls/unread voicemails. - * <li>Displaying a badge to the user in the bottom nav when there are missed calls/unread - * voicemails present. - * <li>Marking missed calls as read when appropriate. See {@link - * #markMissedCallsAsReadAndRemoveNotification()} - * <li>TODO(calderwoodra): multiselect - * <li>TODO(calderwoodra): voicemail status - * </ul> - * - * @see CallLogFragmentListener - * @see CallLogQueryHandler.Listener - * @see OnBottomNavTabSelectedListener - */ - private static final class MainCallLogFragmentListener - implements CallLogFragmentListener, - CallLogQueryHandler.Listener, - OnBottomNavTabSelectedListener { - - private final CallLogQueryHandler callLogQueryHandler; - private final BottomNavBar bottomNavBar; - private final Context context; - - private @TabIndex int currentTab = TabIndex.SPEED_DIAL; - private long timeSelected = -1; - private boolean activityIsAlive; - - MainCallLogFragmentListener( - Context context, ContentResolver contentResolver, BottomNavBar bottomNavBar) { - callLogQueryHandler = new CallLogQueryHandler(context, contentResolver, this); - this.bottomNavBar = bottomNavBar; - this.context = context; - } - - @Override - public void updateTabUnreadCounts() { - callLogQueryHandler.fetchMissedCallsUnreadCount(); - callLogQueryHandler.fetchVoicemailUnreadCount(); - } - - @Override - public void showMultiSelectRemoveView(boolean show) { - // TODO(a bug): handle multiselect mode - } - - @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 - } - } - - /** @see OldSpeedDialFragment.HostInterface */ - private static final class MainOldSpeedDialFragmentHostInterface - implements OldSpeedDialFragment.HostInterface { - - private final MainBottomNavBarBottomNavTabListener listener; - private final ImageView dragShadowOverlay; - - // TODO(calderwoodra): Use this for drag and drop - @SuppressWarnings("unused") - private DragDropController dragDropController; - - MainOldSpeedDialFragmentHostInterface( - MainBottomNavBarBottomNavTabListener listener, ImageView dragShadowOverlay) { - this.listener = listener; - this.dragShadowOverlay = dragShadowOverlay; - } - - @Override - public void setDragDropController(DragDropController dragDropController) { - this.dragDropController = dragDropController; - } - - @Override - public void showAllContactsTab() { - listener.onContactsSelected(); - } - - @Override - public ImageView getDragShadowOverlay() { - return dragShadowOverlay; - } - } - - /** @see com.android.dialer.app.list.OnDragDropListener */ - // TODO(calderwoodra): implement drag and drop - private static final class MainOnDragDropListener implements OnDragDropListener { - - @Override - public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {} - - @Override - public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {} - - @Override - public void onDragFinished(int x, int y) {} - - @Override - public void onDroppedOnRemove() {} - } - - /** - * 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 Context context; - private final FragmentManager fragmentManager; - private final android.support.v4.app.FragmentManager supportFragmentManager; - - private MainBottomNavBarBottomNavTabListener( - Context context, - FragmentManager fragmentManager, - android.support.v4.app.FragmentManager supportFragmentManager) { - this.context = context; - this.fragmentManager = fragmentManager; - this.supportFragmentManager = supportFragmentManager; - } - - @Override - public void onSpeedDialSelected() { - hideAllFragments(); - Fragment fragment = fragmentManager.findFragmentByTag(SPEED_DIAL_TAG); - if (fragment == null) { - if (ConfigProviderComponent.get(context) - .getConfigProvider() - .getBoolean("enable_new_favorites_tab", false)) { - fragment = SpeedDialFragment.newInstance(); - } else { - fragment = new OldSpeedDialFragment(); - } - fragmentManager - .beginTransaction() - .add(R.id.fragment_container, fragment, SPEED_DIAL_TAG) - .commit(); - } else { - fragmentManager.beginTransaction().show(fragment).commit(); - } - } - - @Override - public void onCallLogSelected() { - hideAllFragments(); - if (ConfigProviderComponent.get(context) - .getConfigProvider() - .getBoolean("enable_new_call_log", false)) { - NewCallLogFragment fragment = - (NewCallLogFragment) supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); - if (fragment == null) { - supportFragmentManager - .beginTransaction() - .add(R.id.fragment_container, new NewCallLogFragment(), CALL_LOG_TAG) - .commit(); - } else { - supportFragmentManager.beginTransaction().show(fragment).commit(); - } - } else { - 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() { - 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() { - hideAllFragments(); - if (ConfigProviderComponent.get(context) - .getConfigProvider() - .getBoolean("enable_new_voicemail_fragment", false)) { - NewVoicemailFragment fragment = - (NewVoicemailFragment) supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG); - if (fragment == null) { - supportFragmentManager - .beginTransaction() - .add(R.id.fragment_container, new NewVoicemailFragment(), VOICEMAIL_TAG) - .commit(); - } else { - supportFragmentManager.beginTransaction().show(fragment).commit(); - } - } else { - 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() { - FragmentTransaction supportTransaction = supportFragmentManager.beginTransaction(); - if (supportFragmentManager.findFragmentByTag(CALL_LOG_TAG) != null) { - // NewCallLogFragment - supportTransaction.hide(supportFragmentManager.findFragmentByTag(CALL_LOG_TAG)); - } - if (supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG) != null) { - // NewVoicemailFragment - supportTransaction.hide(supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG)); - } - supportTransaction.commit(); - - 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; - - public LastTabController(Context context, BottomNavBar bottomNavBar) { - this.context = context; - this.bottomNavBar = bottomNavBar; - isEnabled = ConfigProviderBindings.get(context).getBoolean("last_tab_enabled", false); - } - - /** Sets the last tab if the feature is enabled, otherwise defaults to speed dial. */ - public void selectLastTab() { - @TabIndex int tabIndex = TabIndex.SPEED_DIAL; - if (isEnabled) { - tabIndex = - StorageComponent.get(context) - .unencryptedSharedPrefs() - .getInt(KEY_LAST_TAB, TabIndex.SPEED_DIAL); - } - bottomNavBar.selectTab(tabIndex); - } - - public void onActivityStop() { - StorageComponent.get(context) - .unencryptedSharedPrefs() - .edit() - .putInt(KEY_LAST_TAB, bottomNavBar.getSelectedTab()) - .apply(); - } + @Override + public MainActivityPeer getPeer() { + return activePeer; } } diff --git a/java/com/android/dialer/main/impl/NewMainActivityPeer.java b/java/com/android/dialer/main/impl/NewMainActivityPeer.java new file mode 100644 index 000000000..cc4c3e7fd --- /dev/null +++ b/java/com/android/dialer/main/impl/NewMainActivityPeer.java @@ -0,0 +1,137 @@ +/* + * 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.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import com.android.dialer.calllog.ui.NewCallLogFragment; +import com.android.dialer.main.MainActivityPeer; +import com.android.dialer.main.impl.BottomNavBar.OnBottomNavTabSelectedListener; +import com.android.dialer.main.impl.BottomNavBar.TabIndex; +import com.android.dialer.voicemail.listui.NewVoicemailFragment; + +/** MainActivityPeer that implements the new fragments. */ +public class NewMainActivityPeer implements MainActivityPeer { + + private final MainActivity mainActivity; + + public NewMainActivityPeer(MainActivity mainActivity) { + this.mainActivity = mainActivity; + } + + @Override + public void onActivityCreate(Bundle saveInstanceState) { + mainActivity.setContentView(R.layout.main_activity); + MainBottomNavBarBottomNavTabListener bottomNavBarBottomNavTabListener = + new MainBottomNavBarBottomNavTabListener(mainActivity.getSupportFragmentManager()); + BottomNavBar bottomNav = mainActivity.findViewById(R.id.bottom_nav_bar); + bottomNav.addOnTabSelectedListener(bottomNavBarBottomNavTabListener); + bottomNav.selectTab(TabIndex.SPEED_DIAL); + } + + @Override + public void onActivityResume() {} + + @Override + public void onActivityStop() {} + + @Override + public void onNewIntent(Intent intent) {} + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) {} + + @Override + public void onSaveInstanceState(Bundle bundle) {} + + @Override + public boolean onBackPressed() { + return false; + } + + /** + * 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 CALL_LOG_TAG = "call_log"; + private static final String VOICEMAIL_TAG = "voicemail"; + + private final FragmentManager supportFragmentManager; + + private MainBottomNavBarBottomNavTabListener(FragmentManager supportFragmentManager) { + this.supportFragmentManager = supportFragmentManager; + } + + @Override + public void onSpeedDialSelected() { + hideAllFragments(); + // TODO(calderwoodra): Implement SpeedDialFragment when FragmentUtils#getParent works + } + + @Override + public void onCallLogSelected() { + hideAllFragments(); + NewCallLogFragment fragment = + (NewCallLogFragment) supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); + if (fragment == null) { + supportFragmentManager + .beginTransaction() + .add(R.id.fragment_container, new NewCallLogFragment(), CALL_LOG_TAG) + .commit(); + } else { + supportFragmentManager.beginTransaction().show(fragment).commit(); + } + } + + @Override + public void onContactsSelected() { + hideAllFragments(); + // TODO(calderwoodra): Implement ContactsFragment when FragmentUtils#getParent works + } + + @Override + public void onVoicemailSelected() { + hideAllFragments(); + NewVoicemailFragment fragment = + (NewVoicemailFragment) supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG); + if (fragment == null) { + supportFragmentManager + .beginTransaction() + .add(R.id.fragment_container, new NewVoicemailFragment(), VOICEMAIL_TAG) + .commit(); + } else { + supportFragmentManager.beginTransaction().show(fragment).commit(); + } + } + + private void hideAllFragments() { + FragmentTransaction supportTransaction = supportFragmentManager.beginTransaction(); + if (supportFragmentManager.findFragmentByTag(CALL_LOG_TAG) != null) { + supportTransaction.hide(supportFragmentManager.findFragmentByTag(CALL_LOG_TAG)); + } + if (supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG) != null) { + supportTransaction.hide(supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG)); + } + supportTransaction.commit(); + } + } +} diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java new file mode 100644 index 000000000..489de1a64 --- /dev/null +++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java @@ -0,0 +1,815 @@ +/* + * 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.view.View; +import android.widget.ImageView; +import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; +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.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.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.interactions.PhoneNumberInteraction; +import com.android.dialer.main.MainActivityPeer; +import com.android.dialer.main.impl.BottomNavBar.OnBottomNavTabSelectedListener; +import com.android.dialer.main.impl.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.google.common.util.concurrent.ListenableFuture; +import java.util.concurrent.TimeUnit; + +/** + * OldMainActivityPeer which implements all of the old fragments we know and love <3 + * + * <p>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"; + + 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 MainOldSpeedDialFragmentHostInterface oldSpeedDialFragmentHostInterface; + private MainOnDragDropListener onDragDropListener; + + /** 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<String> getLastOutgoingCallListener; + + public OldMainActivityPeer(MainActivity mainActivity) { + this.mainActivity = mainActivity; + } + + @Override + public void onActivityCreate(Bundle savedInstanceState) { + mainActivity.setContentView(R.layout.main_activity); + initUiListeners(); + initLayout(savedInstanceState); + SmartDialPrefix.initializeNanpSettings(mainActivity); + } + + private void initUiListeners() { + getLastOutgoingCallListener = + DialerExecutorComponent.get(mainActivity) + .createUiListener(mainActivity.getFragmentManager(), "Query last phone number"); + } + + private void initLayout(Bundle savedInstanceState) { + onContactSelectedListener = new MainOnContactSelectedListener(mainActivity); + dialpadFragmentHostInterface = new MainDialpadFragmentHost(); + + snackbarContainer = mainActivity.findViewById(R.id.coordinator_layout); + + FloatingActionButton fab = mainActivity.findViewById(R.id.fab); + fab.setOnClickListener(v -> searchController.showDialpad(true)); + + MainToolbar toolbar = mainActivity.findViewById(R.id.toolbar); + mainActivity.setSupportActionBar(mainActivity.findViewById(R.id.toolbar)); + + bottomNav = mainActivity.findViewById(R.id.bottom_nav_bar); + MainBottomNavBarBottomNavTabListener bottomNavTabListener = + new MainBottomNavBarBottomNavTabListener(mainActivity.getFragmentManager()); + bottomNav.addOnTabSelectedListener(bottomNavTabListener); + + callLogFragmentListener = + new MainCallLogFragmentListener(mainActivity, mainActivity.getContentResolver(), bottomNav); + bottomNav.addOnTabSelectedListener(callLogFragmentListener); + + searchController = new MainSearchController(mainActivity, bottomNav, fab, toolbar); + toolbar.setSearchBarListener(searchController); + + onDialpadQueryChangedListener = new MainOnDialpadQueryChangedListener(searchController); + dialpadListener = + new MainDialpadListener(mainActivity, searchController, getLastOutgoingCallListener); + searchFragmentListener = new MainSearchFragmentListener(searchController); + callLogAdapterOnActionModeStateChangedListener = + new MainCallLogAdapterOnActionModeStateChangedListener(); + callLogHostInterface = new MainCallLogHost(searchController, fab); + + onListFragmentScrolledListener = new MainOnListFragmentScrolledListener(snackbarContainer); + onPhoneNumberPickerActionListener = new MainOnPhoneNumberPickerActionListener(mainActivity); + oldSpeedDialFragmentHostInterface = + new MainOldSpeedDialFragmentHostInterface( + bottomNav, mainActivity.findViewById(R.id.contact_tile_drag_shadow_overlay)); + onDragDropListener = new MainOnDragDropListener(); + + lastTabController = new LastTabController(mainActivity, bottomNav); + + // Restore our view state if needed, else initialize as if the app opened for the first time + if (savedInstanceState != null) { + savedLanguageCode = savedInstanceState.getString(KEY_SAVED_LANGUAGE_CODE); + searchController.onRestoreInstanceState(savedInstanceState); + bottomNav.selectTab(savedInstanceState.getInt(KEY_CURRENT_TAB)); + } else { + lastTabController.selectLastTab(); + } + } + + @Override + public void onNewIntent(Intent intent) { + lastTabController.selectLastTab(); + } + + @Override + public void onActivityResume() { + callLogFragmentListener.onActivityResume(); + // Start the thread that updates the smart dial database if the activity is recreated with a + // language change. + boolean forceUpdate = + !CompatUtils.getLocale(mainActivity).getISO3Language().equals(savedLanguageCode); + Database.get(mainActivity) + .getDatabaseHelper(mainActivity) + .startSmartDialUpdateThread(forceUpdate); + showPostCallPrompt(); + } + + @Override + public void onActivityStop() { + lastTabController.onActivityStop(); + callLogFragmentListener.onActivityStop( + mainActivity.isChangingConfigurations(), + mainActivity.getSystemService(KeyguardManager.class).isKeyguardLocked()); + } + + private void showPostCallPrompt() { + if (TelecomUtil.isInManagedCall(mainActivity)) { + // No prompt to show if the user is in a call + return; + } + + if (searchController.isInSearch()) { + // Don't show the prompt if we're in the search ui + return; + } + + PostCall.promptUserForMessageIfNecessary(mainActivity, snackbarContainer); + } + + @Override + public void onSaveInstanceState(Bundle bundle) { + bundle.putString( + KEY_SAVED_LANGUAGE_CODE, CompatUtils.getLocale(mainActivity).getISO3Language()); + bundle.putInt(KEY_CURRENT_TAB, bottomNav.getSelectedTab()); + searchController.onSaveInstanceState(bundle); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == ActivityRequestCodes.DIALTACTS_VOICE_SEARCH) { + searchController.onVoiceResults(resultCode, data); + } else { + LogUtil.e("MainActivity.onActivityResult", "Unknown request code: " + requestCode); + } + } + + @Override + public boolean onBackPressed() { + if (searchController.onBackPressed()) { + return true; + } + return false; + } + + @Nullable + @Override + @SuppressWarnings("unchecked") // Casts are checked using runtime methods + public <T> T getImpl(Class<T> callbackInterface) { + if (callbackInterface.isInstance(onContactSelectedListener)) { + return (T) onContactSelectedListener; + } else if (callbackInterface.isInstance(onDialpadQueryChangedListener)) { + return (T) onDialpadQueryChangedListener; + } else if (callbackInterface.isInstance(dialpadListener)) { + return (T) dialpadListener; + } else if (callbackInterface.isInstance(dialpadFragmentHostInterface)) { + return (T) dialpadFragmentHostInterface; + } else if (callbackInterface.isInstance(searchFragmentListener)) { + return (T) searchFragmentListener; + } else if (callbackInterface.isInstance(callLogAdapterOnActionModeStateChangedListener)) { + return (T) callLogAdapterOnActionModeStateChangedListener; + } else if (callbackInterface.isInstance(callLogHostInterface)) { + return (T) callLogHostInterface; + } else if (callbackInterface.isInstance(callLogFragmentListener)) { + return (T) callLogFragmentListener; + } else if (callbackInterface.isInstance(onListFragmentScrolledListener)) { + return (T) onListFragmentScrolledListener; + } else if (callbackInterface.isInstance(onPhoneNumberPickerActionListener)) { + return (T) onPhoneNumberPickerActionListener; + } else if (callbackInterface.isInstance(oldSpeedDialFragmentHostInterface)) { + return (T) oldSpeedDialFragmentHostInterface; + } else if (callbackInterface.isInstance(onDragDropListener)) { + return (T) onDragDropListener; + } else { + return null; + } + } + + /** @see OnContactSelectedListener */ + private static final class MainOnContactSelectedListener implements OnContactSelectedListener { + + private final Context context; + + MainOnContactSelectedListener(Context context) { + this.context = context; + } + + @Override + public void onContactSelected(ImageView photo, Uri contactUri, long contactId) { + // TODO(calderwoodra): Add impression logging + QuickContact.showQuickContact( + context, photo, contactUri, QuickContact.MODE_LARGE, null /* excludeMimes */); + } + } + + /** @see OnDialpadQueryChangedListener */ + private static final class MainOnDialpadQueryChangedListener + implements OnDialpadQueryChangedListener { + + private final MainSearchController searchController; + + MainOnDialpadQueryChangedListener(MainSearchController searchController) { + this.searchController = searchController; + } + + @Override + public void onDialpadQueryChanged(String query) { + searchController.onDialpadQueryChanged(query); + } + } + + /** @see DialpadListener */ + private static final class MainDialpadListener implements DialpadListener { + + private final MainSearchController searchController; + private final Context context; + private final UiListener<String> listener; + + MainDialpadListener( + Context context, MainSearchController searchController, UiListener<String> uiListener) { + this.context = context; + this.searchController = searchController; + this.listener = uiListener; + } + + @Override + public void getLastOutgoingCall(LastOutgoingCallCallback callback) { + ListenableFuture<String> listenableFuture = + DialerExecutorComponent.get(context) + .backgroundExecutor() + .submit(() -> Calls.getLastOutgoingCall(context)); + listener.listen(context, listenableFuture, callback::lastOutgoingCall, throwable -> {}); + } + + @Override + public void onDialpadShown() { + searchController.onDialpadShown(); + } + + @Override + public void onCallPlacedFromDialpad() { + // TODO(calderwoodra): logging + } + } + + /** @see SearchFragmentListener */ + private static final class MainSearchFragmentListener implements SearchFragmentListener { + + private final MainSearchController searchController; + + MainSearchFragmentListener(MainSearchController searchController) { + this.searchController = searchController; + } + + @Override + public void onSearchListTouch() { + searchController.onSearchListTouch(); + } + + @Override + public void onCallPlacedFromSearch() { + // TODO(calderwoodra): logging + } + } + + /** @see DialpadFragment.HostInterface */ + private static final class MainDialpadFragmentHost implements DialpadFragment.HostInterface { + + @Override + public boolean onDialpadSpacerTouchWithEmptyQuery() { + // No-op, just let the clicks fall through to the search list + return false; + } + } + + /** @see CallLogAdapter.OnActionModeStateChangedListener */ + // TODO(a bug): handle multiselect mode + private static final class MainCallLogAdapterOnActionModeStateChangedListener + implements CallLogAdapter.OnActionModeStateChangedListener { + + @Override + public void onActionModeStateChanged(boolean isEnabled) {} + + @Override + public boolean isActionModeStateEnabled() { + return false; + } + } + + /** @see CallLogFragment.HostInterface */ + private static final class MainCallLogHost implements CallLogFragment.HostInterface { + + private final FloatingActionButton fab; + private final MainSearchController searchController; + + MainCallLogHost(MainSearchController searchController, FloatingActionButton fab) { + this.searchController = searchController; + this.fab = fab; + } + + @Override + public void showDialpad() { + searchController.showDialpad(true); + } + + @Override + public void enableFloatingButton(boolean enabled) { + if (enabled) { + fab.show(); + } else { + fab.hide(); + } + } + } + + /** + * Handles the logic for callbacks from: + * + * <ul> + * <li>{@link CallLogFragment} + * <li>{@link CallLogQueryHandler} + * <li>{@link BottomNavBar} + * </ul> + * + * This mainly entails: + * + * <ul> + * <li>Handling querying for missed calls/unread voicemails. + * <li>Displaying a badge to the user in the bottom nav when there are missed calls/unread + * voicemails present. + * <li>Marking missed calls as read when appropriate. See {@link + * #markMissedCallsAsReadAndRemoveNotification()} + * <li>TODO(calderwoodra): multiselect + * <li>TODO(calderwoodra): voicemail status + * </ul> + * + * @see CallLogFragmentListener + * @see CallLogQueryHandler.Listener + * @see OnBottomNavTabSelectedListener + */ + private static final class MainCallLogFragmentListener + implements CallLogFragmentListener, + CallLogQueryHandler.Listener, + OnBottomNavTabSelectedListener { + + private final CallLogQueryHandler callLogQueryHandler; + private final BottomNavBar bottomNavBar; + private final Context context; + + private @TabIndex int currentTab = TabIndex.SPEED_DIAL; + private long timeSelected = -1; + private boolean activityIsAlive; + + MainCallLogFragmentListener( + Context context, ContentResolver contentResolver, BottomNavBar bottomNavBar) { + callLogQueryHandler = new CallLogQueryHandler(context, contentResolver, this); + this.bottomNavBar = bottomNavBar; + this.context = context; + } + + @Override + public void updateTabUnreadCounts() { + callLogQueryHandler.fetchMissedCallsUnreadCount(); + callLogQueryHandler.fetchVoicemailUnreadCount(); + } + + @Override + public void showMultiSelectRemoveView(boolean show) { + // TODO(a bug): handle multiselect mode + } + + @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 + } + } + + /** @see OldSpeedDialFragment.HostInterface */ + private static final class MainOldSpeedDialFragmentHostInterface + implements OldSpeedDialFragment.HostInterface { + + private final BottomNavBar bottomNavBar; + private final ImageView dragShadowOverlay; + + // TODO(calderwoodra): Use this for drag and drop + @SuppressWarnings("unused") + private DragDropController dragDropController; + + MainOldSpeedDialFragmentHostInterface(BottomNavBar bottomNavBar, ImageView dragShadowOverlay) { + this.bottomNavBar = bottomNavBar; + this.dragShadowOverlay = dragShadowOverlay; + } + + @Override + public void setDragDropController(DragDropController dragDropController) { + this.dragDropController = dragDropController; + } + + @Override + public void showAllContactsTab() { + bottomNavBar.selectTab(TabIndex.CONTACTS); + } + + @Override + public ImageView getDragShadowOverlay() { + return dragShadowOverlay; + } + } + + /** @see com.android.dialer.app.list.OnDragDropListener */ + // TODO(calderwoodra): implement drag and drop + private static final class MainOnDragDropListener implements OnDragDropListener { + + @Override + public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {} + + @Override + public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {} + + @Override + public void onDragFinished(int x, int y) {} + + @Override + public void onDroppedOnRemove() {} + } + + /** + * 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 MainBottomNavBarBottomNavTabListener(FragmentManager fragmentManager) { + this.fragmentManager = fragmentManager; + } + + @Override + public void onSpeedDialSelected() { + 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() { + 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() { + 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() { + 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; + + LastTabController(Context context, BottomNavBar bottomNavBar) { + this.context = context; + this.bottomNavBar = bottomNavBar; + isEnabled = ConfigProviderBindings.get(context).getBoolean("last_tab_enabled", false); + } + + /** 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); + } + bottomNavBar.selectTab(tabIndex); + } + + void onActivityStop() { + StorageComponent.get(context) + .unencryptedSharedPrefs() + .edit() + .putInt(KEY_LAST_TAB, bottomNavBar.getSelectedTab()) + .apply(); + } + } +} |