blob: 4a7ce0bac2b3631887018be6557c3d19a4c192e8 [file] [log] [blame]
/*
* 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.calllog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.annotation.Nullable;
import com.android.dialer.calllog.RefreshAnnotatedCallLogWorker.RefreshResult;
import com.android.dialer.calllog.constants.IntentNames;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.ThreadUtil;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
import com.android.dialer.logging.LoggingBindings;
import com.android.dialer.metrics.FutureTimer;
import com.android.dialer.metrics.Metrics;
import com.android.dialer.metrics.MetricsComponent;
import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
/**
* A {@link BroadcastReceiver} that starts/cancels refreshing the annotated call log when notified.
*/
public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver {
/**
* 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 REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS = 100L;
private final RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker;
private final FutureTimer futureTimer;
private final LoggingBindings logger;
@Nullable private Runnable refreshAnnotatedCallLogRunnable;
/** Returns an {@link IntentFilter} containing all actions accepted by this broadcast receiver. */
public static IntentFilter getIntentFilter() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(IntentNames.ACTION_REFRESH_ANNOTATED_CALL_LOG);
intentFilter.addAction(IntentNames.ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG);
return intentFilter;
}
public RefreshAnnotatedCallLogReceiver(Context context) {
refreshAnnotatedCallLogWorker =
CallLogComponent.get(context).getRefreshAnnotatedCallLogWorker();
futureTimer = MetricsComponent.get(context).futureTimer();
logger = Logger.get(context);
}
@Override
public void onReceive(Context context, Intent intent) {
LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.onReceive");
String action = intent.getAction();
if (IntentNames.ACTION_REFRESH_ANNOTATED_CALL_LOG.equals(action)) {
boolean checkDirty = intent.getBooleanExtra(IntentNames.EXTRA_CHECK_DIRTY, false);
refreshAnnotatedCallLog(checkDirty);
} else if (IntentNames.ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG.equals(action)) {
cancelRefreshingAnnotatedCallLog();
}
}
/**
* Request a refresh of the annotated call log.
*
* <p>Note that the execution will be delayed by {@link #REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS}.
* Once the work begins, it can't be cancelled.
*
* @see #cancelRefreshingAnnotatedCallLog()
*/
private void refreshAnnotatedCallLog(boolean checkDirty) {
LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.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<RefreshResult> future =
checkDirty
? refreshAnnotatedCallLogWorker.refreshWithDirtyCheck()
: refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck();
Futures.addCallback(
future,
new FutureCallback<RefreshResult>() {
@Override
public void onSuccess(RefreshResult refreshResult) {
logger.logImpression(getImpressionType(checkDirty, refreshResult));
}
@Override
public void onFailure(Throwable throwable) {
ThreadUtil.getUiThreadHandler()
.post(
() -> {
throw new RuntimeException(throwable);
});
}
},
MoreExecutors.directExecutor());
futureTimer.applyTiming(future, new EventNameFromResultFunction(checkDirty));
};
ThreadUtil.getUiThreadHandler()
.postDelayed(refreshAnnotatedCallLogRunnable, REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS);
}
/**
* When a refresh is requested, its execution is delayed (see {@link
* #refreshAnnotatedCallLog(boolean)}). This method only cancels the refresh if it hasn't started.
*/
private void cancelRefreshingAnnotatedCallLog() {
LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.cancelRefreshingAnnotatedCallLog");
ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable);
}
private static class EventNameFromResultFunction implements Function<RefreshResult, String> {
private final boolean checkDirty;
private EventNameFromResultFunction(boolean checkDirty) {
this.checkDirty = checkDirty;
}
@Override
public String apply(RefreshResult refreshResult) {
switch (refreshResult) {
case NOT_DIRTY:
return Metrics.ANNOTATED_CALL_LOG_NOT_DIRTY; // NOT_DIRTY implies forceRefresh is false
case REBUILT_BUT_NO_CHANGES_NEEDED:
return checkDirty
? Metrics.ANNOTATED_LOG_NO_CHANGES_NEEDED
: Metrics.NEW_CALL_LOG_FORCE_REFRESH_NO_CHANGES_NEEDED;
case REBUILT_AND_CHANGES_NEEDED:
return checkDirty
? Metrics.ANNOTATED_CALL_LOG_CHANGES_NEEDED
: Metrics.ANNOTATED_CALL_LOG_FORCE_REFRESH_CHANGES_NEEDED;
default:
throw new IllegalStateException("Unsupported result: " + refreshResult);
}
}
}
private static DialerImpression.Type getImpressionType(
boolean checkDirty, RefreshResult refreshResult) {
switch (refreshResult) {
case NOT_DIRTY:
return DialerImpression.Type.ANNOTATED_CALL_LOG_NOT_DIRTY;
case REBUILT_BUT_NO_CHANGES_NEEDED:
return checkDirty
? DialerImpression.Type.ANNOTATED_CALL_LOG_NO_CHANGES_NEEDED
: DialerImpression.Type.ANNOTATED_CALL_LOG_FORCE_REFRESH_NO_CHANGES_NEEDED;
case REBUILT_AND_CHANGES_NEEDED:
return checkDirty
? DialerImpression.Type.ANNOTATED_CALL_LOG_CHANGES_NEEDED
: DialerImpression.Type.ANNOTATED_CALL_LOG_FORCE_REFRESH_CHANGES_NEEDED;
default:
throw new IllegalStateException("Unsupported result: " + refreshResult);
}
}
}