1 /* 2 * Copyright (C) 2022 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.server.nearby.fastpair.halfsheet; 18 19 20 import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.ACTIVE; 21 import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DISMISSED; 22 import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN; 23 import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG; 24 25 import android.util.Log; 26 import android.util.LruCache; 27 28 import androidx.annotation.VisibleForTesting; 29 30 import com.android.server.nearby.fastpair.blocklist.Blocklist; 31 import com.android.server.nearby.fastpair.blocklist.BlocklistElement; 32 import com.android.server.nearby.util.Clock; 33 import com.android.server.nearby.util.DefaultClock; 34 35 36 /** 37 * Maintains a list of half sheet id to tell whether the half sheet should be suppressed or not. 38 * 39 * <p>When user cancel half sheet, the ble address related half sheet should be in block list and 40 * after certain duration of time half sheet can show again. 41 */ 42 public class FastPairHalfSheetBlocklist extends LruCache<Integer, BlocklistElement> 43 implements Blocklist { 44 private static final String TAG = "HalfSheetBlocklist"; 45 // Number of entries in the FastPair blocklist 46 private static final int FAST_PAIR_BLOCKLIST_CACHE_SIZE = 16; 47 // Duration between first half sheet dismiss and second half sheet shows: 2 seconds 48 private static final int FAST_PAIR_HALF_SHEET_DISMISS_COOL_DOWN_MILLIS = 2000; 49 // The timeout to ban half sheet after user trigger the ban logic even number of time : 1 day 50 private static final int DURATION_RESURFACE_HALFSHEET_EVEN_NUMBER_BAN_MILLI_SECONDS = 86400000; 51 // Timeout for DISMISSED entries in the blocklist to expire : 1 min 52 private static final int FAST_PAIR_BLOCKLIST_DISMISSED_HALF_SHEET_TIMEOUT_MILLIS = 60000; 53 // The timeout for entries in the blocklist to expire : 1 day 54 private static final int STATE_EXPIRATION_MILLI_SECONDS = 86400000; 55 private long mEndTimeBanAllItems; 56 private final Clock mClock; 57 58 FastPairHalfSheetBlocklist()59 public FastPairHalfSheetBlocklist() { 60 // Reuses the size limit from notification cache. 61 // Number of entries in the FastPair blocklist 62 super(FAST_PAIR_BLOCKLIST_CACHE_SIZE); 63 mClock = new DefaultClock(); 64 } 65 66 @VisibleForTesting FastPairHalfSheetBlocklist(int size, Clock clock)67 FastPairHalfSheetBlocklist(int size, Clock clock) { 68 super(size); 69 mClock = clock; 70 } 71 72 /** 73 * Checks whether need to show HalfSheet or not. 74 * 75 * <p> When the HalfSheet {@link BlocklistState} is DISMISS, there is a little cool down period 76 * to allow half sheet to reshow. 77 * If the HalfSheet {@link BlocklistState} is DO_NOT_SHOW_AGAIN, within durationMilliSeconds 78 * from banned start time, the function will return true 79 * otherwise it will return false if the status is expired 80 * If the HalfSheet {@link BlocklistState} is DO_NOT_SHOW_AGAIN_LONG, the half sheet will be 81 * baned for a longer duration. 82 * 83 * @param id {@link com.android.nearby.halfsheet.HalfSheetActivity} id 84 * @param durationMilliSeconds the time duration from item is banned to now 85 * @return whether the HalfSheet is blocked to show 86 */ 87 @Override isBlocklisted(int id, int durationMilliSeconds)88 public boolean isBlocklisted(int id, int durationMilliSeconds) { 89 if (shouldBanAllItem()) { 90 return true; 91 } 92 BlocklistElement entry = get(id); 93 if (entry == null) { 94 return false; 95 } 96 if (entry.getState().equals(DO_NOT_SHOW_AGAIN)) { 97 Log.d(TAG, "BlocklistState: DO_NOT_SHOW_AGAIN"); 98 return mClock.elapsedRealtime() < entry.getTimeStamp() + durationMilliSeconds; 99 } 100 if (entry.getState().equals(DO_NOT_SHOW_AGAIN_LONG)) { 101 Log.d(TAG, "BlocklistState: DO_NOT_SHOW_AGAIN_LONG "); 102 return mClock.elapsedRealtime() 103 < entry.getTimeStamp() 104 + DURATION_RESURFACE_HALFSHEET_EVEN_NUMBER_BAN_MILLI_SECONDS; 105 } 106 107 if (entry.getState().equals(ACTIVE)) { 108 Log.d(TAG, "BlocklistState: ACTIVE"); 109 return false; 110 } 111 // Get some cool down period for dismiss state 112 if (entry.getState().equals(DISMISSED)) { 113 Log.d(TAG, "BlocklistState: DISMISSED"); 114 return mClock.elapsedRealtime() 115 < entry.getTimeStamp() + FAST_PAIR_HALF_SHEET_DISMISS_COOL_DOWN_MILLIS; 116 } 117 if (dismissStateHasExpired(entry)) { 118 Log.d(TAG, "stateHasExpired: True"); 119 return false; 120 } 121 return true; 122 } 123 124 @Override removeBlocklist(int id)125 public boolean removeBlocklist(int id) { 126 BlocklistElement oldValue = remove(id); 127 return oldValue != null; 128 } 129 130 /** 131 * Updates the HalfSheet blocklist state 132 * 133 * <p>When the new {@link BlocklistState} has higher priority then old {@link BlocklistState} or 134 * the old {@link BlocklistState} status is expired,the function will update the status. 135 * 136 * @param id HalfSheet id 137 * @param state Blocklist state 138 * @return update status successful or not 139 */ 140 @Override updateState(int id, BlocklistState state)141 public boolean updateState(int id, BlocklistState state) { 142 BlocklistElement entry = get(id); 143 if (entry == null || state.hasHigherPriorityThan(entry.getState()) 144 || dismissStateHasExpired(entry)) { 145 Log.d(TAG, "updateState: " + state); 146 put(id, new BlocklistElement(state, mClock.elapsedRealtime())); 147 return true; 148 } 149 return false; 150 } 151 152 /** Enables lower state to override the higher value state. */ forceUpdateState(int id, BlocklistState state)153 public void forceUpdateState(int id, BlocklistState state) { 154 put(id, new BlocklistElement(state, mClock.elapsedRealtime())); 155 } 156 157 /** Resets certain device ban state to active. */ 158 @Override resetBlockState(int id)159 public void resetBlockState(int id) { 160 BlocklistElement entry = get(id); 161 if (entry != null) { 162 put(id, new BlocklistElement(ACTIVE, mClock.elapsedRealtime())); 163 } 164 } 165 166 /** Checks whether certain device state has expired. */ isStateExpired(int id)167 public boolean isStateExpired(int id) { 168 BlocklistElement entry = get(id); 169 if (entry != null) { 170 return mClock.elapsedRealtime() > entry.getTimeStamp() + STATE_EXPIRATION_MILLI_SECONDS; 171 } 172 return false; 173 } 174 dismissStateHasExpired(BlocklistElement entry)175 private boolean dismissStateHasExpired(BlocklistElement entry) { 176 return mClock.elapsedRealtime() 177 > entry.getTimeStamp() + FAST_PAIR_BLOCKLIST_DISMISSED_HALF_SHEET_TIMEOUT_MILLIS; 178 } 179 180 /** 181 * Updates the end time that all half sheet will be banned. 182 */ banAllItem(long banDurationTimeMillis)183 void banAllItem(long banDurationTimeMillis) { 184 long endTime = mClock.elapsedRealtime() + banDurationTimeMillis; 185 if (endTime > mEndTimeBanAllItems) { 186 mEndTimeBanAllItems = endTime; 187 } 188 } 189 shouldBanAllItem()190 private boolean shouldBanAllItem() { 191 return mClock.elapsedRealtime() < mEndTimeBanAllItems; 192 } 193 } 194