1 /* 2 * Copyright (C) 2017 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.internal.telephony; 18 19 import android.annotation.NonNull; 20 import android.os.Handler; 21 import android.os.Looper; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 25 /** The implementation of exponential backoff with jitter applied. */ 26 public class ExponentialBackoff { 27 private int mRetryCounter; 28 private long mStartDelayMs; 29 private long mMaximumDelayMs; 30 private long mCurrentDelayMs; 31 private int mMultiplier; 32 private final Runnable mRunnable; 33 private final Handler mHandler; 34 35 /** 36 * Implementation of Handler methods, Adapter for testing (can't spy on final methods). 37 */ 38 private HandlerAdapter mHandlerAdapter = new HandlerAdapter() { 39 @Override 40 public boolean postDelayed(Runnable runnable, long delayMillis) { 41 return mHandler.postDelayed(runnable, delayMillis); 42 } 43 44 @Override 45 public void removeCallbacks(Runnable runnable) { 46 mHandler.removeCallbacks(runnable); 47 } 48 }; 49 50 /** 51 * Need to spy final methods for testing. 52 */ 53 public interface HandlerAdapter { postDelayed(Runnable runnable, long delayMillis)54 boolean postDelayed(Runnable runnable, long delayMillis); removeCallbacks(Runnable runnable)55 void removeCallbacks(Runnable runnable); 56 } 57 ExponentialBackoff( long initialDelayMs, long maximumDelayMs, int multiplier, @NonNull Looper looper, @NonNull Runnable runnable)58 public ExponentialBackoff( 59 long initialDelayMs, 60 long maximumDelayMs, 61 int multiplier, 62 @NonNull Looper looper, 63 @NonNull Runnable runnable) { 64 this(initialDelayMs, maximumDelayMs, multiplier, new Handler(looper), runnable); 65 } 66 ExponentialBackoff( long initialDelayMs, long maximumDelayMs, int multiplier, @NonNull Handler handler, @NonNull Runnable runnable)67 public ExponentialBackoff( 68 long initialDelayMs, 69 long maximumDelayMs, 70 int multiplier, 71 @NonNull Handler handler, 72 @NonNull Runnable runnable) { 73 mRetryCounter = 0; 74 mStartDelayMs = initialDelayMs; 75 mMaximumDelayMs = maximumDelayMs; 76 mMultiplier = multiplier; 77 mHandler = handler; 78 mRunnable = runnable; 79 } 80 81 /** Starts the backoff, the runnable will be executed after {@link #mStartDelayMs}. */ start()82 public void start() { 83 mRetryCounter = 0; 84 mCurrentDelayMs = mStartDelayMs; 85 mHandlerAdapter.removeCallbacks(mRunnable); 86 mHandlerAdapter.postDelayed(mRunnable, mCurrentDelayMs); 87 } 88 89 /** Stops the backoff, all pending messages will be removed from the message queue. */ stop()90 public void stop() { 91 mRetryCounter = 0; 92 mHandlerAdapter.removeCallbacks(mRunnable); 93 } 94 95 /** Should call when the retry action has failed and we want to retry after a longer delay. */ notifyFailed()96 public void notifyFailed() { 97 mRetryCounter++; 98 long temp = Math.min( 99 mMaximumDelayMs, (long) (mStartDelayMs * Math.pow(mMultiplier, mRetryCounter))); 100 mCurrentDelayMs = (long) (((1 + Math.random()) / 2) * temp); 101 mHandlerAdapter.removeCallbacks(mRunnable); 102 mHandlerAdapter.postDelayed(mRunnable, mCurrentDelayMs); 103 } 104 105 /** Returns the delay for the most recently posted message. */ getCurrentDelay()106 public long getCurrentDelay() { 107 return mCurrentDelayMs; 108 } 109 110 @VisibleForTesting setHandlerAdapter(HandlerAdapter a)111 public void setHandlerAdapter(HandlerAdapter a) { 112 mHandlerAdapter = a; 113 } 114 } 115