• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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