Merge "AppClone: Changes in AppInfo page for cloned app."
This commit is contained in:
@@ -90,7 +90,7 @@ public class CloneBackend {
|
||||
* @param packageName
|
||||
* @return error/success code
|
||||
*/
|
||||
int installCloneApp(String packageName) {
|
||||
public int installCloneApp(String packageName) {
|
||||
String userName = "cloneUser";
|
||||
UserHandle cloneUserHandle = null;
|
||||
boolean newlyCreated = false;
|
||||
@@ -160,4 +160,8 @@ public class CloneBackend {
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
public int getCloneUserId() {
|
||||
return mCloneUserId;
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.settings.applications.manageapplications;
|
||||
|
||||
import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_SPA;
|
||||
|
||||
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_DRAGGING;
|
||||
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
|
||||
|
||||
@@ -47,6 +49,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageItemInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
@@ -64,6 +67,7 @@ import android.provider.DeviceConfig;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.FeatureFlagUtils;
|
||||
import android.util.IconDrawableFactory;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -148,6 +152,8 @@ import com.android.settings.notification.ConfigureNotificationSettings;
|
||||
import com.android.settings.notification.NotificationBackend;
|
||||
import com.android.settings.notification.app.AppNotificationSettings;
|
||||
import com.android.settings.spa.SpaActivity;
|
||||
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider;
|
||||
import com.android.settings.spa.app.appinfo.CloneAppInfoSettingsProvider;
|
||||
import com.android.settings.widget.LoadingViewController;
|
||||
import com.android.settings.wifi.AppStateChangeWifiStateBridge;
|
||||
import com.android.settings.wifi.ChangeWifiStateDetails;
|
||||
@@ -707,6 +713,20 @@ public class ManageApplications extends InstrumentedFragment
|
||||
startAppInfoFragment(LongBackgroundTasksDetails.class,
|
||||
R.string.long_background_tasks_label);
|
||||
break;
|
||||
case LIST_TYPE_CLONED_APPS:
|
||||
if (!FeatureFlagUtils.isEnabled(getContext(), SETTINGS_ENABLE_SPA)) {
|
||||
return;
|
||||
}
|
||||
int userId = UserHandle.getUserId(mCurrentUid);
|
||||
UserInfo userInfo = mUserManager.getUserInfo(userId);
|
||||
if (userInfo != null && !userInfo.isCloneProfile()) {
|
||||
SpaActivity.startSpaActivity(getContext(), CloneAppInfoSettingsProvider.INSTANCE
|
||||
.getRoute(mCurrentPkgName, userId));
|
||||
} else {
|
||||
SpaActivity.startSpaActivity(getContext(), AppInfoSettingsProvider.INSTANCE
|
||||
.getRoute(mCurrentPkgName, userId));
|
||||
}
|
||||
break;
|
||||
// TODO: Figure out if there is a way where we can spin up the profile's settings
|
||||
// process ahead of time, to avoid a long load of data when user clicks on a managed
|
||||
// app. Maybe when they load the list of apps that contains managed profile apps.
|
||||
|
@@ -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
|
||||
@@ -72,6 +73,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