Merge "Create SpaPrivileged Lib"

This commit is contained in:
Chaohui Wang
2022-08-15 15:04:21 +00:00
committed by Android (Google) Code Review
18 changed files with 472 additions and 41 deletions

View File

@@ -16,9 +16,9 @@
buildscript {
ext {
minSdk_version = 31
compose_version = '1.2.0-alpha04'
compose_material3_version = '1.0.0-alpha06'
spa_min_sdk = 31
jetpack_compose_version = '1.2.0-alpha04'
jetpack_compose_material3_version = '1.0.0-alpha06'
}
}
plugins {

View File

@@ -13,14 +13,14 @@
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.
-->
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.spa.codelab">
<application
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.SettingsLib.Compose.DayNight">
android:theme="@style/Theme.SpaLib.DayNight">
<activity
android:name="com.android.settingslib.spa.codelab.MainActivity"
android:exported="true">

View File

@@ -25,7 +25,7 @@ android {
defaultConfig {
applicationId "com.android.settingslib.spa.codelab"
minSdk minSdk_version
minSdk spa_min_sdk
targetSdk 33
versionCode 1
versionName "1.0"
@@ -52,7 +52,7 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerExtensionVersion jetpack_compose_version
}
packagingOptions {
resources {

View File

@@ -24,7 +24,7 @@ android {
compileSdk 33
defaultConfig {
minSdk minSdk_version
minSdk spa_min_sdk
targetSdk 33
}
@@ -49,7 +49,7 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerExtensionVersion jetpack_compose_version
}
packagingOptions {
resources {
@@ -59,11 +59,11 @@ android {
}
dependencies {
api "androidx.compose.material3:material3:$compose_material3_version"
api "androidx.compose.material:material-icons-extended:$compose_version"
api "androidx.compose.runtime:runtime-livedata:$compose_version"
api "androidx.compose.ui:ui-tooling-preview:$compose_version"
api "androidx.compose.material3:material3:$jetpack_compose_material3_version"
api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
api 'androidx.navigation:navigation-compose:2.5.0'
api 'com.google.android.material:material:1.6.1'
debugApi "androidx.compose.ui:ui-tooling:$compose_version"
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
}

View File

@@ -13,8 +13,8 @@
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.
-->
-->
<resources>
<style name="Theme.SettingsLib.Compose.DayNight" />
<style name="Theme.SpaLib.DayNight" />
</resources>

View File

@@ -13,15 +13,15 @@
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.
-->
-->
<resources>
<style name="Theme.SettingsLib.Compose" parent="Theme.Material3.DayNight.NoActionBar">
<style name="Theme.SpaLib" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="Theme.SettingsLib.Compose.DayNight">
<style name="Theme.SpaLib.DayNight">
<item name="android:windowLightStatusBar">true</item>
</style>
</resources>

View File

@@ -0,0 +1,180 @@
/*
* Copyright (C) 2022 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.settingslib.spa.framework.compose
import android.graphics.drawable.Animatable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.Looper
import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.asAndroidColorFilter
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.withSave
import androidx.compose.ui.unit.LayoutDirection
import kotlin.math.roundToInt
/**
* *************************************************************************************************
* This file was forked from
* https://github.com/google/accompanist/blob/main/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt
* and will be removed once it lands in AndroidX.
*/
private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) {
Handler(Looper.getMainLooper())
}
/**
* A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
* should be remembered to be able to start and stop [Animatable] animations.
*
* Instances are usually retrieved from [rememberDrawablePainter].
*/
class DrawablePainter(
val drawable: Drawable
) : Painter(), RememberObserver {
private var drawInvalidateTick by mutableStateOf(0)
private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
private val callback: Drawable.Callback by lazy {
object : Drawable.Callback {
override fun invalidateDrawable(d: Drawable) {
// Update the tick so that we get re-drawn
drawInvalidateTick++
// Update our intrinsic size too
drawableIntrinsicSize = drawable.intrinsicSize
}
override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
MAIN_HANDLER.postAtTime(what, time)
}
override fun unscheduleDrawable(d: Drawable, what: Runnable) {
MAIN_HANDLER.removeCallbacks(what)
}
}
}
init {
if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
// Update the drawable's bounds to match the intrinsic size
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
}
}
override fun onRemembered() {
drawable.callback = callback
drawable.setVisible(true, true)
if (drawable is Animatable) drawable.start()
}
override fun onAbandoned() = onForgotten()
override fun onForgotten() {
if (drawable is Animatable) drawable.stop()
drawable.setVisible(false, false)
drawable.callback = null
}
override fun applyAlpha(alpha: Float): Boolean {
drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
return true
}
override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
drawable.colorFilter = colorFilter?.asAndroidColorFilter()
return true
}
override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean =
drawable.setLayoutDirection(
when (layoutDirection) {
LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
}
)
override val intrinsicSize: Size get() = drawableIntrinsicSize
override fun DrawScope.onDraw() {
drawIntoCanvas { canvas ->
// Reading this ensures that we invalidate when invalidateDrawable() is called
drawInvalidateTick
// Update the Drawable's bounds
drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
canvas.withSave {
drawable.draw(canvas.nativeCanvas)
}
}
}
}
/**
* Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the
* drawable contents and use Compose primitives where possible.
*
* If the provided [drawable] is `null`, an empty no-op painter is returned.
*
* This function tries to dispatch lifecycle events to [drawable] as much as possible from
* within Compose.
*
* @sample com.google.accompanist.sample.drawablepainter.BasicSample
*/
@Composable
fun rememberDrawablePainter(drawable: Drawable?): Painter = remember(drawable) {
when (drawable) {
null -> EmptyPainter
is BitmapDrawable -> BitmapPainter(drawable.bitmap.asImageBitmap())
is ColorDrawable -> ColorPainter(Color(drawable.color))
// Since the DrawablePainter will be remembered and it implements RememberObserver, it
// will receive the necessary events
else -> DrawablePainter(drawable.mutate())
}
}
private val Drawable.intrinsicSize: Size
get() = when {
// Only return a finite size if the drawable has an intrinsic size
intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
}
else -> Size.Unspecified
}
internal object EmptyPainter : Painter() {
override val intrinsicSize: Size get() = Size.Unspecified
override fun DrawScope.onDraw() {}
}

View File

@@ -16,9 +16,17 @@
package com.android.settingslib.spa.framework.compose
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
@Composable
fun <T> rememberContext(constructor: (Context) -> T): T {
val context = LocalContext.current
return remember(context) { constructor(context) }
}
/**
* Remember the [State] initialized with the [this].

View File

@@ -25,8 +25,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Alignment
@@ -39,6 +37,7 @@ import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.ui.SettingsTitle
@Composable
internal fun BaseLayout(
@@ -94,11 +93,7 @@ private fun BaseIcon(
@Composable
private fun Titles(title: String, subTitle: @Composable () -> Unit, modifier: Modifier) {
Column(modifier) {
Text(
text = title,
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleMedium,
)
SettingsTitle(title)
subTitle()
}
}

View File

@@ -19,8 +19,6 @@ package com.android.settingslib.spa.widget.preference
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.BatteryChargingFull
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
@@ -29,6 +27,7 @@ import androidx.compose.ui.unit.Dp
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.ui.SettingsBody
@Composable
internal fun BasePreference(
@@ -44,15 +43,7 @@ internal fun BasePreference(
) {
BaseLayout(
title = title,
subTitle = {
if (summary.value.isNotEmpty()) {
Text(
text = summary.value,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
)
}
},
subTitle = { SettingsBody(summary) },
modifier = modifier,
icon = icon,
enabled = enabled,

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2022 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.settingslib.spa.widget.ui
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
@Composable
fun SettingsTitle(title: State<String>) {
SettingsTitle(title.value)
}
@Composable
fun SettingsTitle(title: String) {
Text(
text = title,
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleMedium,
)
}
@Composable
fun SettingsBody(body: State<String>) {
SettingsBody(body.value)
}
@Composable
fun SettingsBody(body: String) {
if (body.isNotEmpty()) {
Text(
text = body,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
)
}
}

View File

@@ -24,7 +24,7 @@ android {
compileSdk 33
defaultConfig {
minSdk minSdk_version
minSdk spa_min_sdk
targetSdk 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -50,7 +50,7 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerExtensionVersion jetpack_compose_version
}
packagingOptions {
resources {
@@ -62,6 +62,6 @@ android {
dependencies {
androidTestImplementation(project(":spa"))
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$jetpack_compose_version")
androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
}

View File

@@ -0,0 +1,33 @@
//
// Copyright (C) 2022 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 {
default_applicable_licenses: ["frameworks_base_license"],
}
android_library {
name: "SpaPrivilegedLib",
srcs: ["src/**/*.kt"],
static_libs: [
"SpaLib",
"SettingsLib",
"androidx.compose.runtime_runtime",
],
kotlincflags: ["-Xjvm-default=all"],
min_sdk_version: "31",
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2022 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.
-->
<manifest package="com.android.settingslib.spaprivileged" />

View File

@@ -0,0 +1 @@
include platform/frameworks/base:/packages/SettingsLib/Spa/OWNERS

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2022 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.settingslib.spaprivileged.framework.app
import android.content.Context
import android.content.pm.ApplicationInfo
import android.graphics.drawable.Drawable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import com.android.settingslib.Utils
import com.android.settingslib.spa.framework.compose.rememberContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun rememberAppRepository(): AppRepository = rememberContext(::AppRepositoryImpl)
interface AppRepository {
@Composable
fun produceLabel(app: ApplicationInfo): State<String>
@Composable
fun produceIcon(app: ApplicationInfo): State<Drawable?>
}
private class AppRepositoryImpl(private val context: Context) : AppRepository {
private val packageManager = context.packageManager
@Composable
override fun produceLabel(app: ApplicationInfo) = produceState(initialValue = "", app) {
withContext(Dispatchers.Default) {
value = app.loadLabel(packageManager).toString()
}
}
@Composable
override fun produceIcon(app: ApplicationInfo) =
produceState<Drawable?>(initialValue = null, app) {
withContext(Dispatchers.Default) {
value = Utils.getBadgedIcon(context, app)
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2022 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.settingslib.spaprivileged.framework.app
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
object PackageManagers {
fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo =
PackageManager.getPackageInfoAsUserCached(packageName, 0, userId)
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2022 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.settingslib.spaprivileged.template.app
import android.content.pm.ApplicationInfo
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsTitle
import com.android.settingslib.spaprivileged.framework.app.PackageManagers
import com.android.settingslib.spaprivileged.framework.app.rememberAppRepository
@Composable
fun AppInfo(packageName: String, userId: Int) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally) {
val packageInfo = remember { PackageManagers.getPackageInfoAsUser(packageName, userId) }
Box(modifier = Modifier.padding(8.dp)) {
AppIcon(app = packageInfo.applicationInfo, size = 48)
}
AppLabel(packageInfo.applicationInfo)
Spacer(modifier = Modifier.height(4.dp))
SettingsBody(packageInfo.versionName)
}
}
@Composable
fun AppIcon(app: ApplicationInfo, size: Int) {
val appRepository = rememberAppRepository()
Image(
painter = rememberDrawablePainter(appRepository.produceIcon(app).value),
contentDescription = null,
modifier = Modifier.size(size.dp)
)
}
@Composable
fun AppLabel(app: ApplicationInfo) {
val appRepository = rememberAppRepository()
SettingsTitle(appRepository.produceLabel(app))
}