1 /* <lambda>null2 * 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.provider.Settings 22 import android.util.Log 23 import androidx.activity.ComponentActivity 24 import androidx.activity.viewModels 25 import androidx.lifecycle.DefaultLifecycleObserver 26 import androidx.lifecycle.Lifecycle 27 import androidx.lifecycle.LifecycleOwner 28 import androidx.lifecycle.lifecycleScope 29 import androidx.lifecycle.repeatOnLifecycle 30 import com.android.intentresolver.annotation.JavaInterop 31 import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.ActivityResultRepository 32 import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.PendingSelectionCallbackRepository 33 import com.android.intentresolver.data.model.ChooserRequest 34 import com.android.intentresolver.platform.GlobalSettings 35 import com.android.intentresolver.ui.viewmodel.ChooserViewModel 36 import com.android.intentresolver.validation.Invalid 37 import com.android.intentresolver.validation.Valid 38 import com.android.intentresolver.validation.log 39 import dagger.hilt.android.scopes.ActivityScoped 40 import java.util.function.Consumer 41 import javax.inject.Inject 42 import kotlinx.coroutines.flow.combine 43 import kotlinx.coroutines.flow.distinctUntilChanged 44 import kotlinx.coroutines.flow.filter 45 import kotlinx.coroutines.flow.filterNotNull 46 import kotlinx.coroutines.flow.first 47 import kotlinx.coroutines.flow.map 48 import kotlinx.coroutines.flow.onEach 49 import kotlinx.coroutines.launch 50 51 private const val TAG: String = "ChooserHelper" 52 53 /** 54 * __Purpose__ 55 * 56 * Cleanup aid. Provides a pathway to cleaner code. 57 * 58 * __Incoming References__ 59 * 60 * ChooserHelper must not expose any properties or functions directly back to ChooserActivity. If a 61 * value or operation is required by ChooserActivity, then it must be added to ChooserInitializer 62 * (or a new interface as appropriate) with ChooserActivity supplying a callback to receive it at 63 * the appropriate point. This enforces unidirectional control flow. 64 * 65 * __Outgoing References__ 66 * 67 * _ChooserActivity_ 68 * 69 * This class must only reference it's host as Activity/ComponentActivity; no down-cast to 70 * [ChooserActivity]. Other components should be created here or supplied via Injection, and not 71 * referenced directly within ChooserActivity. This prevents circular dependencies from forming. If 72 * necessary, during cleanup the dependency can be supplied back to ChooserActivity as described 73 * above in 'Incoming References', see [ChooserInitializer]. 74 * 75 * _Elsewhere_ 76 * 77 * Where possible, Singleton and ActivityScoped dependencies should be injected here instead of 78 * referenced from an existing location. If not available for injection, the value should be 79 * constructed here, then provided to where it is needed. 80 */ 81 @ActivityScoped 82 @JavaInterop 83 class ChooserHelper 84 @Inject 85 constructor( 86 hostActivity: Activity, 87 private val activityResultRepo: ActivityResultRepository, 88 private val pendingSelectionCallbackRepo: PendingSelectionCallbackRepository, 89 private val globalSettings: GlobalSettings, 90 ) : DefaultLifecycleObserver { 91 // This is guaranteed by Hilt, since only a ComponentActivity is injectable. 92 private val activity: ComponentActivity = hostActivity as ComponentActivity 93 private val viewModel by activity.viewModels<ChooserViewModel>() 94 95 // TODO: provide the following through an init object passed into [setInitialize] 96 private lateinit var activityInitializer: Runnable 97 /** Invoked when there are updates to ChooserRequest */ 98 var onChooserRequestChanged: Consumer<ChooserRequest> = Consumer {} 99 /** Invoked when there are a new change to payload selection */ 100 var onPendingSelection: Runnable = Runnable {} 101 102 init { 103 activity.lifecycle.addObserver(this) 104 } 105 106 /** 107 * Set the initialization hook for the host activity. 108 * 109 * This _must_ be called from [ChooserActivity.onCreate]. 110 */ 111 fun setInitializer(initializer: Runnable) { 112 check(activity.lifecycle.currentState == Lifecycle.State.INITIALIZED) { 113 "setInitializer must be called before onCreate returns" 114 } 115 activityInitializer = initializer 116 } 117 118 /** Invoked by Lifecycle, after [ChooserActivity.onCreate] _returns_. */ 119 override fun onCreate(owner: LifecycleOwner) { 120 Log.i(TAG, "CREATE") 121 Log.i(TAG, "${viewModel.activityModel}") 122 123 val callerUid: Int = viewModel.activityModel.launchedFromUid 124 if (callerUid < 0 || UserHandle.isIsolated(callerUid)) { 125 Log.e(TAG, "Can't start a chooser from uid $callerUid") 126 activity.finish() 127 return 128 } 129 130 if (globalSettings.getBooleanOrNull(Settings.Global.SECURE_FRP_MODE) == true) { 131 Log.e(TAG, "Sharing disabled due to active FRP lock.") 132 activity.finish() 133 return 134 } 135 136 when (val request = viewModel.initialRequest) { 137 is Valid -> initializeActivity(request) 138 is Invalid -> reportErrorsAndFinish(request) 139 } 140 141 activity.lifecycleScope.launch { 142 activity.setResult(activityResultRepo.activityResult.filterNotNull().first()) 143 activity.finish() 144 } 145 146 activity.lifecycleScope.launch { 147 val hasPendingCallbackFlow = 148 pendingSelectionCallbackRepo.pendingTargetIntent 149 .map { it != null } 150 .distinctUntilChanged() 151 .onEach { hasPendingCallback -> 152 if (hasPendingCallback) { 153 onPendingSelection.run() 154 } 155 } 156 activity.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { 157 viewModel.request 158 .combine(hasPendingCallbackFlow) { request, hasPendingCallback -> 159 request to hasPendingCallback 160 } 161 // only take ChooserRequest if there are no pending callbacks 162 .filter { !it.second } 163 .map { it.first } 164 .distinctUntilChanged(areEquivalent = { old, new -> old === new }) 165 .collect { onChooserRequestChanged.accept(it) } 166 } 167 } 168 } 169 170 override fun onStart(owner: LifecycleOwner) { 171 Log.i(TAG, "START") 172 } 173 174 override fun onResume(owner: LifecycleOwner) { 175 Log.i(TAG, "RESUME") 176 } 177 178 override fun onPause(owner: LifecycleOwner) { 179 Log.i(TAG, "PAUSE") 180 } 181 182 override fun onStop(owner: LifecycleOwner) { 183 Log.i(TAG, "STOP") 184 } 185 186 override fun onDestroy(owner: LifecycleOwner) { 187 Log.i(TAG, "DESTROY") 188 } 189 190 private fun reportErrorsAndFinish(request: Invalid<ChooserRequest>) { 191 request.errors.forEach { it.log(TAG) } 192 activity.finish() 193 } 194 195 private fun initializeActivity(request: Valid<ChooserRequest>) { 196 request.warnings.forEach { it.log(TAG) } 197 activityInitializer.run() 198 } 199 } 200