1 /*
2  * Copyright (C) 2023 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.app;
18 
19 import android.annotation.NonNull;
20 import android.os.IBinder;
21 
22 import com.android.internal.annotations.GuardedBy;
23 
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.concurrent.Executor;
27 import java.util.function.Consumer;
28 
29 /**
30  * Manages listeners for unfiltered configuration changes.
31  * @hide
32  */
33 class ConfigurationChangedListenerController {
34 
35     private final Object mLock = new Object();
36 
37     @GuardedBy("mLock")
38     private final List<ListenerContainer> mListenerContainers = new ArrayList<>();
39 
40     /**
41      * Adds a listener to receive updates when they are dispatched. This only dispatches updates and
42      * does not relay the last emitted value. If called with the same listener then this method does
43      * not have any effect.
44      * @param executor an executor that is used to dispatch the updates.
45      * @param consumer a listener interested in receiving updates.
46      */
addListener(@onNull Executor executor, @NonNull Consumer<IBinder> consumer)47     void addListener(@NonNull Executor executor,
48             @NonNull Consumer<IBinder> consumer) {
49         synchronized (mLock) {
50             if (indexOf(consumer) > -1) {
51                 return;
52             }
53             mListenerContainers.add(new ListenerContainer(executor, consumer));
54         }
55     }
56 
57     /**
58      * Removes the listener that was previously registered. If the listener was not registered this
59      * method does not have any effect.
60      */
removeListener(@onNull Consumer<IBinder> consumer)61     void removeListener(@NonNull Consumer<IBinder> consumer) {
62         synchronized (mLock) {
63             final int index = indexOf(consumer);
64             if (index > -1) {
65                 mListenerContainers.remove(index);
66             }
67         }
68     }
69 
70     /**
71      * Dispatches the update to all registered listeners
72      * @param activityToken a token for the {@link Activity} that received a configuration update.
73      */
dispatchOnConfigurationChanged(@onNull IBinder activityToken)74     void dispatchOnConfigurationChanged(@NonNull IBinder activityToken) {
75         final List<ListenerContainer> consumers;
76         synchronized (mLock) {
77             consumers = new ArrayList<>(mListenerContainers);
78         }
79         for (int i = 0; i < consumers.size(); i++) {
80             consumers.get(i).accept(activityToken);
81         }
82     }
83 
84     @GuardedBy("mLock")
indexOf(Consumer<IBinder> consumer)85     private int indexOf(Consumer<IBinder> consumer) {
86         for (int i = 0; i < mListenerContainers.size(); i++) {
87             if (mListenerContainers.get(i).isMatch(consumer)) {
88                 return i;
89             }
90         }
91         return -1;
92     }
93 
94     private static final class ListenerContainer {
95 
96         @NonNull
97         private final Executor mExecutor;
98         @NonNull
99         private final Consumer<IBinder> mConsumer;
100 
ListenerContainer(@onNull Executor executor, @NonNull Consumer<IBinder> consumer)101         ListenerContainer(@NonNull Executor executor,
102                 @NonNull Consumer<IBinder> consumer) {
103             mExecutor = executor;
104             mConsumer = consumer;
105         }
106 
isMatch(@onNull Consumer<IBinder> consumer)107         public boolean isMatch(@NonNull Consumer<IBinder> consumer) {
108             return mConsumer.equals(consumer);
109         }
110 
accept(@onNull IBinder activityToken)111         public void accept(@NonNull IBinder activityToken) {
112             mExecutor.execute(() -> mConsumer.accept(activityToken));
113         }
114 
115     }
116 }
117