AppClone: Changes in AppInfo page for cloned app.
- Hides preferences for cloneable apps under Cloned Apps page - Displays Create option for cloneable apps under Cloned Apps page. - Invokes CloneBackend on click of create and refreshes to display newly cloned app's AppInfo page. - Appends suffix 'clone' for cloneable/cloned app. - Displays text 'Delete' instead of 'uninstall'. Screencast: https://screencast.googleplex.com/cast/NjI3MDEyMjk1MzAxNTI5NnxhOTIxZDhiZC03Zg Bug: 262375058 Test: make RunSettingsRoboTests -j64 Change-Id: I34018f6cc7420d2667c25fbca59c832b398d723e
This commit is contained in:
@@ -20,6 +20,7 @@ import android.content.Context
|
||||
import com.android.settings.spa.app.AllAppListPageProvider
|
||||
import com.android.settings.spa.app.AppsMainPageProvider
|
||||
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
|
||||
import com.android.settings.spa.app.appinfo.CloneAppInfoSettingsProvider
|
||||
import com.android.settings.spa.app.backgroundinstall.BackgroundInstalledAppsPageProvider
|
||||
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
|
||||
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
|
||||
@@ -68,6 +69,7 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) {
|
||||
AppLanguagesPageProvider,
|
||||
UsageStatsPageProvider,
|
||||
BackgroundInstalledAppsPageProvider,
|
||||
CloneAppInfoSettingsProvider,
|
||||
) + togglePermissionAppListTemplate.createPageProviders(),
|
||||
rootPages = listOf(
|
||||
SettingsPage.create(HomePageProvider.name),
|
||||
|
||||
74
src/com/android/settings/spa/app/appinfo/AppCreateButton.kt
Normal file
74
src/com/android/settings/spa/app/appinfo/AppCreateButton.kt
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.settings.spa.app.appinfo
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.settings.SettingsEnums
|
||||
import android.content.pm.ApplicationInfo
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Add
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import com.android.settings.R
|
||||
import com.android.settings.applications.manageapplications.CloneBackend
|
||||
import com.android.settings.overlay.FeatureFactory
|
||||
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider.getRoute
|
||||
import com.android.settingslib.spa.framework.compose.LocalNavController
|
||||
import com.android.settingslib.spa.widget.button.ActionButton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class AppCreateButton(packageInfoPresenter: PackageInfoPresenter) {
|
||||
private val context = packageInfoPresenter.context
|
||||
val enabledState = mutableStateOf(true)
|
||||
|
||||
@Composable
|
||||
fun getActionButton(app: ApplicationInfo): ActionButton? {
|
||||
return createButton(app)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun createButton(app: ApplicationInfo): ActionButton {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val navController = LocalNavController.current
|
||||
return ActionButton(
|
||||
text = context.getString(R.string.create),
|
||||
imageVector = Icons.Outlined.Add,
|
||||
enabled = enabledState.value,
|
||||
)
|
||||
{
|
||||
val cloneBackend = CloneBackend.getInstance(context)
|
||||
FeatureFactory.getFactory(context).metricsFeatureProvider.action(context,
|
||||
SettingsEnums.ACTION_CREATE_CLONE_APP)
|
||||
coroutineScope.launch {
|
||||
enabledState.value = false
|
||||
val result = installCloneApp(app, cloneBackend)
|
||||
if (result == CloneBackend.SUCCESS) {
|
||||
navController.navigate(getRoute(app.packageName, cloneBackend.cloneUserId))
|
||||
} else {
|
||||
enabledState.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun installCloneApp(app: ApplicationInfo, cloneBackend: CloneBackend): Int = withContext(Dispatchers.IO) {
|
||||
cloneBackend.installCloneApp(app.packageName)
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ package com.android.settings.spa.app.appinfo
|
||||
|
||||
import android.content.om.OverlayManager
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import com.android.settings.R
|
||||
@@ -30,6 +32,7 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
|
||||
private val context = packageInfoPresenter.context
|
||||
private val appButtonRepository = AppButtonRepository(context)
|
||||
private val overlayManager = context.getSystemService(OverlayManager::class.java)!!
|
||||
private val userManager = context.getSystemService(UserManager::class.java)!!
|
||||
|
||||
fun getActionButton(app: ApplicationInfo): ActionButton? {
|
||||
if (app.isSystemApp || app.isInstantApp) return null
|
||||
@@ -80,7 +83,8 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
|
||||
overlayManager.getOverlayInfo(packageName, userHandle)?.isEnabled == true
|
||||
|
||||
private fun uninstallButton(app: ApplicationInfo, enabled: Boolean) = ActionButton(
|
||||
text = context.getString(R.string.uninstall_text),
|
||||
text = if (isCloneApp(app)) context.getString(R.string.delete) else
|
||||
context.getString(R.string.uninstall_text),
|
||||
imageVector = Icons.Outlined.Delete,
|
||||
enabled = enabled,
|
||||
) { onUninstallClicked(app) }
|
||||
@@ -89,4 +93,9 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
|
||||
if (appButtonRepository.isUninstallBlockedByAdmin(app)) return
|
||||
packageInfoPresenter.startUninstallActivity()
|
||||
}
|
||||
|
||||
private fun isCloneApp(app: ApplicationInfo): Boolean {
|
||||
val userInfo = userManager.getUserInfo(UserHandle.getUserId(app.uid))
|
||||
return userInfo != null && userInfo.isCloneProfile
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.settings.spa.app.appinfo
|
||||
|
||||
import android.app.settings.SettingsEnums
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
||||
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
|
||||
import com.android.settingslib.spaprivileged.model.app.toRoute
|
||||
import com.android.settingslib.spaprivileged.template.app.AppInfoProvider
|
||||
|
||||
private const val PACKAGE_NAME = "packageName"
|
||||
private const val USER_ID = "userId"
|
||||
|
||||
object CloneAppInfoSettingsProvider : SettingsPageProvider {
|
||||
override val name = "CloneAppInfoSettingsProvider"
|
||||
|
||||
override val parameter = listOf(
|
||||
navArgument(PACKAGE_NAME) { type = NavType.StringType },
|
||||
navArgument(USER_ID) { type = NavType.IntType },
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Page(arguments: Bundle?) {
|
||||
val packageName = arguments!!.getString(PACKAGE_NAME)!!
|
||||
val userId = arguments.getInt(USER_ID)
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val packageInfoPresenter = remember {
|
||||
PackageInfoPresenter(context, packageName, userId, coroutineScope)
|
||||
}
|
||||
CloneAppInfoSettings(packageInfoPresenter)
|
||||
packageInfoPresenter.PackageRemoveDetector()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun navigator(app: ApplicationInfo) = com.android.settingslib.spa.framework.compose.navigator(route = "$name/${app.toRoute()}")
|
||||
|
||||
/**
|
||||
* Gets the route to the App Info Settings page.
|
||||
*
|
||||
* Expose route to enable enter from non-SPA pages.
|
||||
*/
|
||||
fun getRoute(packageName: String, userId: Int): String = "$name/$packageName/$userId"
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CloneAppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
|
||||
val packageInfo = packageInfoPresenter.flow.collectAsState().value ?: return
|
||||
RegularScaffold(
|
||||
title = stringResource(R.string.application_info_label),
|
||||
) {
|
||||
val appInfoProvider = remember { AppInfoProvider(packageInfo) }
|
||||
|
||||
appInfoProvider.AppInfo(isClonedAppPage = true)
|
||||
ClonePageAppButtons(packageInfoPresenter)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.settings.spa.app.appinfo
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Launch
|
||||
import androidx.compose.material.icons.outlined.WarningAmber
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spa.widget.button.ActionButton
|
||||
import com.android.settingslib.spa.widget.button.ActionButtons
|
||||
|
||||
@Composable
|
||||
fun ClonePageAppButtons(packageInfoPresenter: PackageInfoPresenter) {
|
||||
val presenter = remember { CloneAppButtonsPresenter(packageInfoPresenter) }
|
||||
ActionButtons(actionButtons = presenter.getActionButtons())
|
||||
}
|
||||
|
||||
private class CloneAppButtonsPresenter(private val packageInfoPresenter: PackageInfoPresenter) {
|
||||
private val appLaunchButton = FakeAppLaunchButton(packageInfoPresenter)
|
||||
private val appCreateButton = AppCreateButton(packageInfoPresenter)
|
||||
private val appForceStopButton = FakeAppForceStopButton(packageInfoPresenter)
|
||||
|
||||
@OptIn(ExperimentalLifecycleComposeApi::class)
|
||||
@Composable
|
||||
fun getActionButtons() =
|
||||
packageInfoPresenter.flow.collectAsStateWithLifecycle(initialValue = null).value?.let {
|
||||
getActionButtons(it.applicationInfo)
|
||||
} ?: emptyList()
|
||||
|
||||
@Composable
|
||||
private fun getActionButtons(app: ApplicationInfo): List<ActionButton> = listOfNotNull(
|
||||
appLaunchButton.getActionButton(),
|
||||
appCreateButton.getActionButton(app),
|
||||
appForceStopButton.getActionButton(),
|
||||
)
|
||||
}
|
||||
|
||||
class FakeAppForceStopButton(packageInfoPresenter: PackageInfoPresenter) {
|
||||
private val context = packageInfoPresenter.context
|
||||
|
||||
fun getActionButton(): ActionButton {
|
||||
return ActionButton(
|
||||
text = context.getString(R.string.force_stop),
|
||||
imageVector = Icons.Outlined.WarningAmber,
|
||||
enabled = false,
|
||||
) {
|
||||
// Unclickable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FakeAppLaunchButton(packageInfoPresenter: PackageInfoPresenter) {
|
||||
private val context = packageInfoPresenter.context
|
||||
|
||||
@Composable
|
||||
fun getActionButton(): ActionButton {
|
||||
return ActionButton(
|
||||
text = context.getString(R.string.launch_instant_app),
|
||||
imageVector = Icons.Outlined.Launch,
|
||||
enabled = false
|
||||
) {
|
||||
// Unclickable
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user