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