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