Merge "Support new call log fragment in old peer."
diff --git a/java/com/android/dialer/calllog/CallLogFramework.java b/java/com/android/dialer/calllog/CallLogFramework.java
index 53ff430..b86bfbc 100644
--- a/java/com/android/dialer/calllog/CallLogFramework.java
+++ b/java/com/android/dialer/calllog/CallLogFramework.java
@@ -16,11 +16,17 @@
 
 package com.android.dialer.calllog;
 
+import android.content.Context;
+import android.content.Intent;
+import android.support.v4.content.LocalBroadcastManager;
 import com.android.dialer.calllog.datasources.CallLogDataSource;
 import com.android.dialer.calllog.datasources.DataSources;
 import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.Annotations.Ui;
+import com.android.dialer.inject.ApplicationContext;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import java.util.ArrayList;
 import java.util.List;
@@ -35,13 +41,24 @@
 @Singleton
 public final class CallLogFramework {
 
+  private final Context appContext;
   private final DataSources dataSources;
   private final AnnotatedCallLogMigrator annotatedCallLogMigrator;
+  private final ListeningExecutorService uiExecutor;
+  private final CallLogState callLogState;
 
   @Inject
-  CallLogFramework(DataSources dataSources, AnnotatedCallLogMigrator annotatedCallLogMigrator) {
+  CallLogFramework(
+      @ApplicationContext Context appContext,
+      DataSources dataSources,
+      AnnotatedCallLogMigrator annotatedCallLogMigrator,
+      @Ui ListeningExecutorService uiExecutor,
+      CallLogState callLogState) {
+    this.appContext = appContext;
     this.dataSources = dataSources;
     this.annotatedCallLogMigrator = annotatedCallLogMigrator;
+    this.uiExecutor = uiExecutor;
+    this.callLogState = callLogState;
   }
 
   /** Registers the content observers for all data sources. */
@@ -73,12 +90,25 @@
       dataSource.unregisterContentObservers();
     }
 
+    callLogState.clearData();
+
     // Clear data only after all content observers have been disabled.
     List<ListenableFuture<Void>> allFutures = new ArrayList<>();
     for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) {
       allFutures.add(dataSource.clearData());
     }
+
     return Futures.transform(
-        Futures.allAsList(allFutures), unused -> null, MoreExecutors.directExecutor());
+        Futures.allAsList(allFutures),
+        unused -> {
+          // Send a broadcast to the OldMainActivityPeer to remove the NewCallLogFragment if it is
+          // currently attached. If this is not done, user interaction with the fragment could cause
+          // call log framework state to be unexpectedly written. For example scrolling could cause
+          // the AnnotatedCallLog to be read (which would trigger database creation).
+          LocalBroadcastManager.getInstance(appContext)
+              .sendBroadcastSync(new Intent("disableNewCallLogFragment"));
+          return null;
+        },
+        uiExecutor);
   }
 }
diff --git a/java/com/android/dialer/calllog/CallLogState.java b/java/com/android/dialer/calllog/CallLogState.java
index bdb30a7..6d2ec1e 100644
--- a/java/com/android/dialer/calllog/CallLogState.java
+++ b/java/com/android/dialer/calllog/CallLogState.java
@@ -54,6 +54,15 @@
   }
 
   /**
+   * Clear the call log state. This is useful for example if the annotated call log needs to be
+   * disabled because there was a problem.
+   */
+  @AnyThread
+  public void clearData() {
+    sharedPreferences.edit().remove(ANNOTATED_CALL_LOG_BUILT_PREF).apply();
+  }
+
+  /**
    * Returns true if the annotated call log has been built at least once.
    *
    * <p>It may not yet have been built if the user was just upgraded to the new call log, or they
diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
index 9332acd..8362a81 100644
--- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
@@ -30,7 +30,6 @@
 import android.provider.VoicemailContract;
 import android.provider.VoicemailContract.Voicemails;
 import android.support.annotation.ColorInt;
-import android.support.annotation.MainThread;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.VisibleForTesting;
@@ -102,11 +101,8 @@
     this.annotatedCallLogDatabaseHelper = annotatedCallLogDatabaseHelper;
   }
 
-  @MainThread
   @Override
   public void registerContentObservers() {
-    Assert.isMainThread();
-
     LogUtil.enterBlock("SystemCallLogDataSource.registerContentObservers");
 
     if (!PermissionsUtil.hasCallLogReadPermissions(appContext)) {
diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java
index 9e8bd1a..6d78a51 100644
--- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java
+++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java
@@ -21,9 +21,11 @@
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.app.KeyguardManager;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
@@ -37,6 +39,7 @@
 import android.support.design.widget.FloatingActionButton;
 import android.support.design.widget.Snackbar;
 import android.support.v4.content.ContextCompat;
+import android.support.v4.content.LocalBroadcastManager;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
 import android.telecom.PhoneAccount;
@@ -65,6 +68,8 @@
 import com.android.dialer.calldetails.OldCallDetailsActivity;
 import com.android.dialer.callintent.CallIntentBuilder;
 import com.android.dialer.callintent.CallSpecificAppData;
+import com.android.dialer.calllog.config.CallLogConfigComponent;
+import com.android.dialer.calllog.ui.NewCallLogFragment;
 import com.android.dialer.common.FragmentUtils.FragmentUtilListener;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.DialerExecutorComponent;
@@ -110,7 +115,6 @@
 import com.android.voicemail.VoicemailComponent;
 import com.google.common.util.concurrent.ListenableFuture;
 import java.util.Locale;
-import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -132,6 +136,23 @@
   // TODO(calderwoodra): change to AppCompatActivity once new speed dial ships
   private final TransactionSafeActivity activity;
 
+  private final BroadcastReceiver disableNewCallLogReceiver =
+      new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+          if (bottomNavTabListener == null) {
+            return;
+          }
+          /*
+           * Remove the NewCallLogFragment if it is currently attached. If this is not done, user
+           * interaction with the fragment could cause call log framework state to be unexpectedly
+           * written. For example scrolling could cause the AnnotatedCallLog to be read (which would
+           * trigger database creation).
+           */
+          bottomNavTabListener.disableNewCallLogFragment();
+        }
+      };
+
   // Contacts
   private MainOnContactSelectedListener onContactSelectedListener;
 
@@ -161,6 +182,7 @@
   private LastTabController lastTabController;
 
   private BottomNavBar bottomNav;
+  private MainBottomNavBarBottomNavTabListener bottomNavTabListener;
   private View snackbarContainer;
   private UiListener<String> getLastOutgoingCallListener;
 
@@ -217,8 +239,9 @@
     activity.setSupportActionBar(activity.findViewById(R.id.toolbar));
 
     bottomNav = activity.findViewById(R.id.bottom_nav_bar);
-    MainBottomNavBarBottomNavTabListener bottomNavTabListener =
-        new MainBottomNavBarBottomNavTabListener(activity, activity.getFragmentManager(), fab);
+    bottomNavTabListener =
+        new MainBottomNavBarBottomNavTabListener(
+            activity, activity.getFragmentManager(), activity.getSupportFragmentManager(), fab);
     bottomNav.addOnTabSelectedListener(bottomNavTabListener);
     // TODO(uabdullah): Handle case of when a sim is inserted/removed while the activity is open.
     boolean showVoicemailTab = canVoicemailTabBeShown(activity);
@@ -432,6 +455,22 @@
       bottomNav.setVisibility(View.VISIBLE);
     }
 
+    /*
+     * While the activity is running, listen for the call log framework being disabled. If this is
+     * not done, user interaction with the fragment could cause call log framework state to be
+     * unexpectedly written. For example scrolling could cause the AnnotatedCallLog to be read
+     * (which would trigger database creation).
+     */
+    LocalBroadcastManager.getInstance(activity)
+        .registerReceiver(disableNewCallLogReceiver, new IntentFilter("disableNewCallLogFragment"));
+
+    /*
+     * Similar to above, if the new call log is being shown and then the activity is paused, when
+     * the user returns we need to remove the NewCallLogFragment if the framework has been disabled
+     * in the meantime.
+     */
+    bottomNavTabListener.ensureCorrectCallLogShown();
+
     // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume.
     ThreadUtil.postDelayedOnUiThread(
         () ->
@@ -449,6 +488,7 @@
   @Override
   public void onActivityPause() {
     searchController.onActivityPause();
+    LocalBroadcastManager.getInstance(activity).unregisterReceiver(disableNewCallLogReceiver);
   }
 
   @Override
@@ -1122,14 +1162,19 @@
 
     private final AppCompatActivity activity;
     private final FragmentManager fragmentManager;
+    private final android.support.v4.app.FragmentManager supportFragmentManager;
     private final FloatingActionButton fab;
 
     @TabIndex private int selectedTab = -1;
 
     private MainBottomNavBarBottomNavTabListener(
-        AppCompatActivity activity, FragmentManager fragmentManager, FloatingActionButton fab) {
+        AppCompatActivity activity,
+        FragmentManager fragmentManager,
+        android.support.v4.app.FragmentManager supportFragmentManager,
+        FloatingActionButton fab) {
       this.activity = activity;
       this.fragmentManager = fragmentManager;
+      this.supportFragmentManager = supportFragmentManager;
       this.fab = fab;
     }
 
@@ -1154,11 +1199,46 @@
       }
       Logger.get(activity).logScreenView(ScreenEvent.Type.MAIN_CALL_LOG, activity);
       selectedTab = TabIndex.CALL_LOG;
-      Fragment fragment = fragmentManager.findFragmentByTag(CALL_LOG_TAG);
-      showFragment(fragment == null ? new CallLogFragment() : fragment, CALL_LOG_TAG);
+
+      if (CallLogConfigComponent.get(activity).callLogConfig().isNewCallLogFragmentEnabled()) {
+        android.support.v4.app.Fragment supportFragment =
+            supportFragmentManager.findFragmentByTag(CALL_LOG_TAG);
+        showSupportFragment(
+            supportFragment == null ? new NewCallLogFragment() : supportFragment, CALL_LOG_TAG);
+      } else {
+        Fragment fragment = fragmentManager.findFragmentByTag(CALL_LOG_TAG);
+        showFragment(fragment == null ? new CallLogFragment() : fragment, CALL_LOG_TAG);
+      }
       fab.show();
     }
 
+    void disableNewCallLogFragment() {
+      LogUtil.i("MainBottomNavBarBottomNavTabListener.disableNewCallLogFragment", "disabled");
+      android.support.v4.app.Fragment supportFragment =
+          supportFragmentManager.findFragmentByTag(CALL_LOG_TAG);
+      if (supportFragment != null) {
+        supportFragmentManager.beginTransaction().remove(supportFragment).commit();
+        // If the NewCallLogFragment was showing, immediately show the old call log fragment
+        // instead.
+        if (selectedTab == TabIndex.CALL_LOG) {
+          LogUtil.i(
+              "MainBottomNavBarBottomNavTabListener.disableNewCallLogFragment", "showing old");
+          Fragment fragment = fragmentManager.findFragmentByTag(CALL_LOG_TAG);
+          showFragment(fragment == null ? new CallLogFragment() : fragment, CALL_LOG_TAG);
+        }
+      }
+    }
+
+    void ensureCorrectCallLogShown() {
+      android.support.v4.app.Fragment supportFragment =
+          supportFragmentManager.findFragmentByTag(CALL_LOG_TAG);
+      if (supportFragment != null
+          && !CallLogConfigComponent.get(activity).callLogConfig().isNewCallLogFragmentEnabled()) {
+        LogUtil.i("MainBottomNavBarBottomNavTabListener.ensureCorrectCallLogShown", "disabling");
+        disableNewCallLogFragment();
+      }
+    }
+
     @Override
     public void onContactsSelected() {
       LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onContactsSelected");
@@ -1193,34 +1273,65 @@
       fragment.onVisible();
     }
 
+    private void showFragment(@NonNull Fragment fragment, String tag) {
+      showFragment(fragment, null, tag);
+    }
+
     /**
      * Shows the passed in fragment and hides all of the others in one transaction.
      *
+     * <p>Exactly one of fragment or supportFragment should be provided.
+     *
      * <p>Executes all fragment shows/hides in one transaction with no conflicting transactions
      * (like showing and hiding the same fragment in the same transaction). See a bug.
      *
      * <p>Special care should be taken to avoid calling this method several times in a short window
      * as it can lead to fragments overlapping.
      */
-    private void showFragment(@NonNull Fragment fragment, String tag) {
+    private void showFragment(
+        @Nullable Fragment fragment,
+        @Nullable android.support.v4.app.Fragment supportFragment,
+        String tag) {
       LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.showFragment");
       Fragment speedDial = fragmentManager.findFragmentByTag(SPEED_DIAL_TAG);
-      Fragment callLog = fragmentManager.findFragmentByTag(CALL_LOG_TAG);
+      Fragment oldCallLog = fragmentManager.findFragmentByTag(CALL_LOG_TAG);
       Fragment contacts = fragmentManager.findFragmentByTag(CONTACTS_TAG);
       Fragment voicemail = fragmentManager.findFragmentByTag(VOICEMAIL_TAG);
 
       FragmentTransaction transaction = fragmentManager.beginTransaction();
       boolean fragmentShown = showIfEqualElseHide(transaction, fragment, speedDial);
-      fragmentShown |= showIfEqualElseHide(transaction, fragment, callLog);
+      fragmentShown |= showIfEqualElseHide(transaction, fragment, oldCallLog);
       fragmentShown |= showIfEqualElseHide(transaction, fragment, contacts);
       fragmentShown |= showIfEqualElseHide(transaction, fragment, voicemail);
 
-      if (!fragmentShown) {
+      if (!fragmentShown && fragment != null) {
         LogUtil.i(
             "MainBottomNavBarBottomNavTabListener.showFragment", "Not added yet: " + fragment);
         transaction.add(R.id.fragment_container, fragment, tag);
       }
       transaction.commit();
+
+      // Handle support fragments.
+      // TODO(calderwoodra): Handle other new fragments.
+      android.support.v4.app.Fragment newCallLog =
+          supportFragmentManager.findFragmentByTag(CALL_LOG_TAG);
+
+      android.support.v4.app.FragmentTransaction supportTransaction =
+          supportFragmentManager.beginTransaction();
+      boolean supportFragmentShown =
+          showIfEqualElseHideSupport(supportTransaction, supportFragment, newCallLog);
+      if (!supportFragmentShown && supportFragment != null) {
+        LogUtil.i(
+            "MainBottomNavBarBottomNavTabListener.showFragment",
+            "Not added yet: " + supportFragment);
+        supportTransaction.add(R.id.fragment_container, supportFragment, tag);
+      }
+      supportTransaction.commit();
+    }
+
+    private void showSupportFragment(
+        @NonNull android.support.v4.app.Fragment supportFragment, String tag) {
+      showFragment(null, supportFragment, tag);
     }
 
     /**
@@ -1231,7 +1342,7 @@
     private boolean showIfEqualElseHide(
         FragmentTransaction transaction, Fragment fragment1, Fragment fragment2) {
       boolean shown = false;
-      if (Objects.equals(fragment1, fragment2)) {
+      if (fragment1 != null && fragment1.equals(fragment2)) {
         transaction.show(fragment1);
         shown = true;
       } else if (fragment2 != null) {
@@ -1243,6 +1354,25 @@
       }
       return shown;
     }
+
+    /**
+     * @param supportFragment1 will be shown if equal to {@code fragment2}
+     * @param supportFragment2 will be hidden if unequal to {@code fragment1}
+     * @return {@code true} if {@code fragment1} was shown
+     */
+    private boolean showIfEqualElseHideSupport(
+        android.support.v4.app.FragmentTransaction supportTransaction,
+        android.support.v4.app.Fragment supportFragment1,
+        android.support.v4.app.Fragment supportFragment2) {
+      boolean shown = false;
+      if (supportFragment1 != null && supportFragment1.equals(supportFragment2)) {
+        supportTransaction.show(supportFragment1);
+        shown = true;
+      } else if (supportFragment2 != null) {
+        supportTransaction.hide(supportFragment2);
+      }
+      return shown;
+    }
   }
 
   private static final class LastTabController {