1 /*
<lambda>null2  * Copyright (C) 2019 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.permissioncontroller.permission.data
18 
19 import android.os.Binder
20 import com.android.permissioncontroller.permission.utils.IPC
21 import kotlinx.coroutines.Dispatchers.Main
22 import kotlinx.coroutines.GlobalScope
23 import kotlinx.coroutines.Job
24 import kotlinx.coroutines.launch
25 
26 /**
27  * A LiveData which loads its data in a background AsyncTask. It will cancel current tasks, if new
28  * requests come during execution
29  *
30  * @param isStaticVal Whether or not this LiveData value is expected to change
31  * @param alwaysUpdateOnActive Whether or not this LiveData should update upon going active
32  */
33 abstract class SmartAsyncMediatorLiveData<T>(
34     isStaticVal: Boolean = false,
35     private val alwaysUpdateOnActive: Boolean = true
36 ) : SmartUpdateMediatorLiveData<T>(isStaticVal) {
37 
38     private var currentJob: Job? = null
39     @Volatile private var jobQueued = false
40     @Volatile private var jobRunning = false
41 
42     /**
43      * The main function which will load data. It should periodically check isCancelled to see if it
44      * should stop working. If data is loaded, it should call "postValue".
45      */
46     abstract suspend fun loadDataAndPostValue(job: Job)
47 
48     override fun onUpdate() {
49         updateAsync()
50     }
51 
52     open fun updateAsync() {
53         if (jobRunning) {
54             jobQueued = true
55             return
56         } else {
57             jobRunning = true
58         }
59 
60         GlobalScope.launch(IPC) {
61             currentJob = coroutineContext[Job]
62             loadDataAndPostValue(currentJob!!)
63             // TODO ntmyren: generalize this command to the IPC dispatcher
64             Binder.flushPendingCommands()
65             jobRunning = false
66             if (jobQueued) {
67                 jobQueued = false
68                 GlobalScope.launch(Main.immediate) { updateAsync() }
69             }
70         }
71     }
72 
73     override fun onActive() {
74         super.onActive()
75 
76         if (alwaysUpdateOnActive) {
77             updateAsync()
78         }
79     }
80 
81     override fun onInactive() {
82         cancelJobIfRunning()
83         jobQueued = false
84         super.onInactive()
85     }
86 
87     private fun cancelJobIfRunning() {
88         currentJob?.let { job ->
89             if (job.isActive) {
90                 job.cancel()
91             }
92         }
93     }
94 }
95