ActionTrampoline.kt
/*
* Copyright 2021 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 androidx.glance.appwidget.action
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.widget.RemoteViews
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.glance.appwidget.TranslationContext
internal enum class ActionTrampolineType {
ACTIVITY, BROADCAST, SERVICE, FOREGROUND_SERVICE, CALLBACK
}
private const val ActionTrampolineScheme = "glance-action"
/**
* Wraps the "action intent" into an activity trampoline intent, where it will be invoked based on
* the type, with modifying its content.
*
* @see launchTrampolineAction
*/
internal fun Intent.applyTrampolineIntent(
translationContext: TranslationContext,
viewId: Int,
type: ActionTrampolineType
): Intent {
val target = if (type == ActionTrampolineType.ACTIVITY) {
ActionTrampolineActivity::class.java
} else {
InvisibleActionTrampolineActivity::class.java
}
return Intent(translationContext.context, target).also { intent ->
intent.data = createUniqueUri(translationContext, viewId, type)
intent.putExtra(ActionTypeKey, type.name)
intent.putExtra(ActionIntentKey, this)
}
}
internal fun createUniqueUri(
translationContext: TranslationContext,
viewId: Int,
type: ActionTrampolineType,
): Uri = Uri.Builder().apply {
scheme(ActionTrampolineScheme)
path(type.name)
appendQueryParameter("appWidgetId", translationContext.appWidgetId.toString())
appendQueryParameter("viewId", viewId.toString())
appendQueryParameter("viewSize", translationContext.layoutSize.toString())
if (translationContext.isLazyCollectionDescendant) {
appendQueryParameter(
"lazyCollection",
translationContext.layoutCollectionViewId.toString()
)
appendQueryParameter(
"lazeViewItem",
translationContext.layoutCollectionItemId.toString()
)
}
}.build()
/**
* Unwraps and launches the action intent based on its type.
*
* @see applyTrampolineIntent
*/
internal fun Activity.launchTrampolineAction(intent: Intent) {
val actionIntent = requireNotNull(intent.getParcelableExtra<Intent>(ActionIntentKey)) {
"List adapter activity trampoline invoked without specifying target intent."
}
if (intent.hasExtra(RemoteViews.EXTRA_CHECKED)) {
actionIntent.putExtra(
RemoteViews.EXTRA_CHECKED,
intent.getBooleanExtra(RemoteViews.EXTRA_CHECKED, false)
)
}
val type = requireNotNull(intent.getStringExtra(ActionTypeKey)) {
"List adapter activity trampoline invoked without trampoline type"
}
when (ActionTrampolineType.valueOf(type)) {
ActionTrampolineType.ACTIVITY -> startActivity(actionIntent)
ActionTrampolineType.BROADCAST, ActionTrampolineType.CALLBACK -> sendBroadcast(actionIntent)
ActionTrampolineType.SERVICE -> startService(actionIntent)
ActionTrampolineType.FOREGROUND_SERVICE -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ListAdapterTrampolineApi26Impl.startForegroundService(
context = this,
intent = actionIntent
)
} else {
startService(actionIntent)
}
}
}
finish()
}
private const val ActionTypeKey = "ACTION_TYPE"
private const val ActionIntentKey = "ACTION_INTENT"
@RequiresApi(Build.VERSION_CODES.O)
private object ListAdapterTrampolineApi26Impl {
@DoNotInline
fun startForegroundService(context: Context, intent: Intent) {
context.startForegroundService(intent)
}
}