1 /*
2  * Copyright (C) 2024 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.intentresolver
18 
19 import android.app.Activity
20 import android.os.UserHandle
21 import android.util.Log
22 import androidx.activity.ComponentActivity
23 import androidx.activity.viewModels
24 import androidx.lifecycle.DefaultLifecycleObserver
25 import androidx.lifecycle.Lifecycle
26 import androidx.lifecycle.LifecycleOwner
27 import com.android.intentresolver.annotation.JavaInterop
28 import com.android.intentresolver.domain.interactor.UserInteractor
29 import com.android.intentresolver.inject.Background
30 import com.android.intentresolver.ui.model.ResolverRequest
31 import com.android.intentresolver.ui.viewmodel.ResolverViewModel
32 import com.android.intentresolver.validation.Invalid
33 import com.android.intentresolver.validation.Valid
34 import com.android.intentresolver.validation.log
35 import dagger.hilt.android.scopes.ActivityScoped
36 import javax.inject.Inject
37 import kotlinx.coroutines.CoroutineDispatcher
38 
39 private const val TAG: String = "ResolverHelper"
40 
41 /**
42  * __Purpose__
43  *
44  * Cleanup aid. Provides a pathway to cleaner code.
45  *
46  * __Incoming References__
47  *
48  * ResolverHelper must not expose any properties or functions directly back to ResolverActivity. If
49  * a value or operation is required by ResolverActivity, then it must be added to
50  * ResolverInitializer (or a new interface as appropriate) with ResolverActivity supplying a
51  * callback to receive it at the appropriate point. This enforces unidirectional control flow.
52  *
53  * __Outgoing References__
54  *
55  * _ResolverActivity_
56  *
57  * This class must only reference it's host as Activity/ComponentActivity; no down-cast to
58  * [ResolverActivity]. Other components should be created here or supplied via Injection, and not
59  * referenced directly from the activity. This prevents circular dependencies from forming. If
60  * necessary, during cleanup the dependency can be supplied back to ChooserActivity as described
61  * above in 'Incoming References', see [ResolverInitializer].
62  *
63  * _Elsewhere_
64  *
65  * Where possible, Singleton and ActivityScoped dependencies should be injected here instead of
66  * referenced from an existing location. If not available for injection, the value should be
67  * constructed here, then provided to where it is needed.
68  */
69 @ActivityScoped
70 @JavaInterop
71 class ResolverHelper
72 @Inject
73 constructor(
74     hostActivity: Activity,
75     private val userInteractor: UserInteractor,
76     @Background private val background: CoroutineDispatcher,
77 ) : DefaultLifecycleObserver {
78     // This is guaranteed by Hilt, since only a ComponentActivity is injectable.
79     private val activity: ComponentActivity = hostActivity as ComponentActivity
80     private val viewModel by activity.viewModels<ResolverViewModel>()
81 
82     private lateinit var activityInitializer: Runnable
83 
84     init {
85         activity.lifecycle.addObserver(this)
86     }
87 
88     /**
89      * Set the initialization hook for the host activity.
90      *
91      * This _must_ be called from [ResolverActivity.onCreate].
92      */
setInitializernull93     fun setInitializer(initializer: Runnable) {
94         if (activity.lifecycle.currentState != Lifecycle.State.INITIALIZED) {
95             error("setInitializer must be called before onCreate returns")
96         }
97         activityInitializer = initializer
98     }
99 
100     /** Invoked by Lifecycle, after Activity.onCreate() _returns_. */
onCreatenull101     override fun onCreate(owner: LifecycleOwner) {
102         Log.i(TAG, "CREATE")
103         Log.i(TAG, "${viewModel.activityModel}")
104 
105         val callerUid: Int = viewModel.activityModel.launchedFromUid
106         if (callerUid < 0 || UserHandle.isIsolated(callerUid)) {
107             Log.e(TAG, "Can't start a resolver from uid $callerUid")
108             activity.finish()
109             return
110         }
111 
112         when (val request = viewModel.initialRequest) {
113             is Valid -> initializeActivity(request)
114             is Invalid -> reportErrorsAndFinish(request)
115         }
116     }
117 
reportErrorsAndFinishnull118     private fun reportErrorsAndFinish(request: Invalid<ResolverRequest>) {
119         request.errors.forEach { it.log(TAG) }
120         activity.finish()
121     }
122 
initializeActivitynull123     private fun initializeActivity(request: Valid<ResolverRequest>) {
124         Log.d(TAG, "initializeActivity")
125         request.warnings.forEach { it.log(TAG) }
126 
127         activityInitializer.run()
128     }
129 }
130