• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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