Home | History | Annotate | Download | only in calllog
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.dialer.calllog;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.support.annotation.Nullable;
     24 import com.android.dialer.calllog.RefreshAnnotatedCallLogWorker.RefreshResult;
     25 import com.android.dialer.calllog.constants.IntentNames;
     26 import com.android.dialer.common.LogUtil;
     27 import com.android.dialer.common.concurrent.ThreadUtil;
     28 import com.android.dialer.logging.DialerImpression;
     29 import com.android.dialer.logging.Logger;
     30 import com.android.dialer.logging.LoggingBindings;
     31 import com.android.dialer.metrics.FutureTimer;
     32 import com.android.dialer.metrics.Metrics;
     33 import com.android.dialer.metrics.MetricsComponent;
     34 import com.google.common.base.Function;
     35 import com.google.common.util.concurrent.FutureCallback;
     36 import com.google.common.util.concurrent.Futures;
     37 import com.google.common.util.concurrent.ListenableFuture;
     38 import com.google.common.util.concurrent.MoreExecutors;
     39 
     40 /**
     41  * A {@link BroadcastReceiver} that starts/cancels refreshing the annotated call log when notified.
     42  */
     43 public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver {
     44 
     45   /**
     46    * This is a reasonable time that it might take between related call log writes, that also
     47    * shouldn't slow down single-writes too much. For example, when populating the database using the
     48    * simulator, using this value results in ~6 refresh cycles (on a release build) to write 120 call
     49    * log entries.
     50    */
     51   private static final long REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS = 100L;
     52 
     53   private final RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker;
     54   private final FutureTimer futureTimer;
     55   private final LoggingBindings logger;
     56 
     57   @Nullable private Runnable refreshAnnotatedCallLogRunnable;
     58 
     59   /** Returns an {@link IntentFilter} containing all actions accepted by this broadcast receiver. */
     60   public static IntentFilter getIntentFilter() {
     61     IntentFilter intentFilter = new IntentFilter();
     62     intentFilter.addAction(IntentNames.ACTION_REFRESH_ANNOTATED_CALL_LOG);
     63     intentFilter.addAction(IntentNames.ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG);
     64     return intentFilter;
     65   }
     66 
     67   public RefreshAnnotatedCallLogReceiver(Context context) {
     68     refreshAnnotatedCallLogWorker =
     69         CallLogComponent.get(context).getRefreshAnnotatedCallLogWorker();
     70     futureTimer = MetricsComponent.get(context).futureTimer();
     71     logger = Logger.get(context);
     72   }
     73 
     74   @Override
     75   public void onReceive(Context context, Intent intent) {
     76     LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.onReceive");
     77 
     78     String action = intent.getAction();
     79 
     80     if (IntentNames.ACTION_REFRESH_ANNOTATED_CALL_LOG.equals(action)) {
     81       boolean checkDirty = intent.getBooleanExtra(IntentNames.EXTRA_CHECK_DIRTY, false);
     82       refreshAnnotatedCallLog(checkDirty);
     83     } else if (IntentNames.ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG.equals(action)) {
     84       cancelRefreshingAnnotatedCallLog();
     85     }
     86   }
     87 
     88   /**
     89    * Request a refresh of the annotated call log.
     90    *
     91    * <p>Note that the execution will be delayed by {@link #REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS}.
     92    * Once the work begins, it can't be cancelled.
     93    *
     94    * @see #cancelRefreshingAnnotatedCallLog()
     95    */
     96   private void refreshAnnotatedCallLog(boolean checkDirty) {
     97     LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.refreshAnnotatedCallLog");
     98 
     99     // If we already scheduled a refresh, cancel it and schedule a new one so that repeated requests
    100     // in quick succession don't result in too much work. For example, if we get 10 requests in
    101     // 10ms, and a complete refresh takes a constant 200ms, the refresh will take 300ms (100ms wait
    102     // and 1 iteration @200ms) instead of 2 seconds (10 iterations @ 200ms) since the work requests
    103     // are serialized in RefreshAnnotatedCallLogWorker.
    104     //
    105     // We might get many requests in quick succession, for example, when the simulator inserts
    106     // hundreds of rows into the system call log, or when the data for a new call is incrementally
    107     // written to different columns as it becomes available.
    108     ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable);
    109 
    110     refreshAnnotatedCallLogRunnable =
    111         () -> {
    112           ListenableFuture<RefreshResult> future =
    113               checkDirty
    114                   ? refreshAnnotatedCallLogWorker.refreshWithDirtyCheck()
    115                   : refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck();
    116           Futures.addCallback(
    117               future,
    118               new FutureCallback<RefreshResult>() {
    119                 @Override
    120                 public void onSuccess(RefreshResult refreshResult) {
    121                   logger.logImpression(getImpressionType(checkDirty, refreshResult));
    122                 }
    123 
    124                 @Override
    125                 public void onFailure(Throwable throwable) {
    126                   ThreadUtil.getUiThreadHandler()
    127                       .post(
    128                           () -> {
    129                             throw new RuntimeException(throwable);
    130                           });
    131                 }
    132               },
    133               MoreExecutors.directExecutor());
    134           futureTimer.applyTiming(future, new EventNameFromResultFunction(checkDirty));
    135         };
    136 
    137     ThreadUtil.getUiThreadHandler()
    138         .postDelayed(refreshAnnotatedCallLogRunnable, REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS);
    139   }
    140 
    141   /**
    142    * When a refresh is requested, its execution is delayed (see {@link
    143    * #refreshAnnotatedCallLog(boolean)}). This method only cancels the refresh if it hasn't started.
    144    */
    145   private void cancelRefreshingAnnotatedCallLog() {
    146     LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.cancelRefreshingAnnotatedCallLog");
    147 
    148     ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable);
    149   }
    150 
    151   private static class EventNameFromResultFunction implements Function<RefreshResult, String> {
    152 
    153     private final boolean checkDirty;
    154 
    155     private EventNameFromResultFunction(boolean checkDirty) {
    156       this.checkDirty = checkDirty;
    157     }
    158 
    159     @Override
    160     public String apply(RefreshResult refreshResult) {
    161       switch (refreshResult) {
    162         case NOT_DIRTY:
    163           return Metrics.ANNOTATED_CALL_LOG_NOT_DIRTY; // NOT_DIRTY implies forceRefresh is false
    164         case REBUILT_BUT_NO_CHANGES_NEEDED:
    165           return checkDirty
    166               ? Metrics.ANNOTATED_LOG_NO_CHANGES_NEEDED
    167               : Metrics.NEW_CALL_LOG_FORCE_REFRESH_NO_CHANGES_NEEDED;
    168         case REBUILT_AND_CHANGES_NEEDED:
    169           return checkDirty
    170               ? Metrics.ANNOTATED_CALL_LOG_CHANGES_NEEDED
    171               : Metrics.ANNOTATED_CALL_LOG_FORCE_REFRESH_CHANGES_NEEDED;
    172         default:
    173           throw new IllegalStateException("Unsupported result: " + refreshResult);
    174       }
    175     }
    176   }
    177 
    178   private static DialerImpression.Type getImpressionType(
    179       boolean checkDirty, RefreshResult refreshResult) {
    180     switch (refreshResult) {
    181       case NOT_DIRTY:
    182         return DialerImpression.Type.ANNOTATED_CALL_LOG_NOT_DIRTY;
    183       case REBUILT_BUT_NO_CHANGES_NEEDED:
    184         return checkDirty
    185             ? DialerImpression.Type.ANNOTATED_CALL_LOG_NO_CHANGES_NEEDED
    186             : DialerImpression.Type.ANNOTATED_CALL_LOG_FORCE_REFRESH_NO_CHANGES_NEEDED;
    187       case REBUILT_AND_CHANGES_NEEDED:
    188         return checkDirty
    189             ? DialerImpression.Type.ANNOTATED_CALL_LOG_CHANGES_NEEDED
    190             : DialerImpression.Type.ANNOTATED_CALL_LOG_FORCE_REFRESH_CHANGES_NEEDED;
    191       default:
    192         throw new IllegalStateException("Unsupported result: " + refreshResult);
    193     }
    194   }
    195 }
    196