Loading java/com/android/dialer/calllog/RefreshAnnotatedCallLogReceiver.java +35 −1 Original line number Diff line number Diff line Loading @@ -21,10 +21,15 @@ 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.DefaultFutureCallback; import com.android.dialer.common.concurrent.ThreadUtil; 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.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; Loading @@ -43,6 +48,7 @@ public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver { private static final long REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS = 100L; private final RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; private final FutureTimer futureTimer; @Nullable private Runnable refreshAnnotatedCallLogRunnable; Loading @@ -57,6 +63,7 @@ public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver { public RefreshAnnotatedCallLogReceiver(Context context) { refreshAnnotatedCallLogWorker = CallLogComponent.get(context).getRefreshAnnotatedCallLogWorker(); futureTimer = MetricsComponent.get(context).futureTimer(); } @Override Loading Loading @@ -97,12 +104,14 @@ public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver { refreshAnnotatedCallLogRunnable = () -> { ListenableFuture<Void> future = ListenableFuture<RefreshResult> future = checkDirty ? refreshAnnotatedCallLogWorker.refreshWithDirtyCheck() : refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(); Futures.addCallback( future, new DefaultFutureCallback<>(), MoreExecutors.directExecutor()); futureTimer.applyTiming(future, new EventNameFromResultFunction(checkDirty)); // TODO(zachh): Should also log impression counts of RefreshResults. }; ThreadUtil.getUiThreadHandler() Loading @@ -118,4 +127,29 @@ public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver { 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.REFRESH_NOT_DIRTY; // NOT_DIRTY implies forceRefresh is false case REBUILT_BUT_NO_CHANGES_NEEDED: return checkDirty ? Metrics.REFRESH_NO_CHANGES_NEEDED : Metrics.FORCE_REFRESH_NO_CHANGES_NEEDED; case REBUILT_AND_CHANGES_NEEDED: return checkDirty ? Metrics.REFRESH_CHANGES_NEEDED : Metrics.FORCE_REFRESH_CHANGES_NEEDED; default: throw new IllegalStateException("Unsupported result: " + refreshResult); } } } } java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java +16 −7 Original line number Diff line number Diff line Loading @@ -79,23 +79,30 @@ public class RefreshAnnotatedCallLogWorker { this.lightweightExecutorService = lightweightExecutorService; } /** Result of refreshing the annotated call log. */ public enum RefreshResult { NOT_DIRTY, REBUILT_BUT_NO_CHANGES_NEEDED, REBUILT_AND_CHANGES_NEEDED } /** Checks if the annotated call log is dirty and refreshes it if necessary. */ ListenableFuture<Void> refreshWithDirtyCheck() { ListenableFuture<RefreshResult> refreshWithDirtyCheck() { return refresh(true); } /** Refreshes the annotated call log, bypassing dirty checks. */ ListenableFuture<Void> refreshWithoutDirtyCheck() { ListenableFuture<RefreshResult> refreshWithoutDirtyCheck() { return refresh(false); } private ListenableFuture<Void> refresh(boolean checkDirty) { private ListenableFuture<RefreshResult> refresh(boolean checkDirty) { LogUtil.i("RefreshAnnotatedCallLogWorker.refresh", "submitting serialized refresh request"); return dialerFutureSerializer.submitAsync( () -> checkDirtyAndRebuildIfNecessary(checkDirty), lightweightExecutorService); } private ListenableFuture<Void> checkDirtyAndRebuildIfNecessary(boolean checkDirty) { private ListenableFuture<RefreshResult> checkDirtyAndRebuildIfNecessary(boolean checkDirty) { ListenableFuture<Boolean> forceRebuildFuture = backgroundExecutorService.submit( () -> { Loading Loading @@ -139,7 +146,7 @@ public class RefreshAnnotatedCallLogWorker { return Futures.transformAsync( callLogState.isBuilt(), this::rebuild, MoreExecutors.directExecutor()); } return Futures.immediateFuture(null); return Futures.immediateFuture(RefreshResult.NOT_DIRTY); }, lightweightExecutorService); } Loading @@ -160,7 +167,7 @@ public class RefreshAnnotatedCallLogWorker { return isDirtyFuture; } private ListenableFuture<Void> rebuild(boolean isBuilt) { private ListenableFuture<RefreshResult> rebuild(boolean isBuilt) { CallLogMutations mutations = new CallLogMutations(); // Start by filling the data sources--the system call log data source must go first! Loading Loading @@ -225,7 +232,9 @@ public class RefreshAnnotatedCallLogWorker { unused -> { sharedPreferences.edit().putBoolean(SharedPrefKeys.FORCE_REBUILD, false).apply(); callLogState.markBuilt(); return null; return mutations.isEmpty() ? RefreshResult.REBUILT_BUT_NO_CHANGES_NEEDED : RefreshResult.REBUILT_AND_CHANGES_NEEDED; }, backgroundExecutorService); } Loading java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java +4 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,8 @@ import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.Ann import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.metrics.Metrics; import com.android.dialer.metrics.MetricsComponent; import java.util.ArrayList; import java.util.Arrays; Loading Loading @@ -153,6 +155,7 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { Assert.checkArgument( selectionArgs == null, "selection args not supported for coalesced call log"); Assert.checkArgument(sortOrder == null, "sort order not supported for coalesced call log"); MetricsComponent.get(getContext()).metrics().startTimer(Metrics.NEW_CALL_LOG_COALESCE); try (Cursor allAnnotatedCallLogRows = queryBuilder.query( db, Loading @@ -168,6 +171,7 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { .coalesce(allAnnotatedCallLogRows); coalescedRows.setNotificationUri( getContext().getContentResolver(), CoalescedAnnotatedCallLog.CONTENT_URI); MetricsComponent.get(getContext()).metrics().stopTimer(Metrics.NEW_CALL_LOG_COALESCE); return coalescedRows; } default: Loading java/com/android/dialer/metrics/FutureTimer.java +25 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.support.annotation.IntDef; import android.support.annotation.VisibleForTesting; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.LightweightExecutor; 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; Loading Loading @@ -72,7 +73,7 @@ public final class FutureTimer { * of tracking heavyweight operations (which is what this method is intended for). */ public <T> void applyTiming(ListenableFuture<T> future, String eventName) { applyTiming(future, eventName, LogCatMode.DONT_LOG_VALUES); applyTiming(future, unused -> eventName, LogCatMode.DONT_LOG_VALUES); } /** Loading @@ -81,14 +82,35 @@ public final class FutureTimer { */ public <T> void applyTiming( ListenableFuture<T> future, String eventName, @LogCatMode int logCatMode) { applyTiming(future, unused -> eventName, logCatMode); } /** * Overload of {@link #applyTiming(ListenableFuture, String)} that accepts a function which * specifies how to compute an event name from the result of the future. * * <p>This is useful when the event name depends on the result of the future. */ public <T> void applyTiming( ListenableFuture<T> future, Function<T, String> eventNameFromResultFunction) { applyTiming(future, eventNameFromResultFunction, LogCatMode.DONT_LOG_VALUES); } private <T> void applyTiming( ListenableFuture<T> future, Function<T, String> eventNameFromResultFunction, @LogCatMode int logCatMode) { long startTime = SystemClock.elapsedRealtime(); metrics.startTimer(eventName); Integer timerId = metrics.startUnnamedTimer(); Futures.addCallback( future, new FutureCallback<T>() { @Override public void onSuccess(T result) { metrics.stopTimer(eventName); String eventName = eventNameFromResultFunction.apply(result); if (timerId != null) { metrics.stopUnnamedTimer(timerId, eventName); } long operationTime = SystemClock.elapsedRealtime() - startTime; // If the operation took a long time, do some WARNING logging. Loading java/com/android/dialer/metrics/Metrics.java +26 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.dialer.metrics; import android.app.Application; import android.support.annotation.Nullable; /** Logs metrics. */ public interface Metrics { Loading @@ -36,6 +37,14 @@ public interface Metrics { String NEW_CALL_LOG_JANK_EVENT_NAME = "NewCallLog.Jank"; // Events related to refreshing the annotated call log. String NEW_CALL_LOG_COALESCE = "NewCallLog.Coalesce"; String REFRESH_NOT_DIRTY = "RefreshAnnotatedCallLogReceiver.NotDirty"; String REFRESH_CHANGES_NEEDED = "RefreshAnnotatedCallLogReceiver.ChangesNeeded"; String REFRESH_NO_CHANGES_NEEDED = "RefreshAnnotatedCallLogReceiver.NoChangesNeeded"; String FORCE_REFRESH_CHANGES_NEEDED = "RefreshAnnotatedCallLogReceiver.ForceRefreshChangesNeeded"; String FORCE_REFRESH_NO_CHANGES_NEEDED = "RefreshAnnotatedCallLogReceiver.ForceRefreshNoChangesNeeded"; String INITIAL_FILL_EVENT_NAME = "RefreshAnnotatedCallLog.Initial.Fill"; String INITIAL_ON_SUCCESSFUL_FILL_EVENT_NAME = "RefreshAnnotatedCallLog.Initial.OnSuccessfulFill"; String INITIAL_APPLY_MUTATIONS_EVENT_NAME = "RefreshAnnotatedCallLog.Initial.ApplyMutations"; Loading @@ -61,6 +70,23 @@ public interface Metrics { /** Start a timer. */ void startTimer(String timerEventName); /** * Starts a timer for which the name is not yet known. * * @return opaque identifier for the event which should be provided back to {@link * #stopUnnamedTimer(int, String)} to stop the timer. Null if the timer cannot be started, for * example because the user is locked. */ @Nullable Integer startUnnamedTimer(); /** * Stop a timer which was started with {@link #startUnnamedTimer()}. * * @param timerId the value returned in the corresponding call to {@link #startUnnamedTimer()} */ void stopUnnamedTimer(int timerId, String timerEventName); /** Stop a timer. */ void stopTimer(String timerEventName); Loading Loading
java/com/android/dialer/calllog/RefreshAnnotatedCallLogReceiver.java +35 −1 Original line number Diff line number Diff line Loading @@ -21,10 +21,15 @@ 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.DefaultFutureCallback; import com.android.dialer.common.concurrent.ThreadUtil; 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.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; Loading @@ -43,6 +48,7 @@ public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver { private static final long REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS = 100L; private final RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; private final FutureTimer futureTimer; @Nullable private Runnable refreshAnnotatedCallLogRunnable; Loading @@ -57,6 +63,7 @@ public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver { public RefreshAnnotatedCallLogReceiver(Context context) { refreshAnnotatedCallLogWorker = CallLogComponent.get(context).getRefreshAnnotatedCallLogWorker(); futureTimer = MetricsComponent.get(context).futureTimer(); } @Override Loading Loading @@ -97,12 +104,14 @@ public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver { refreshAnnotatedCallLogRunnable = () -> { ListenableFuture<Void> future = ListenableFuture<RefreshResult> future = checkDirty ? refreshAnnotatedCallLogWorker.refreshWithDirtyCheck() : refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(); Futures.addCallback( future, new DefaultFutureCallback<>(), MoreExecutors.directExecutor()); futureTimer.applyTiming(future, new EventNameFromResultFunction(checkDirty)); // TODO(zachh): Should also log impression counts of RefreshResults. }; ThreadUtil.getUiThreadHandler() Loading @@ -118,4 +127,29 @@ public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver { 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.REFRESH_NOT_DIRTY; // NOT_DIRTY implies forceRefresh is false case REBUILT_BUT_NO_CHANGES_NEEDED: return checkDirty ? Metrics.REFRESH_NO_CHANGES_NEEDED : Metrics.FORCE_REFRESH_NO_CHANGES_NEEDED; case REBUILT_AND_CHANGES_NEEDED: return checkDirty ? Metrics.REFRESH_CHANGES_NEEDED : Metrics.FORCE_REFRESH_CHANGES_NEEDED; default: throw new IllegalStateException("Unsupported result: " + refreshResult); } } } }
java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java +16 −7 Original line number Diff line number Diff line Loading @@ -79,23 +79,30 @@ public class RefreshAnnotatedCallLogWorker { this.lightweightExecutorService = lightweightExecutorService; } /** Result of refreshing the annotated call log. */ public enum RefreshResult { NOT_DIRTY, REBUILT_BUT_NO_CHANGES_NEEDED, REBUILT_AND_CHANGES_NEEDED } /** Checks if the annotated call log is dirty and refreshes it if necessary. */ ListenableFuture<Void> refreshWithDirtyCheck() { ListenableFuture<RefreshResult> refreshWithDirtyCheck() { return refresh(true); } /** Refreshes the annotated call log, bypassing dirty checks. */ ListenableFuture<Void> refreshWithoutDirtyCheck() { ListenableFuture<RefreshResult> refreshWithoutDirtyCheck() { return refresh(false); } private ListenableFuture<Void> refresh(boolean checkDirty) { private ListenableFuture<RefreshResult> refresh(boolean checkDirty) { LogUtil.i("RefreshAnnotatedCallLogWorker.refresh", "submitting serialized refresh request"); return dialerFutureSerializer.submitAsync( () -> checkDirtyAndRebuildIfNecessary(checkDirty), lightweightExecutorService); } private ListenableFuture<Void> checkDirtyAndRebuildIfNecessary(boolean checkDirty) { private ListenableFuture<RefreshResult> checkDirtyAndRebuildIfNecessary(boolean checkDirty) { ListenableFuture<Boolean> forceRebuildFuture = backgroundExecutorService.submit( () -> { Loading Loading @@ -139,7 +146,7 @@ public class RefreshAnnotatedCallLogWorker { return Futures.transformAsync( callLogState.isBuilt(), this::rebuild, MoreExecutors.directExecutor()); } return Futures.immediateFuture(null); return Futures.immediateFuture(RefreshResult.NOT_DIRTY); }, lightweightExecutorService); } Loading @@ -160,7 +167,7 @@ public class RefreshAnnotatedCallLogWorker { return isDirtyFuture; } private ListenableFuture<Void> rebuild(boolean isBuilt) { private ListenableFuture<RefreshResult> rebuild(boolean isBuilt) { CallLogMutations mutations = new CallLogMutations(); // Start by filling the data sources--the system call log data source must go first! Loading Loading @@ -225,7 +232,9 @@ public class RefreshAnnotatedCallLogWorker { unused -> { sharedPreferences.edit().putBoolean(SharedPrefKeys.FORCE_REBUILD, false).apply(); callLogState.markBuilt(); return null; return mutations.isEmpty() ? RefreshResult.REBUILT_BUT_NO_CHANGES_NEEDED : RefreshResult.REBUILT_AND_CHANGES_NEEDED; }, backgroundExecutorService); } Loading
java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java +4 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,8 @@ import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.Ann import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.metrics.Metrics; import com.android.dialer.metrics.MetricsComponent; import java.util.ArrayList; import java.util.Arrays; Loading Loading @@ -153,6 +155,7 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { Assert.checkArgument( selectionArgs == null, "selection args not supported for coalesced call log"); Assert.checkArgument(sortOrder == null, "sort order not supported for coalesced call log"); MetricsComponent.get(getContext()).metrics().startTimer(Metrics.NEW_CALL_LOG_COALESCE); try (Cursor allAnnotatedCallLogRows = queryBuilder.query( db, Loading @@ -168,6 +171,7 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { .coalesce(allAnnotatedCallLogRows); coalescedRows.setNotificationUri( getContext().getContentResolver(), CoalescedAnnotatedCallLog.CONTENT_URI); MetricsComponent.get(getContext()).metrics().stopTimer(Metrics.NEW_CALL_LOG_COALESCE); return coalescedRows; } default: Loading
java/com/android/dialer/metrics/FutureTimer.java +25 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.support.annotation.IntDef; import android.support.annotation.VisibleForTesting; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.LightweightExecutor; 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; Loading Loading @@ -72,7 +73,7 @@ public final class FutureTimer { * of tracking heavyweight operations (which is what this method is intended for). */ public <T> void applyTiming(ListenableFuture<T> future, String eventName) { applyTiming(future, eventName, LogCatMode.DONT_LOG_VALUES); applyTiming(future, unused -> eventName, LogCatMode.DONT_LOG_VALUES); } /** Loading @@ -81,14 +82,35 @@ public final class FutureTimer { */ public <T> void applyTiming( ListenableFuture<T> future, String eventName, @LogCatMode int logCatMode) { applyTiming(future, unused -> eventName, logCatMode); } /** * Overload of {@link #applyTiming(ListenableFuture, String)} that accepts a function which * specifies how to compute an event name from the result of the future. * * <p>This is useful when the event name depends on the result of the future. */ public <T> void applyTiming( ListenableFuture<T> future, Function<T, String> eventNameFromResultFunction) { applyTiming(future, eventNameFromResultFunction, LogCatMode.DONT_LOG_VALUES); } private <T> void applyTiming( ListenableFuture<T> future, Function<T, String> eventNameFromResultFunction, @LogCatMode int logCatMode) { long startTime = SystemClock.elapsedRealtime(); metrics.startTimer(eventName); Integer timerId = metrics.startUnnamedTimer(); Futures.addCallback( future, new FutureCallback<T>() { @Override public void onSuccess(T result) { metrics.stopTimer(eventName); String eventName = eventNameFromResultFunction.apply(result); if (timerId != null) { metrics.stopUnnamedTimer(timerId, eventName); } long operationTime = SystemClock.elapsedRealtime() - startTime; // If the operation took a long time, do some WARNING logging. Loading
java/com/android/dialer/metrics/Metrics.java +26 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.dialer.metrics; import android.app.Application; import android.support.annotation.Nullable; /** Logs metrics. */ public interface Metrics { Loading @@ -36,6 +37,14 @@ public interface Metrics { String NEW_CALL_LOG_JANK_EVENT_NAME = "NewCallLog.Jank"; // Events related to refreshing the annotated call log. String NEW_CALL_LOG_COALESCE = "NewCallLog.Coalesce"; String REFRESH_NOT_DIRTY = "RefreshAnnotatedCallLogReceiver.NotDirty"; String REFRESH_CHANGES_NEEDED = "RefreshAnnotatedCallLogReceiver.ChangesNeeded"; String REFRESH_NO_CHANGES_NEEDED = "RefreshAnnotatedCallLogReceiver.NoChangesNeeded"; String FORCE_REFRESH_CHANGES_NEEDED = "RefreshAnnotatedCallLogReceiver.ForceRefreshChangesNeeded"; String FORCE_REFRESH_NO_CHANGES_NEEDED = "RefreshAnnotatedCallLogReceiver.ForceRefreshNoChangesNeeded"; String INITIAL_FILL_EVENT_NAME = "RefreshAnnotatedCallLog.Initial.Fill"; String INITIAL_ON_SUCCESSFUL_FILL_EVENT_NAME = "RefreshAnnotatedCallLog.Initial.OnSuccessfulFill"; String INITIAL_APPLY_MUTATIONS_EVENT_NAME = "RefreshAnnotatedCallLog.Initial.ApplyMutations"; Loading @@ -61,6 +70,23 @@ public interface Metrics { /** Start a timer. */ void startTimer(String timerEventName); /** * Starts a timer for which the name is not yet known. * * @return opaque identifier for the event which should be provided back to {@link * #stopUnnamedTimer(int, String)} to stop the timer. Null if the timer cannot be started, for * example because the user is locked. */ @Nullable Integer startUnnamedTimer(); /** * Stop a timer which was started with {@link #startUnnamedTimer()}. * * @param timerId the value returned in the corresponding call to {@link #startUnnamedTimer()} */ void stopUnnamedTimer(int timerId, String timerEventName); /** Stop a timer. */ void stopTimer(String timerEventName); Loading