/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi; import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY; import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SCAN_ONLY; import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT; import android.annotation.NonNull; import android.content.Context; import android.net.NetworkInfo; import android.util.ArrayMap; import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * Used to buffer public broadcasts when multiple concurrent client interfaces are active to * preserve legacy behavior expected by apps when there is a single client interface active. */ public class ClientModeManagerBroadcastQueue { private static final String TAG = "WifiBroadcastQueue"; private final ActiveModeWarden mActiveModeWarden; private final Context mContext; /** List of buffered broadcasts, per-ClientModeManager. */ private final Map> mBufferedBroadcasts = new ArrayMap<>(); /** Lambda representing a broadcast to be sent. */ public interface QueuedBroadcast { /** Send the broadcast using one of the many different Context#send* implementations. */ void send(); } private boolean mVerboseLoggingEnabled = false; public ClientModeManagerBroadcastQueue(@NonNull ActiveModeWarden activeModeWarden, @NonNull Context context) { mActiveModeWarden = activeModeWarden; mContext = context; mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback()); mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback( new PrimaryClientModeManagerChangedCallback()); } public void setVerboseLoggingEnabled(boolean verboseLoggingEnabled) { mVerboseLoggingEnabled = verboseLoggingEnabled; } /** * If the ClientModeManager is primary or scan only, the broadcast will be sent immediately. * Otherwise, the broadcast will be queued, and sent out if and when the ClientModeManager * becomes primary. */ public void queueOrSendBroadcast( @NonNull ClientModeManager manager, @NonNull QueuedBroadcast broadcast) { if (manager.getRole() == ROLE_CLIENT_PRIMARY || manager.getRole() == ROLE_CLIENT_SCAN_ONLY) { // Primary or scan only, send existing queued broadcasts and send the new broadcast // immediately. Assume that queue is empty for this manager (flushed when it originally // became primary). // TODO: b/192612399 - look into the race issue causing the ClientModeManager to be // already in ROLE_CLIENT_SCAN_ONLY when ClientModeImpl sends the broadcast. broadcast.send(); } else if (manager.getRole() == ROLE_CLIENT_SECONDARY_TRANSIENT) { // buffer the broadcast until the ClientModeManager becomes primary. mBufferedBroadcasts .computeIfAbsent(manager, k -> new ArrayList<>()) .add(broadcast); } // for all other roles, they will never become primary, so discard their broadcasts } private void sendAllBroadcasts(ClientModeManager manager) { List queuedBroadcasts = mBufferedBroadcasts.getOrDefault( manager, Collections.emptyList()); for (QueuedBroadcast broadcast : queuedBroadcasts) { broadcast.send(); } // clear the sent broadcasts clearQueue(manager); } /** * Clear the broadcast queue for the given manager when e.g. the Make-Before-Break attempt * fails, or the ClientModeManager is deleted. * * TODO(b/174041877): Call this when connection fails during Make Before Break */ public void clearQueue(@NonNull ClientModeManager manager) { mBufferedBroadcasts.remove(manager); } /** * Send broadcasts to fake the disconnection of the previous network, since apps expect there * to be only one connection at a time. */ public void fakeDisconnectionBroadcasts() { ClientModeImpl.sendNetworkChangeBroadcast( mContext, NetworkInfo.DetailedState.DISCONNECTED, mVerboseLoggingEnabled); } private class PrimaryClientModeManagerChangedCallback implements ActiveModeWarden.PrimaryClientModeManagerChangedCallback { @Override public void onChange( @Nullable ConcreteClientModeManager prevPrimaryClientModeManager, @Nullable ConcreteClientModeManager newPrimaryClientModeManager) { if (newPrimaryClientModeManager == null) { return; } // when the a ClientModeManager becomes primary, send all its queued broadcasts sendAllBroadcasts(newPrimaryClientModeManager); } } private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback { @Override public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) { // no-op } @Override public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) { // no-op } @Override public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) { if (!(activeModeManager instanceof ClientModeManager)) { return; } ClientModeManager clientModeManager = (ClientModeManager) activeModeManager; clearQueue(clientModeManager); } } }