1 /* 2 * Copyright (C) 2022 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 com.android.settings.network.helper; 18 19 import androidx.annotation.AnyThread; 20 import androidx.annotation.NonNull; 21 import androidx.annotation.UiThread; 22 import androidx.annotation.VisibleForTesting; 23 import androidx.lifecycle.Lifecycle; 24 25 import com.android.settingslib.utils.ThreadUtils; 26 27 import java.util.concurrent.atomic.AtomicLong; 28 import java.util.function.Consumer; 29 30 /** 31 * A {@link LifecycleCallbackAdapter} which support carrying a result from any threads back to UI 32 * thread through {@link #postResult(T)}. 33 * 34 * A {@link Consumer<T>} would be invoked from UI thread for further processing on the result. 35 * 36 * Note: Result not in STARTED or RESUMED stage will be discarded silently. 37 * This is to align with the criteria set within 38 * {@link LifecycleCallbackAdapter#onStateChanged()}. 39 */ 40 @VisibleForTesting 41 public class LifecycleCallbackConverter<T> extends LifecycleCallbackAdapter { 42 private static final String TAG = "LifecycleCallbackConverter"; 43 44 private final Thread mUiThread; 45 private final Consumer<T> mResultCallback; 46 47 /** 48 * A record of number of active status change. 49 * Even numbers (0, 2, 4, 6 ...) are inactive status. 50 * Odd numbers (1, 3, 5, 7 ...) are active status. 51 */ 52 private final AtomicLong mNumberOfActiveStatusChange = new AtomicLong(); 53 54 /** 55 * Constructor 56 * 57 * @param lifecycle {@link Lifecycle} to monitor 58 * @param resultCallback for further processing the result 59 */ 60 @VisibleForTesting 61 @UiThread LifecycleCallbackConverter( @onNull Lifecycle lifecycle, @NonNull Consumer<T> resultCallback)62 public LifecycleCallbackConverter( 63 @NonNull Lifecycle lifecycle, @NonNull Consumer<T> resultCallback) { 64 super(lifecycle); 65 mUiThread = Thread.currentThread(); 66 mResultCallback = resultCallback; 67 } 68 69 /** 70 * Post a result (from any thread) back to UI thread. 71 * 72 * @param result the object ready to be passed back to {@link Consumer<T>}. 73 */ 74 @AnyThread 75 @VisibleForTesting postResult(T result)76 public void postResult(T result) { 77 /** 78 * Since mNumberOfActiveStatusChange only increase, it is a concept of sequence number. 79 * Carry it when sending data in between different threads allow to verify if the data 80 * has arrived on time. And drop the data when expired. 81 */ 82 long currentNumberOfChange = mNumberOfActiveStatusChange.get(); 83 if (Thread.currentThread() == mUiThread) { 84 dispatchExtResult(currentNumberOfChange, result); // Dispatch directly 85 } else { 86 postResultToUiThread(currentNumberOfChange, result); 87 } 88 } 89 90 @AnyThread postResultToUiThread(long numberOfStatusChange, T result)91 protected void postResultToUiThread(long numberOfStatusChange, T result) { 92 ThreadUtils.postOnMainThread(() -> dispatchExtResult(numberOfStatusChange, result)); 93 } 94 95 @UiThread dispatchExtResult(long numberOfStatusChange, T result)96 protected void dispatchExtResult(long numberOfStatusChange, T result) { 97 /** 98 * For a postResult() sending in between different threads, not only create a latency 99 * but also enqueued into main UI thread for dispatch. 100 * 101 * To align behavior within {@link LifecycleCallbackAdapter#onStateChanged()}, 102 * some checking on both numberOfStatusChange and {@link Lifecycle} status are required. 103 */ 104 if (isActiveStatus(numberOfStatusChange) 105 && (numberOfStatusChange == mNumberOfActiveStatusChange.get()) 106 && getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { 107 mResultCallback.accept(result); 108 } 109 } 110 isActiveStatus(long numberOfStatusChange)111 private static final boolean isActiveStatus(long numberOfStatusChange) { 112 return ((numberOfStatusChange & 1L) != 0L); 113 } 114 115 /* Implementation of LifecycleCallbackAdapter */ 116 @UiThread isCallbackActive()117 public boolean isCallbackActive() { 118 return isActiveStatus(mNumberOfActiveStatusChange.get()); 119 } 120 121 /* Implementation of LifecycleCallbackAdapter */ 122 @UiThread setCallbackActive(boolean updatedActiveStatus)123 public void setCallbackActive(boolean updatedActiveStatus) { 124 /** 125 * Make sure only increase when active status got changed. 126 * This is to implement the definition of mNumberOfActiveStatusChange. 127 */ 128 if (isCallbackActive() != updatedActiveStatus) { 129 mNumberOfActiveStatusChange.getAndIncrement(); 130 } 131 } 132 } 133