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 android.car.vms;
18 
19 import android.annotation.NonNull;
20 import android.util.ArrayMap;
21 import android.util.ArraySet;
22 import android.util.SparseBooleanArray;
23 
24 import com.android.internal.annotations.GuardedBy;
25 
26 import java.util.Collections;
27 import java.util.Map;
28 import java.util.Objects;
29 import java.util.Set;
30 import java.util.function.Consumer;
31 import java.util.stream.Collectors;
32 import java.util.stream.Stream;
33 
34 /**
35  * Internal utility for computing subscription updates.
36  *
37  * @hide
38  */
39 public final class VmsSubscriptionHelper {
40     private final Consumer<Set<VmsAssociatedLayer>> mUpdateHandler;
41 
42     private final Object mLock = new Object();
43 
44     @GuardedBy("mLock")
45     private final Set<VmsLayer> mLayerSubscriptions = new ArraySet<>();
46 
47     @GuardedBy("mLock")
48     private final Map<VmsLayer, SparseBooleanArray> mPublisherSubscriptions = new ArrayMap<>();
49 
50     @GuardedBy("mLock")
51     private boolean mPendingUpdate;
52 
53     /**
54      * Constructor for subscription helper.
55      *
56      * @param updateHandler Consumer of subscription updates.
57      */
VmsSubscriptionHelper(@onNull Consumer<Set<VmsAssociatedLayer>> updateHandler)58     public VmsSubscriptionHelper(@NonNull Consumer<Set<VmsAssociatedLayer>> updateHandler) {
59         mUpdateHandler = Objects.requireNonNull(updateHandler, "updateHandler cannot be null");
60     }
61 
62     /**
63      * Adds a subscription to a layer.
64      */
subscribe(@onNull VmsLayer layer)65     public void subscribe(@NonNull VmsLayer layer) {
66         Objects.requireNonNull(layer, "layer cannot be null");
67         synchronized (mLock) {
68             if (mLayerSubscriptions.add(layer)) {
69                 mPendingUpdate = true;
70             }
71             publishSubscriptionUpdate();
72         }
73     }
74 
75     /**
76      * Adds a subscription to a specific provider of a layer.
77      */
subscribe(@onNull VmsLayer layer, int providerId)78     public void subscribe(@NonNull VmsLayer layer, int providerId) {
79         Objects.requireNonNull(layer, "layer cannot be null");
80         synchronized (mLock) {
81             SparseBooleanArray providerIds = mPublisherSubscriptions.computeIfAbsent(layer,
82                     ignored -> new SparseBooleanArray());
83             if (!providerIds.get(providerId)) {
84                 providerIds.put(providerId, true);
85                 mPendingUpdate = true;
86             }
87             publishSubscriptionUpdate();
88         }
89     }
90 
91     /**
92      * Removes a subscription to a layer.
93      */
unsubscribe(@onNull VmsLayer layer)94     public void unsubscribe(@NonNull VmsLayer layer) {
95         Objects.requireNonNull(layer, "layer cannot be null");
96         synchronized (mLock) {
97             if (mLayerSubscriptions.remove(layer)) {
98                 mPendingUpdate = true;
99             }
100             publishSubscriptionUpdate();
101         }
102     }
103 
104     /**
105      * Removes a subscription to the specific provider of a layer.
106      */
unsubscribe(@onNull VmsLayer layer, int providerId)107     public void unsubscribe(@NonNull VmsLayer layer, int providerId) {
108         Objects.requireNonNull(layer, "layer cannot be null");
109         synchronized (mLock) {
110             SparseBooleanArray providerIds = mPublisherSubscriptions.get(layer);
111             if (providerIds != null && providerIds.get(providerId)) {
112                 providerIds.delete(providerId);
113                 if (providerIds.size() == 0) {
114                     mPublisherSubscriptions.remove(layer);
115                 }
116                 mPendingUpdate = true;
117             }
118             publishSubscriptionUpdate();
119         }
120     }
121 
122     /**
123      * Gets the current set of subscriptions.
124      */
125     @NonNull
getSubscriptions()126     public Set<VmsAssociatedLayer> getSubscriptions() {
127         return Stream.concat(
128                 mLayerSubscriptions.stream().map(
129                         layer -> new VmsAssociatedLayer(layer, Collections.emptySet())),
130                 mPublisherSubscriptions.entrySet().stream()
131                         .filter(entry -> !mLayerSubscriptions.contains(entry.getKey()))
132                         .map(VmsSubscriptionHelper::toAssociatedLayer))
133                 .collect(Collectors.toSet());
134     }
135 
publishSubscriptionUpdate()136     private void publishSubscriptionUpdate() {
137         synchronized (mLock) {
138             if (mPendingUpdate) {
139                 mUpdateHandler.accept(getSubscriptions());
140             }
141             mPendingUpdate = false;
142         }
143     }
144 
toAssociatedLayer( Map.Entry<VmsLayer, SparseBooleanArray> entry)145     private static VmsAssociatedLayer toAssociatedLayer(
146             Map.Entry<VmsLayer, SparseBooleanArray> entry) {
147         SparseBooleanArray providerIdArray = entry.getValue();
148         Set<Integer> providerIds = new ArraySet<>(providerIdArray.size());
149         for (int i = 0; i < providerIdArray.size(); i++) {
150             providerIds.add(providerIdArray.keyAt(i));
151         }
152         return new VmsAssociatedLayer(entry.getKey(), providerIds);
153     }
154 }
155