1 /* 2 * Copyright (C) 2025 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.bluetooth.le_scan; 18 19 import static com.android.bluetooth.le_scan.ScanController.DEFAULT_REPORT_DELAY_FLOOR; 20 21 import android.provider.DeviceConfig; 22 23 import com.android.bluetooth.Utils.TimeProvider; 24 import com.android.internal.annotations.VisibleForTesting; 25 26 import java.util.Set; 27 28 /** 29 * Throttler to reduce the number of times the Bluetooth process wakes up to check for pending batch 30 * scan results. The wake-up intervals are increased when no matching results are found and are 31 * longer when the screen is off. 32 */ 33 class BatchScanThrottler { 34 // Minimum batch trigger interval to check for batched results when the screen is off 35 @VisibleForTesting static final long SCREEN_OFF_MINIMUM_DELAY_FLOOR_MS = 20000L; 36 // Adjusted minimum report delay for unfiltered batch scan clients 37 @VisibleForTesting static final long UNFILTERED_DELAY_FLOOR_MS = 20000L; 38 // Adjusted minimum report delay for unfiltered batch scan clients when the screen is off 39 @VisibleForTesting static final long UNFILTERED_SCREEN_OFF_DELAY_FLOOR_MS = 60000L; 40 // Backoff stages used as multipliers for the minimum delay floor (standard or screen-off) 41 @VisibleForTesting static final int[] BACKOFF_MULTIPLIERS = {1, 1, 2, 2, 4}; 42 // Start screen-off trigger interval throttling after the screen has been off for this period 43 // of time. This allows the screen-on intervals to be used for a short period of time after the 44 // screen has gone off, and avoids too much flipping between screen-off and screen-on backoffs 45 // when the screen is off for a short period of time 46 @VisibleForTesting static final long SCREEN_OFF_DELAY_MS = 60000L; 47 private final TimeProvider mTimeProvider; 48 private final long mDelayFloor; 49 private final long mScreenOffDelayFloor; 50 private int mBackoffStage = 0; 51 private long mScreenOffTriggerTime = 0L; 52 private boolean mScreenOffThrottling = false; 53 BatchScanThrottler(TimeProvider timeProvider, boolean screenOn)54 BatchScanThrottler(TimeProvider timeProvider, boolean screenOn) { 55 mTimeProvider = timeProvider; 56 mDelayFloor = 57 DeviceConfig.getLong( 58 DeviceConfig.NAMESPACE_BLUETOOTH, 59 "report_delay", 60 DEFAULT_REPORT_DELAY_FLOOR); 61 mScreenOffDelayFloor = Math.max(mDelayFloor, SCREEN_OFF_MINIMUM_DELAY_FLOOR_MS); 62 onScreenOn(screenOn); 63 } 64 resetBackoff()65 void resetBackoff() { 66 mBackoffStage = 0; 67 } 68 onScreenOn(boolean screenOn)69 void onScreenOn(boolean screenOn) { 70 if (screenOn) { 71 mScreenOffTriggerTime = 0L; 72 mScreenOffThrottling = false; 73 resetBackoff(); 74 } else { 75 // Screen-off intervals to be used after the trigger time 76 mScreenOffTriggerTime = mTimeProvider.elapsedRealtime() + SCREEN_OFF_DELAY_MS; 77 } 78 } 79 getBatchTriggerIntervalMillis(Set<ScanClient> batchClients)80 long getBatchTriggerIntervalMillis(Set<ScanClient> batchClients) { 81 // Check if we're past the screen-off time and should be using screen-off backoff values 82 if (!mScreenOffThrottling 83 && mScreenOffTriggerTime != 0 84 && mTimeProvider.elapsedRealtime() >= mScreenOffTriggerTime) { 85 mScreenOffThrottling = true; 86 resetBackoff(); 87 } 88 long unfilteredFloor = 89 mScreenOffThrottling 90 ? UNFILTERED_SCREEN_OFF_DELAY_FLOOR_MS 91 : UNFILTERED_DELAY_FLOOR_MS; 92 long intervalMillis = Long.MAX_VALUE; 93 for (ScanClient client : batchClients) { 94 if (client.mSettings.getReportDelayMillis() > 0) { 95 long clientIntervalMillis = client.mSettings.getReportDelayMillis(); 96 if ((client.mFilters == null || client.mFilters.isEmpty()) 97 && clientIntervalMillis < unfilteredFloor) { 98 clientIntervalMillis = unfilteredFloor; 99 } 100 intervalMillis = Math.min(intervalMillis, clientIntervalMillis); 101 } 102 } 103 int backoffIndex = 104 mBackoffStage >= BACKOFF_MULTIPLIERS.length 105 ? BACKOFF_MULTIPLIERS.length - 1 106 : mBackoffStage++; 107 return Math.max( 108 intervalMillis, 109 (mScreenOffThrottling ? mScreenOffDelayFloor : mDelayFloor) 110 * BACKOFF_MULTIPLIERS[backoffIndex]); 111 } 112 } 113