/* * Copyright (C) 2024 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.settingslib.datastore import androidx.annotation.AnyThread import androidx.annotation.GuardedBy import java.util.WeakHashMap import java.util.concurrent.Executor /** * Callback to be informed of changes in [Observable] object. * * The observer is weakly referenced, a strong reference must be kept. */ fun interface Observer { /** * Called by [Observable] in the event of changes. * * This callback will run in the given [Executor] when observer is added. * * @param observable observable of the change * @param reason the reason of change * @see [Observable.addObserver] for the notices. */ fun onChanged(observable: Observable, reason: Int) } /** An observable object allows to observe change with [Observer]. */ @AnyThread interface Observable { /** * Adds an observer. * * Notes: * - The order in which observers will be notified is unspecified. * - The observer is weakly referenced to avoid memory leaking, the call site must keep a strong * reference of the observer. * - It is possible that the callback may be triggered even there is no real data change. For * example, when data restore/clear happens, it might be too complex to check if data is * really changed, thus all the registered observers are notified directly. * * @param observer observer to be notified * @param executor executor to run the [Observer.onChanged] callback */ fun addObserver(observer: Observer, executor: Executor) /** Removes given observer. */ fun removeObserver(observer: Observer) /** * Notifies observers that a change occurs. * * @param reason reason of the change */ fun notifyChange(reason: Int) } /** Delegation of [Observable]. */ interface ObservableDelegation : Observable { /** [Observable] to delegate. */ val observableDelegate: Observable override fun addObserver(observer: Observer, executor: Executor) = observableDelegate.addObserver(observer, executor) override fun removeObserver(observer: Observer) = observableDelegate.removeObserver(observer) override fun notifyChange(reason: Int) = observableDelegate.notifyChange(reason) } /** A thread safe implementation of [Observable]. */ class DataObservable(private val observable: Observable) : Observable { // Instead of @GuardedBy("this"), guarded by itself because DataObservable object could be // synchronized outside by the holder @GuardedBy("itself") private val observers = WeakHashMap() override fun addObserver(observer: Observer, executor: Executor) { val oldExecutor = synchronized(observers) { observers.put(observer, executor) } if (oldExecutor != null && oldExecutor != executor) { throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor") } } override fun removeObserver(observer: Observer) { synchronized(observers) { observers.remove(observer) } } override fun notifyChange(reason: Int) { // make a copy to avoid potential ConcurrentModificationException val entries = synchronized(observers) { observers.entries.toTypedArray() } for (entry in entries) { val observer = entry.key // avoid reference "entry" entry.value.execute { observer.onChanged(observable, reason) } } } }