Merge "Refresh annotated call log from the voicemail tab"
diff --git a/java/com/android/dialer/util/PermissionsUtil.java b/java/com/android/dialer/util/PermissionsUtil.java
index 6684f95..09c2b7f 100644
--- a/java/com/android/dialer/util/PermissionsUtil.java
+++ b/java/com/android/dialer/util/PermissionsUtil.java
@@ -25,6 +25,7 @@
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.Manifest.permission.READ_VOICEMAIL;
+import static android.Manifest.permission.SEND_SMS;
 import static android.Manifest.permission.WRITE_CALL_LOG;
 import static android.Manifest.permission.WRITE_CONTACTS;
 import static android.Manifest.permission.WRITE_VOICEMAIL;
@@ -66,6 +67,7 @@
               WRITE_CALL_LOG,
               READ_PHONE_STATE,
               MODIFY_PHONE_STATE,
+              SEND_SMS,
               CALL_PHONE,
               ADD_VOICEMAIL,
               WRITE_VOICEMAIL,
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
index 0d91f01..8812bcc 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
@@ -27,17 +27,92 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import com.android.dialer.calllog.CallLogComponent;
+import com.android.dialer.calllog.CallLogFramework;
+import com.android.dialer.calllog.CallLogFramework.CallLogUi;
+import com.android.dialer.calllog.RefreshAnnotatedCallLogWorker;
 import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.common.concurrent.UiListener;
 import com.android.dialer.database.CallLogQueryHandler;
 import com.android.dialer.database.CallLogQueryHandler.Listener;
+import com.google.common.util.concurrent.ListenableFuture;
 
 // TODO(uabdullah): Register content observer for VoicemailContract.Status.CONTENT_URI in onStart
 /** Fragment for Dialer Voicemail Tab. */
-public final class NewVoicemailFragment extends Fragment implements LoaderCallbacks<Cursor> {
+public final class NewVoicemailFragment extends Fragment
+    implements LoaderCallbacks<Cursor>, CallLogUi {
+
+  /*
+   * This is a reasonable time that it might take between related call log writes, that also
+   * shouldn't slow down single-writes too much. For example, when populating the database using
+   * the simulator, using this value results in ~6 refresh cycles (on a release build) to write 120
+   * call log entries.
+   */
+  private static final long WAIT_MILLIS = 100L;
+
+  private RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker;
+  private UiListener<Void> refreshAnnotatedCallLogListener;
+  @Nullable private Runnable refreshAnnotatedCallLogRunnable;
 
   private RecyclerView recyclerView;
   private CallLogQueryHandler callLogQueryHandler;
 
+  public NewVoicemailFragment() {
+    LogUtil.enterBlock("NewVoicemailFragment.NewVoicemailFragment");
+  }
+
+  @Override
+  public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+    super.onActivityCreated(savedInstanceState);
+
+    LogUtil.enterBlock("NewVoicemailFragment.onActivityCreated");
+
+    CallLogComponent component = CallLogComponent.get(getContext());
+    CallLogFramework callLogFramework = component.callLogFramework();
+    callLogFramework.attachUi(this);
+
+    // TODO(zachh): Use support fragment manager and add support for them in executors library.
+    refreshAnnotatedCallLogListener =
+        DialerExecutorComponent.get(getContext())
+            .createUiListener(
+                getActivity().getFragmentManager(), "NewVoicemailFragment.refreshAnnotatedCallLog");
+    refreshAnnotatedCallLogWorker = component.getRefreshAnnotatedCallLogWorker();
+  }
+
+  @Override
+  public void onStart() {
+    super.onStart();
+    LogUtil.enterBlock("NewVoicemailFragment.onStart");
+  }
+
+  @Override
+  public void onResume() {
+    super.onResume();
+
+    LogUtil.enterBlock("NewCallLogFragment.onResume");
+
+    CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework();
+    callLogFramework.attachUi(this);
+
+    // TODO(zachh): Consider doing this when fragment becomes visible.
+    refreshAnnotatedCallLog(true /* checkDirty */);
+  }
+
+  @Override
+  public void onPause() {
+    super.onPause();
+
+    LogUtil.enterBlock("NewVoicemailFragment.onPause");
+
+    // This is pending work that we don't actually need to follow through with.
+    ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable);
+
+    CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework();
+    callLogFramework.detachUi();
+  }
+
   @Nullable
   @Override
   public View onCreateView(
@@ -49,6 +124,43 @@
     return view;
   }
 
+  private void refreshAnnotatedCallLog(boolean checkDirty) {
+    LogUtil.enterBlock("NewVoicemailFragment.refreshAnnotatedCallLog");
+
+    // If we already scheduled a refresh, cancel it and schedule a new one so that repeated requests
+    // in quick succession don't result in too much work. For example, if we get 10 requests in
+    // 10ms, and a complete refresh takes a constant 200ms, the refresh will take 300ms (100ms wait
+    // and 1 iteration @200ms) instead of 2 seconds (10 iterations @ 200ms) since the work requests
+    // are serialized in RefreshAnnotatedCallLogWorker.
+    //
+    // We might get many requests in quick succession, for example, when the simulator inserts
+    // hundreds of rows into the system call log, or when the data for a new call is incrementally
+    // written to different columns as it becomes available.
+    ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable);
+
+    refreshAnnotatedCallLogRunnable =
+        () -> {
+          ListenableFuture<Void> future =
+              checkDirty
+                  ? refreshAnnotatedCallLogWorker.refreshWithDirtyCheck()
+                  : refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck();
+          refreshAnnotatedCallLogListener.listen(
+              getContext(),
+              future,
+              unused -> {},
+              throwable -> {
+                throw new RuntimeException(throwable);
+              });
+        };
+    ThreadUtil.getUiThreadHandler().postDelayed(refreshAnnotatedCallLogRunnable, WAIT_MILLIS);
+  }
+
+  @Override
+  public void invalidateUi() {
+    LogUtil.enterBlock("NewVoicemailFragment.invalidateUi");
+    refreshAnnotatedCallLog(false /* checkDirty */);
+  }
+
   @Override
   public Loader<Cursor> onCreateLoader(int id, Bundle args) {
     LogUtil.enterBlock("NewVoicemailFragment.onCreateLoader");