1 /*
2  * Copyright (C) 2020 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.wifi;
18 
19 import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
20 import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SCAN_ONLY;
21 import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
22 
23 import android.annotation.NonNull;
24 import android.content.Context;
25 import android.net.NetworkInfo;
26 import android.util.ArrayMap;
27 
28 import androidx.annotation.Nullable;
29 
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Map;
34 
35 /**
36  * Used to buffer public broadcasts when multiple concurrent client interfaces are active to
37  * preserve legacy behavior expected by apps when there is a single client interface active.
38  */
39 public class ClientModeManagerBroadcastQueue {
40 
41     private static final String TAG = "WifiBroadcastQueue";
42 
43     private final ActiveModeWarden mActiveModeWarden;
44     private final Context mContext;
45     /** List of buffered broadcasts, per-ClientModeManager. */
46     private final Map<ClientModeManager, List<QueuedBroadcast>> mBufferedBroadcasts =
47             new ArrayMap<>();
48 
49     /** Lambda representing a broadcast to be sent. */
50     public interface QueuedBroadcast {
51         /** Send the broadcast using one of the many different Context#send* implementations. */
send()52         void send();
53     }
54 
55     private boolean mVerboseLoggingEnabled = false;
56 
ClientModeManagerBroadcastQueue(@onNull ActiveModeWarden activeModeWarden, @NonNull Context context)57     public ClientModeManagerBroadcastQueue(@NonNull ActiveModeWarden activeModeWarden,
58             @NonNull Context context) {
59         mActiveModeWarden = activeModeWarden;
60         mContext = context;
61 
62         mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback());
63         mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback(
64                 new PrimaryClientModeManagerChangedCallback());
65     }
66 
setVerboseLoggingEnabled(boolean verboseLoggingEnabled)67     public void setVerboseLoggingEnabled(boolean verboseLoggingEnabled) {
68         mVerboseLoggingEnabled = verboseLoggingEnabled;
69     }
70 
71     /**
72      * If the ClientModeManager is primary or scan only, the broadcast will be sent immediately.
73      * Otherwise, the broadcast will be queued, and sent out if and when the ClientModeManager
74      * becomes primary.
75      */
queueOrSendBroadcast( @onNull ClientModeManager manager, @NonNull QueuedBroadcast broadcast)76     public void queueOrSendBroadcast(
77             @NonNull ClientModeManager manager,
78             @NonNull QueuedBroadcast broadcast) {
79 
80         if (manager.getRole() == ROLE_CLIENT_PRIMARY
81                 || manager.getRole() == ROLE_CLIENT_SCAN_ONLY) {
82             // Primary or scan only, send existing queued broadcasts and send the new broadcast
83             // immediately. Assume that queue is empty for this manager (flushed when it originally
84             // became primary).
85             // TODO: b/192612399 - look into the race issue causing the ClientModeManager to be
86             // already in ROLE_CLIENT_SCAN_ONLY when ClientModeImpl sends the broadcast.
87             broadcast.send();
88         } else if (manager.getRole() == ROLE_CLIENT_SECONDARY_TRANSIENT) {
89             // buffer the broadcast until the ClientModeManager becomes primary.
90             mBufferedBroadcasts
91                     .computeIfAbsent(manager, k -> new ArrayList<>())
92                     .add(broadcast);
93         }
94         // for all other roles, they will never become primary, so discard their broadcasts
95     }
96 
sendAllBroadcasts(ClientModeManager manager)97     private void sendAllBroadcasts(ClientModeManager manager) {
98         List<QueuedBroadcast> queuedBroadcasts = mBufferedBroadcasts.getOrDefault(
99                 manager, Collections.emptyList());
100         for (QueuedBroadcast broadcast : queuedBroadcasts) {
101             broadcast.send();
102         }
103         // clear the sent broadcasts
104         clearQueue(manager);
105     }
106 
107     /**
108      * Clear the broadcast queue for the given manager when e.g. the Make-Before-Break attempt
109      * fails, or the ClientModeManager is deleted.
110      *
111      * TODO(b/174041877): Call this when connection fails during Make Before Break
112      */
clearQueue(@onNull ClientModeManager manager)113     public void clearQueue(@NonNull ClientModeManager manager) {
114         mBufferedBroadcasts.remove(manager);
115     }
116 
117     /**
118      * Send broadcasts to fake the disconnection of the previous network, since apps expect there
119      * to be only one connection at a time.
120      */
fakeDisconnectionBroadcasts()121     public void fakeDisconnectionBroadcasts() {
122         ClientModeImpl.sendNetworkChangeBroadcast(
123                 mContext, NetworkInfo.DetailedState.DISCONNECTED, mVerboseLoggingEnabled);
124     }
125 
126     private class PrimaryClientModeManagerChangedCallback
127             implements ActiveModeWarden.PrimaryClientModeManagerChangedCallback {
128 
129         @Override
onChange( @ullable ConcreteClientModeManager prevPrimaryClientModeManager, @Nullable ConcreteClientModeManager newPrimaryClientModeManager)130         public void onChange(
131                 @Nullable ConcreteClientModeManager prevPrimaryClientModeManager,
132                 @Nullable ConcreteClientModeManager newPrimaryClientModeManager) {
133             if (newPrimaryClientModeManager == null) {
134                 return;
135             }
136             // when the a ClientModeManager becomes primary, send all its queued broadcasts
137             sendAllBroadcasts(newPrimaryClientModeManager);
138         }
139     }
140 
141     private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
142 
143         @Override
onActiveModeManagerAdded(@onNull ActiveModeManager activeModeManager)144         public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
145             // no-op
146         }
147 
148         @Override
onActiveModeManagerRoleChanged(@onNull ActiveModeManager activeModeManager)149         public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
150             // no-op
151         }
152 
153         @Override
onActiveModeManagerRemoved(@onNull ActiveModeManager activeModeManager)154         public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
155             if (!(activeModeManager instanceof ClientModeManager)) {
156                 return;
157             }
158             ClientModeManager clientModeManager = (ClientModeManager) activeModeManager;
159             clearQueue(clientModeManager);
160         }
161     }
162 }
163