Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0ee81f17 authored by roldenburg's avatar roldenburg Committed by android-build-merger
Browse files

Merge "*** Reason for rollback ***" am: 1d18e69c am: 36b25c52

am: b71facc1

Change-Id: If87e6208c8956265329ce0168201b13746978c72
parents ed39c211 b71facc1
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@
    <item name="android:colorAccent">@color/dialtacts_theme_color</item>
  </style>

  <style name="DialtactsTheme" parent="DialerThemeBase">
  <style name="DialtactsThemeBase" parent="DialerThemeBase">

    <!-- Styles that require AppCompat compatibility, remember to update both sets -->
    <item name="android:windowActionBarOverlay">true</item>
@@ -77,6 +77,9 @@
    <item name="dialpad_style">@style/Dialpad.Light</item>
  </style>

  <style name="DialtactsTheme" parent="DialtactsThemeBase">
  </style>

  <!-- Action bar overflow menu icon.  White with no shadow. -->
  <style name="DialtactsActionBarOverflowWhite"
    parent="@android:style/Widget.Material.Light.ActionButton.Overflow">
+48 −13
Original line number Diff line number Diff line
@@ -29,39 +29,74 @@ import com.android.dialer.calllog.datasources.CallLogMutations;
import com.android.dialer.calllog.datasources.DataSources;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.DialerExecutor.Worker;
import com.android.dialer.common.concurrent.Annotations.UiSerial;
import com.android.dialer.inject.ApplicationContext;
import com.android.dialer.storage.Unencrypted;
import com.google.common.util.concurrent.ListenableScheduledFuture;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;

/**
 * Worker which brings the annotated call log up to date, if necessary.
 *
 * <p>Accepts a boolean which indicates if the dirty check should be skipped.
/** Brings the annotated call log up to date, if necessary. */
public class RefreshAnnotatedCallLogWorker {

  /*
   * 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.
   */
public class RefreshAnnotatedCallLogWorker implements Worker<Boolean, Void> {
  private static final long WAIT_MILLIS = 100L;

  private final Context appContext;
  private final DataSources dataSources;
  private final SharedPreferences sharedPreferences;
  private final ListeningScheduledExecutorService listeningScheduledExecutorService;
  private ListenableScheduledFuture<Void> scheduledFuture;

  @Inject
  RefreshAnnotatedCallLogWorker(
      @ApplicationContext Context appContext,
      DataSources dataSources,
      @Unencrypted SharedPreferences sharedPreferences) {
      @Unencrypted SharedPreferences sharedPreferences,
      @UiSerial ScheduledExecutorService serialUiExecutorService) {
    this.appContext = appContext;
    this.dataSources = dataSources;
    this.sharedPreferences = sharedPreferences;
    this.listeningScheduledExecutorService =
        MoreExecutors.listeningDecorator(serialUiExecutorService);
  }

  @Override
  public Void doInBackground(Boolean skipDirtyCheck)
  /** Checks if the annotated call log is dirty and refreshes it if necessary. */
  public ListenableScheduledFuture<Void> refreshWithDirtyCheck() {
    return refresh(true);
  }

  /** Refreshes the annotated call log, bypassing dirty checks. */
  public ListenableScheduledFuture<Void> refreshWithoutDirtyCheck() {
    return refresh(false);
  }

  private ListenableScheduledFuture<Void> refresh(boolean checkDirty) {
    if (scheduledFuture != null) {
      LogUtil.i("RefreshAnnotatedCallLogWorker.refresh", "cancelling waiting task");
      scheduledFuture.cancel(false /* mayInterrupt */);
    }
    scheduledFuture =
        listeningScheduledExecutorService.schedule(
            () -> doInBackground(checkDirty), WAIT_MILLIS, TimeUnit.MILLISECONDS);
    return scheduledFuture;
  }

  @WorkerThread
  private Void doInBackground(boolean checkDirty)
      throws RemoteException, OperationApplicationException {
    LogUtil.enterBlock("RefreshAnnotatedCallLogWorker.doInBackground");

    long startTime = System.currentTimeMillis();
    checkDirtyAndRebuildIfNecessary(appContext, skipDirtyCheck);
    checkDirtyAndRebuildIfNecessary(appContext, checkDirty);
    LogUtil.i(
        "RefreshAnnotatedCallLogWorker.doInBackground",
        "took %dms",
@@ -70,7 +105,7 @@ public class RefreshAnnotatedCallLogWorker implements Worker<Boolean, Void> {
  }

  @WorkerThread
  private void checkDirtyAndRebuildIfNecessary(Context appContext, boolean skipDirtyCheck)
  private void checkDirtyAndRebuildIfNecessary(Context appContext, boolean checkDirty)
      throws RemoteException, OperationApplicationException {
    Assert.isWorkerThread();

@@ -86,7 +121,7 @@ public class RefreshAnnotatedCallLogWorker implements Worker<Boolean, Void> {
          "annotated call log has been marked dirty or does not exist");
    }

    boolean isDirty = skipDirtyCheck || forceRebuildPrefValue || isDirty(appContext);
    boolean isDirty = !checkDirty || forceRebuildPrefValue || isDirty(appContext);

    LogUtil.i(
        "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary",
+15 −23
Original line number Diff line number Diff line
@@ -28,24 +28,18 @@ 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.DialerExecutor;
import com.android.dialer.common.concurrent.DialerExecutorComponent;
import com.android.dialer.common.concurrent.DialerExecutorFactory;
import com.android.dialer.common.concurrent.UiListener;
import com.google.common.util.concurrent.ListenableScheduledFuture;

/** The "new" call log fragment implementation, which is built on top of the annotated call log. */
public final class NewCallLogFragment extends Fragment
    implements CallLogUi, LoaderCallbacks<Cursor> {

  /*
   * 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 DialerExecutor<Boolean> refreshAnnotatedCallLogTask;
  private RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker;
  private UiListener<Void> refreshAnnotatedCallLogListener;
  private RecyclerView recyclerView;

  public NewCallLogFragment() {
@@ -62,17 +56,12 @@ public final class NewCallLogFragment extends Fragment
    CallLogFramework callLogFramework = component.callLogFramework();
    callLogFramework.attachUi(this);

    DialerExecutorFactory dialerExecutorFactory =
        DialerExecutorComponent.get(getContext()).dialerExecutorFactory();

    // TODO(zachh): Use support fragment manager and add support for them in executors library.
    refreshAnnotatedCallLogTask =
        dialerExecutorFactory
            .createUiTaskBuilder(
                getActivity().getFragmentManager(),
                "NewCallLogFragment.refreshAnnotatedCallLog",
                component.getRefreshAnnotatedCallLogWorker())
            .build();
    refreshAnnotatedCallLogListener =
        DialerExecutorComponent.get(getContext())
            .createUiListener(
                getActivity().getFragmentManager(), "NewCallLogFragment.refreshAnnotatedCallLog");
    refreshAnnotatedCallLogWorker = component.getRefreshAnnotatedCallLogWorker();
  }

  @Override
@@ -120,13 +109,16 @@ public final class NewCallLogFragment extends Fragment

  private void checkAnnotatedCallLogDirtyAndRefreshIfNecessary() {
    LogUtil.enterBlock("NewCallLogFragment.checkAnnotatedCallLogDirtyAndRefreshIfNecessary");
    refreshAnnotatedCallLogTask.executeSerialWithWait(false /* skipDirtyCheck */, WAIT_MILLIS);
    ListenableScheduledFuture<Void> future = refreshAnnotatedCallLogWorker.refreshWithDirtyCheck();
    refreshAnnotatedCallLogListener.listen(future, unused -> {}, RuntimeException::new);
  }

  @Override
  public void invalidateUi() {
    LogUtil.enterBlock("NewCallLogFragment.invalidateUi");
    refreshAnnotatedCallLogTask.executeSerialWithWait(true /* skipDirtyCheck */, WAIT_MILLIS);
    ListenableScheduledFuture<Void> future =
        refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck();
    refreshAnnotatedCallLogListener.listen(future, unused -> {}, RuntimeException::new);
  }

  @Override
+11 −0
Original line number Diff line number Diff line
@@ -16,9 +16,12 @@

package com.android.dialer.common.concurrent;

import android.app.FragmentManager;
import android.content.Context;
import com.android.dialer.common.concurrent.Annotations.NonUiParallel;
import com.android.dialer.common.concurrent.Annotations.Ui;
import com.android.dialer.inject.HasRootComponent;
import com.google.common.util.concurrent.ListeningExecutorService;
import dagger.Subcomponent;
import java.util.concurrent.ExecutorService;

@@ -28,6 +31,14 @@ public abstract class DialerExecutorComponent {

  public abstract DialerExecutorFactory dialerExecutorFactory();

  @Ui
  public abstract ListeningExecutorService uiExecutorService();

  public <OutputT> UiListener<OutputT> createUiListener(
      FragmentManager fragmentManager, String taskId) {
    return UiListener.create(uiExecutorService(), fragmentManager, taskId);
  }

  @NonUiParallel
  public abstract ExecutorService lowPriorityThreadPool();

+143 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.common.concurrent;

import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.DialerExecutor.FailureListener;
import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.Executor;

/**
 * A headless fragment for use in UI components that interact with ListenableFutures.
 *
 * <p>Callbacks are only executed if the UI component is still alive.
 *
 * <p>Example usage: <code><pre>
 * public class MyActivity extends Activity {
 *
 *   private UiListener&lt;MyOutputType&gt uiListener;
 *
 *   public void onCreate(Bundle bundle) {
 *     super.onCreate(bundle);
 *
 *     // Must be called in onCreate!
 *     uiListener = DialerExecutorComponent.get(context).createUiListener(fragmentManager, taskId);
 *   }
 *
 *   private void onSuccess(MyOutputType output) { ... }
 *   private void onFailure(Throwable throwable) { ... }
 *
 *   private void userDidSomething() {
 *     ListenableFuture&lt;MyOutputType&gt; future = callSomeMethodReturningListenableFuture(input);
 *     uiListener.listen(future, this::onSuccess, this::onFailure);
 *   }
 * }
 * </pre></code>
 */
public class UiListener<OutputT> extends Fragment {

  private Executor uiThreadExecutor;
  private CallbackWrapper<OutputT> callbackWrapper;

  @MainThread
  static <OutputT> UiListener<OutputT> create(
      Executor uiThreadExecutor, FragmentManager fragmentManager, String taskId) {
    @SuppressWarnings("unchecked")
    UiListener<OutputT> uiListener =
        (UiListener<OutputT>) fragmentManager.findFragmentByTag(taskId);

    if (uiListener == null) {
      LogUtil.i("UiListener.create", "creating new UiListener for " + taskId);
      uiListener = new UiListener<>();
      uiListener.uiThreadExecutor = uiThreadExecutor;
      fragmentManager.beginTransaction().add(uiListener, taskId).commit();
    }
    return uiListener;
  }

  /**
   * Adds the specified listeners to the provided future.
   *
   * <p>The listeners are not called if the UI component this {@link UiListener} is declared in is
   * dead.
   */
  @MainThread
  public void listen(
      @NonNull ListenableFuture<OutputT> future,
      @NonNull SuccessListener<OutputT> successListener,
      @NonNull FailureListener failureListener) {
    callbackWrapper =
        new CallbackWrapper<>(Assert.isNotNull(successListener), Assert.isNotNull(failureListener));
    Futures.addCallback(Assert.isNotNull(future), callbackWrapper, uiThreadExecutor);
  }

  private static class CallbackWrapper<OutputT> implements FutureCallback<OutputT> {
    private SuccessListener<OutputT> successListener;
    private FailureListener failureListener;

    private CallbackWrapper(
        SuccessListener<OutputT> successListener, FailureListener failureListener) {
      this.successListener = successListener;
      this.failureListener = failureListener;
    }

    @Override
    public void onSuccess(@Nullable OutputT output) {
      if (successListener == null) {
        LogUtil.i("UiListener.runTask", "task succeeded but UI is dead");
      } else {
        successListener.onSuccess(output);
      }
    }

    @Override
    public void onFailure(Throwable throwable) {
      LogUtil.e("UiListener.runTask", "task failed", throwable);
      if (failureListener == null) {
        LogUtil.i("UiListener.runTask", "task failed but UI is dead");
      } else {
        failureListener.onFailure(throwable);
      }
    }
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);
  }

  @Override
  public void onDetach() {
    super.onDetach();
    LogUtil.enterBlock("UiListener.onDetach");
    if (callbackWrapper != null) {
      callbackWrapper.successListener = null;
      callbackWrapper.failureListener = null;
    }
  }
}
Loading