Merge "AppClone: Changes in AppInfo page for cloned app."
This commit is contained in:
@@ -90,7 +90,7 @@ public class CloneBackend {
|
|||||||
* @param packageName
|
* @param packageName
|
||||||
* @return error/success code
|
* @return error/success code
|
||||||
*/
|
*/
|
||||||
int installCloneApp(String packageName) {
|
public int installCloneApp(String packageName) {
|
||||||
String userName = "cloneUser";
|
String userName = "cloneUser";
|
||||||
UserHandle cloneUserHandle = null;
|
UserHandle cloneUserHandle = null;
|
||||||
boolean newlyCreated = false;
|
boolean newlyCreated = false;
|
||||||
@@ -160,4 +160,8 @@ public class CloneBackend {
|
|||||||
}
|
}
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCloneUserId() {
|
||||||
|
return mCloneUserId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.settings.applications.manageapplications;
|
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_DRAGGING;
|
||||||
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
|
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
|
||||||
|
|
||||||
@@ -47,6 +49,7 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageItemInfo;
|
import android.content.pm.PackageItemInfo;
|
||||||
|
import android.content.pm.UserInfo;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -64,6 +67,7 @@ import android.provider.DeviceConfig;
|
|||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
|
import android.util.FeatureFlagUtils;
|
||||||
import android.util.IconDrawableFactory;
|
import android.util.IconDrawableFactory;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
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.NotificationBackend;
|
||||||
import com.android.settings.notification.app.AppNotificationSettings;
|
import com.android.settings.notification.app.AppNotificationSettings;
|
||||||
import com.android.settings.spa.SpaActivity;
|
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.widget.LoadingViewController;
|
||||||
import com.android.settings.wifi.AppStateChangeWifiStateBridge;
|
import com.android.settings.wifi.AppStateChangeWifiStateBridge;
|
||||||
import com.android.settings.wifi.ChangeWifiStateDetails;
|
import com.android.settings.wifi.ChangeWifiStateDetails;
|
||||||
@@ -707,6 +713,20 @@ public class ManageApplications extends InstrumentedFragment
|
|||||||
startAppInfoFragment(LongBackgroundTasksDetails.class,
|
startAppInfoFragment(LongBackgroundTasksDetails.class,
|
||||||
R.string.long_background_tasks_label);
|
R.string.long_background_tasks_label);
|
||||||
break;
|
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
|
// 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
|
// 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.
|
// 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.AllAppListPageProvider
|
||||||
import com.android.settings.spa.app.AppsMainPageProvider
|
import com.android.settings.spa.app.AppsMainPageProvider
|
||||||
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
|
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.backgroundinstall.BackgroundInstalledAppsPageProvider
|
||||||
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
|
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
|
||||||
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
|
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
|
||||||
@@ -72,6 +73,7 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) {
|
|||||||
AppLanguagesPageProvider,
|
AppLanguagesPageProvider,
|
||||||
UsageStatsPageProvider,
|
UsageStatsPageProvider,
|
||||||
BackgroundInstalledAppsPageProvider,
|
BackgroundInstalledAppsPageProvider,
|
||||||
|
CloneAppInfoSettingsProvider,
|
||||||
) + togglePermissionAppListTemplate.createPageProviders(),
|
) + togglePermissionAppListTemplate.createPageProviders(),
|
||||||
rootPages = listOf(
|
rootPages = listOf(
|
||||||
SettingsPage.create(HomePageProvider.name),
|
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.om.OverlayManager
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.os.UserHandle
|
||||||
|
import android.os.UserManager
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
@@ -30,6 +32,7 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
|
|||||||
private val context = packageInfoPresenter.context
|
private val context = packageInfoPresenter.context
|
||||||
private val appButtonRepository = AppButtonRepository(context)
|
private val appButtonRepository = AppButtonRepository(context)
|
||||||
private val overlayManager = context.getSystemService(OverlayManager::class.java)!!
|
private val overlayManager = context.getSystemService(OverlayManager::class.java)!!
|
||||||
|
private val userManager = context.getSystemService(UserManager::class.java)!!
|
||||||
|
|
||||||
fun getActionButton(app: ApplicationInfo): ActionButton? {
|
fun getActionButton(app: ApplicationInfo): ActionButton? {
|
||||||
if (app.isSystemApp || app.isInstantApp) return null
|
if (app.isSystemApp || app.isInstantApp) return null
|
||||||
@@ -80,7 +83,8 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
|
|||||||
overlayManager.getOverlayInfo(packageName, userHandle)?.isEnabled == true
|
overlayManager.getOverlayInfo(packageName, userHandle)?.isEnabled == true
|
||||||
|
|
||||||
private fun uninstallButton(app: ApplicationInfo, enabled: Boolean) = ActionButton(
|
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,
|
imageVector = Icons.Outlined.Delete,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
) { onUninstallClicked(app) }
|
) { onUninstallClicked(app) }
|
||||||
@@ -89,4 +93,9 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
|
|||||||
if (appButtonRepository.isUninstallBlockedByAdmin(app)) return
|
if (appButtonRepository.isUninstallBlockedByAdmin(app)) return
|
||||||
packageInfoPresenter.startUninstallActivity()
|
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