/* * Copyright (C) 2022 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.settings.network.helper; import androidx.annotation.AnyThread; import androidx.annotation.NonNull; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Lifecycle; import com.android.settingslib.utils.ThreadUtils; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; /** * A {@link LifecycleCallbackAdapter} which support carrying a result from any threads back to UI * thread through {@link #postResult(T)}. * * A {@link Consumer} would be invoked from UI thread for further processing on the result. * * Note: Result not in STARTED or RESUMED stage will be discarded silently. * This is to align with the criteria set within * {@link LifecycleCallbackAdapter#onStateChanged()}. */ @VisibleForTesting public class LifecycleCallbackConverter extends LifecycleCallbackAdapter { private static final String TAG = "LifecycleCallbackConverter"; private final Thread mUiThread; private final Consumer mResultCallback; /** * A record of number of active status change. * Even numbers (0, 2, 4, 6 ...) are inactive status. * Odd numbers (1, 3, 5, 7 ...) are active status. */ private final AtomicLong mNumberOfActiveStatusChange = new AtomicLong(); /** * Constructor * * @param lifecycle {@link Lifecycle} to monitor * @param resultCallback for further processing the result */ @VisibleForTesting @UiThread public LifecycleCallbackConverter( @NonNull Lifecycle lifecycle, @NonNull Consumer resultCallback) { super(lifecycle); mUiThread = Thread.currentThread(); mResultCallback = resultCallback; } /** * Post a result (from any thread) back to UI thread. * * @param result the object ready to be passed back to {@link Consumer}. */ @AnyThread @VisibleForTesting public void postResult(T result) { /** * Since mNumberOfActiveStatusChange only increase, it is a concept of sequence number. * Carry it when sending data in between different threads allow to verify if the data * has arrived on time. And drop the data when expired. */ long currentNumberOfChange = mNumberOfActiveStatusChange.get(); if (Thread.currentThread() == mUiThread) { dispatchExtResult(currentNumberOfChange, result); // Dispatch directly } else { postResultToUiThread(currentNumberOfChange, result); } } @AnyThread protected void postResultToUiThread(long numberOfStatusChange, T result) { ThreadUtils.postOnMainThread(() -> dispatchExtResult(numberOfStatusChange, result)); } @UiThread protected void dispatchExtResult(long numberOfStatusChange, T result) { /** * For a postResult() sending in between different threads, not only create a latency * but also enqueued into main UI thread for dispatch. * * To align behavior within {@link LifecycleCallbackAdapter#onStateChanged()}, * some checking on both numberOfStatusChange and {@link Lifecycle} status are required. */ if (isActiveStatus(numberOfStatusChange) && (numberOfStatusChange == mNumberOfActiveStatusChange.get()) && getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { mResultCallback.accept(result); } } private static final boolean isActiveStatus(long numberOfStatusChange) { return ((numberOfStatusChange & 1L) != 0L); } /* Implementation of LifecycleCallbackAdapter */ @UiThread public boolean isCallbackActive() { return isActiveStatus(mNumberOfActiveStatusChange.get()); } /* Implementation of LifecycleCallbackAdapter */ @UiThread public void setCallbackActive(boolean updatedActiveStatus) { /** * Make sure only increase when active status got changed. * This is to implement the definition of mNumberOfActiveStatusChange. */ if (isCallbackActive() != updatedActiveStatus) { mNumberOfActiveStatusChange.getAndIncrement(); } } }