/* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.intentresolver import android.app.Activity import android.os.UserHandle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.viewModels import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import com.android.intentresolver.annotation.JavaInterop import com.android.intentresolver.domain.interactor.UserInteractor import com.android.intentresolver.inject.Background import com.android.intentresolver.ui.model.ResolverRequest import com.android.intentresolver.ui.viewmodel.ResolverViewModel import com.android.intentresolver.validation.Invalid import com.android.intentresolver.validation.Valid import com.android.intentresolver.validation.log import dagger.hilt.android.scopes.ActivityScoped import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher private const val TAG: String = "ResolverHelper" /** * __Purpose__ * * Cleanup aid. Provides a pathway to cleaner code. * * __Incoming References__ * * ResolverHelper must not expose any properties or functions directly back to ResolverActivity. If * a value or operation is required by ResolverActivity, then it must be added to * ResolverInitializer (or a new interface as appropriate) with ResolverActivity supplying a * callback to receive it at the appropriate point. This enforces unidirectional control flow. * * __Outgoing References__ * * _ResolverActivity_ * * This class must only reference it's host as Activity/ComponentActivity; no down-cast to * [ResolverActivity]. Other components should be created here or supplied via Injection, and not * referenced directly from the activity. This prevents circular dependencies from forming. If * necessary, during cleanup the dependency can be supplied back to ChooserActivity as described * above in 'Incoming References', see [ResolverInitializer]. * * _Elsewhere_ * * Where possible, Singleton and ActivityScoped dependencies should be injected here instead of * referenced from an existing location. If not available for injection, the value should be * constructed here, then provided to where it is needed. */ @ActivityScoped @JavaInterop class ResolverHelper @Inject constructor( hostActivity: Activity, private val userInteractor: UserInteractor, @Background private val background: CoroutineDispatcher, ) : DefaultLifecycleObserver { // This is guaranteed by Hilt, since only a ComponentActivity is injectable. private val activity: ComponentActivity = hostActivity as ComponentActivity private val viewModel by activity.viewModels() private lateinit var activityInitializer: Runnable init { activity.lifecycle.addObserver(this) } /** * Set the initialization hook for the host activity. * * This _must_ be called from [ResolverActivity.onCreate]. */ fun setInitializer(initializer: Runnable) { if (activity.lifecycle.currentState != Lifecycle.State.INITIALIZED) { error("setInitializer must be called before onCreate returns") } activityInitializer = initializer } /** Invoked by Lifecycle, after Activity.onCreate() _returns_. */ override fun onCreate(owner: LifecycleOwner) { Log.i(TAG, "CREATE") Log.i(TAG, "${viewModel.activityModel}") val callerUid: Int = viewModel.activityModel.launchedFromUid if (callerUid < 0 || UserHandle.isIsolated(callerUid)) { Log.e(TAG, "Can't start a resolver from uid $callerUid") activity.finish() return } when (val request = viewModel.initialRequest) { is Valid -> initializeActivity(request) is Invalid -> reportErrorsAndFinish(request) } } private fun reportErrorsAndFinish(request: Invalid) { request.errors.forEach { it.log(TAG) } activity.finish() } private fun initializeActivity(request: Valid) { Log.d(TAG, "initializeActivity") request.warnings.forEach { it.log(TAG) } activityInitializer.run() } }