Snap for 9108299 from 56d1c2fa55 to udc-release
Change-Id: I13157e64e181fba492b2cfd93087f312b29872fa
This commit is contained in:
@@ -4579,6 +4579,13 @@
|
||||
|
||||
<activity android:name=".spa.SpaActivity" android:exported="false"/>
|
||||
<activity android:name=".spa.SpaBridgeActivity" android:exported="false"/>
|
||||
<activity android:name=".spa.SpaDebugActivity" android:exported="false"/>
|
||||
<provider
|
||||
android:name=".spa.SpaEntryProvider"
|
||||
android:authorities="com.android.settings.spa.provider"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</provider>
|
||||
|
||||
<activity-alias android:name="UsageStatsActivity"
|
||||
android:exported="true"
|
||||
@@ -4592,11 +4599,6 @@
|
||||
android:value="UsageStats"/>
|
||||
</activity-alias>
|
||||
|
||||
<activity
|
||||
android:name="com.android.settings.spa.SpaDebugActivity"
|
||||
android:exported="false">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="com.android.settings.applications.mobilebundledapps.MobileBundledAppDetailsActivity"
|
||||
android:label="@string/mobile_bundled_apps_details_title"
|
||||
|
||||
@@ -17,12 +17,28 @@
|
||||
package com.android.settings.applications;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.FeatureFlagUtils;
|
||||
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
|
||||
import com.android.settings.spa.SpaActivity;
|
||||
import com.android.settings.spa.app.appsettings.AppSettingsProvider;
|
||||
|
||||
public class InstalledAppDetailsTop extends SettingsActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
super.onCreate(savedState);
|
||||
if (!FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_ENABLE_SPA)) {
|
||||
return;
|
||||
}
|
||||
String packageName = super.getIntent().getData().getSchemeSpecificPart();
|
||||
SpaActivity.startSpaActivity(
|
||||
this, AppSettingsProvider.INSTANCE.getRoute(packageName, getUserId()));
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getIntent() {
|
||||
Intent modIntent = new Intent(super.getIntent());
|
||||
|
||||
@@ -171,6 +171,56 @@ public final class DataProcessor {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link BatteryUsageStats} from system service.
|
||||
*/
|
||||
@Nullable
|
||||
public static BatteryUsageStats getBatteryUsageStats(final Context context) {
|
||||
final BatteryUsageStatsQuery batteryUsageStatsQuery =
|
||||
new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build();
|
||||
return context.getSystemService(BatteryStatsManager.class)
|
||||
.getBatteryUsageStats(batteryUsageStatsQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the {@link BatteryUsageStats} after using it.
|
||||
*/
|
||||
public static void closeBatteryUsageStats(BatteryUsageStats batteryUsageStats) {
|
||||
if (batteryUsageStats != null) {
|
||||
try {
|
||||
batteryUsageStats.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "BatteryUsageStats.close() failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the list of {@link BatteryEntry} from the supplied {@link BatteryUsageStats}.
|
||||
*/
|
||||
@Nullable
|
||||
public static List<BatteryEntry> generateBatteryEntryListFromBatteryUsageStats(
|
||||
final Context context,
|
||||
@Nullable final BatteryUsageStats batteryUsageStats,
|
||||
@Nullable BatteryAppListPreferenceController batteryAppListPreferenceController) {
|
||||
if (batteryUsageStats == null) {
|
||||
Log.w(TAG, "batteryUsageStats is null content");
|
||||
return null;
|
||||
}
|
||||
// Loads the battery consuming data.
|
||||
final BatteryAppListPreferenceController controller =
|
||||
batteryAppListPreferenceController == null
|
||||
? new BatteryAppListPreferenceController(
|
||||
context,
|
||||
/*preferenceKey=*/ null,
|
||||
/*lifecycle=*/ null,
|
||||
/*activity*=*/ null,
|
||||
/*fragment=*/ null)
|
||||
: batteryAppListPreferenceController;
|
||||
|
||||
return controller.getBatteryEntryList(batteryUsageStats, /*showAllApps=*/ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the processed history map which has interpolated to every hour data.
|
||||
* The start and end timestamp must be the even hours.
|
||||
@@ -477,20 +527,14 @@ public final class DataProcessor {
|
||||
final Context context) {
|
||||
BatteryDiffData batteryDiffData = null;
|
||||
try {
|
||||
final BatteryUsageStatsQuery batteryUsageStatsQuery =
|
||||
new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build();
|
||||
final BatteryUsageStats batteryUsageStats =
|
||||
context.getSystemService(BatteryStatsManager.class)
|
||||
.getBatteryUsageStats(batteryUsageStatsQuery);
|
||||
|
||||
if (batteryUsageStats == null) {
|
||||
Log.w(TAG, "batteryUsageStats is null content");
|
||||
return null;
|
||||
}
|
||||
|
||||
final BatteryUsageStats batteryUsageStats = getBatteryUsageStats(context);
|
||||
final List<BatteryEntry> batteryEntryList =
|
||||
generateBatteryEntryListFromBatteryUsageStats(context, batteryUsageStats);
|
||||
generateBatteryEntryListFromBatteryUsageStats(
|
||||
context,
|
||||
batteryUsageStats,
|
||||
/*batteryAppListPreferenceController=*/ null);
|
||||
batteryDiffData = generateBatteryDiffData(context, batteryEntryList, batteryUsageStats);
|
||||
closeBatteryUsageStats(batteryUsageStats);
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "load batteryUsageStats:" + e);
|
||||
}
|
||||
@@ -498,20 +542,6 @@ public final class DataProcessor {
|
||||
return batteryDiffData;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static List<BatteryEntry> generateBatteryEntryListFromBatteryUsageStats(
|
||||
final Context context, final BatteryUsageStats batteryUsageStats) {
|
||||
// Loads the battery consuming data.
|
||||
final BatteryAppListPreferenceController controller =
|
||||
new BatteryAppListPreferenceController(
|
||||
context,
|
||||
/*preferenceKey=*/ null,
|
||||
/*lifecycle=*/ null,
|
||||
/*activity*=*/ null,
|
||||
/*fragment=*/ null);
|
||||
return controller.getBatteryEntryList(batteryUsageStats, /*showAllApps=*/ true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static List<BatteryHistEntry> convertToBatteryHistEntry(
|
||||
@Nullable final List<BatteryEntry> batteryEntryList,
|
||||
|
||||
@@ -18,5 +18,8 @@ package com.android.settings.spa
|
||||
|
||||
import com.android.settingslib.spa.framework.DebugActivity
|
||||
|
||||
class SpaDebugActivity :
|
||||
DebugActivity(SpaEnvironment.settingsEntryRepository, SpaActivity::class.java)
|
||||
class SpaDebugActivity : DebugActivity(
|
||||
SpaEnvironment.settingsEntryRepository,
|
||||
browseActivityClass = SpaActivity::class.java,
|
||||
entryProviderAuthorities = "com.android.settings.spa.provider",
|
||||
)
|
||||
|
||||
24
src/com/android/settings/spa/SpaEntryProvider.kt
Normal file
24
src/com/android/settings/spa/SpaEntryProvider.kt
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.android.settingslib.spa.framework.EntryProvider
|
||||
|
||||
class SpaEntryProvider : EntryProvider(
|
||||
SpaEnvironment.settingsEntryRepository,
|
||||
browseActivityClass = SpaActivity::class.java,
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.appsettings
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.os.UserManager
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal
|
||||
import com.android.settingslib.Utils
|
||||
import com.android.settingslib.spaprivileged.model.app.userId
|
||||
|
||||
class AppButtonRepository(private val context: Context) {
|
||||
private val packageManager = context.packageManager
|
||||
private val devicePolicyManager = context.getSystemService(DevicePolicyManager::class.java)!!
|
||||
|
||||
/**
|
||||
* Checks whether the given application is disallowed from modifying.
|
||||
*/
|
||||
fun isDisallowControl(app: ApplicationInfo): Boolean = when {
|
||||
// Not allow to control the device provisioning package.
|
||||
Utils.isDeviceProvisioningPackage(context.resources, app.packageName) -> true
|
||||
|
||||
// If the uninstallation intent is already queued, disable the button.
|
||||
devicePolicyManager.isUninstallInQueue(app.packageName) -> true
|
||||
|
||||
RestrictedLockUtilsInternal.hasBaseUserRestriction(
|
||||
context, UserManager.DISALLOW_APPS_CONTROL, app.userId
|
||||
) -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given application is an active admin.
|
||||
*/
|
||||
fun isActiveAdmin(app: ApplicationInfo): Boolean =
|
||||
devicePolicyManager.packageHasActiveAdmins(app.packageName, app.userId)
|
||||
|
||||
fun getHomePackageInfo(): AppUninstallButton.HomePackages {
|
||||
val homePackages = mutableSetOf<String>()
|
||||
val homeActivities = ArrayList<ResolveInfo>()
|
||||
val currentDefaultHome = packageManager.getHomeActivities(homeActivities)
|
||||
homeActivities.map { it.activityInfo }.forEach {
|
||||
homePackages.add(it.packageName)
|
||||
// Also make sure to include anything proxying for the home app
|
||||
val metaPackageName = it.metaData?.getString(ActivityManager.META_HOME_ALTERNATE)
|
||||
if (metaPackageName != null && signaturesMatch(metaPackageName, it.packageName)) {
|
||||
homePackages.add(metaPackageName)
|
||||
}
|
||||
}
|
||||
return AppUninstallButton.HomePackages(homePackages, currentDefaultHome)
|
||||
}
|
||||
|
||||
private fun signaturesMatch(packageName1: String, packageName2: String): Boolean = try {
|
||||
packageManager.checkSignatures(packageName1, packageName2) >= PackageManager.SIGNATURE_MATCH
|
||||
} catch (e: Exception) {
|
||||
// e.g. named alternate package not found during lookup; this is an expected case sometimes
|
||||
false
|
||||
}
|
||||
}
|
||||
59
src/com/android/settings/spa/app/appsettings/AppButtons.kt
Normal file
59
src/com/android/settings/spa/app/appsettings/AppButtons.kt
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.appsettings
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import com.android.settingslib.spa.widget.button.ActionButton
|
||||
import com.android.settingslib.spa.widget.button.ActionButtons
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Composable
|
||||
fun AppButtons(packageInfoPresenter: PackageInfoPresenter) {
|
||||
val appButtonsHolder = remember { AppButtonsHolder(packageInfoPresenter) }
|
||||
appButtonsHolder.Dialogs()
|
||||
ActionButtons(actionButtons = appButtonsHolder.rememberActionsButtons().value)
|
||||
}
|
||||
|
||||
private class AppButtonsHolder(private val packageInfoPresenter: PackageInfoPresenter) {
|
||||
private val appLaunchButton = AppLaunchButton(context = packageInfoPresenter.context)
|
||||
private val appDisableButton = AppDisableButton(packageInfoPresenter)
|
||||
private val appUninstallButton = AppUninstallButton(packageInfoPresenter)
|
||||
private val appForceStopButton = AppForceStopButton(packageInfoPresenter)
|
||||
|
||||
@Composable
|
||||
fun rememberActionsButtons() = remember {
|
||||
packageInfoPresenter.flow.map { packageInfo ->
|
||||
if (packageInfo != null) getActionButtons(packageInfo) else emptyList()
|
||||
}
|
||||
}.collectAsState(initial = emptyList())
|
||||
|
||||
private fun getActionButtons(packageInfo: PackageInfo): List<ActionButton> = listOfNotNull(
|
||||
appLaunchButton.getActionButton(packageInfo),
|
||||
appDisableButton.getActionButton(packageInfo),
|
||||
appUninstallButton.getActionButton(packageInfo),
|
||||
appForceStopButton.getActionButton(packageInfo),
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun Dialogs() {
|
||||
appDisableButton.DisableConfirmDialog()
|
||||
appForceStopButton.ForceStopConfirmDialog()
|
||||
}
|
||||
}
|
||||
139
src/com/android/settings/spa/app/appsettings/AppDisableButton.kt
Normal file
139
src/com/android/settings/spa/app/appsettings/AppDisableButton.kt
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.appsettings
|
||||
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.UserManager
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ArrowCircleDown
|
||||
import androidx.compose.material.icons.outlined.HideSource
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.android.settings.R
|
||||
import com.android.settings.Utils
|
||||
import com.android.settings.overlay.FeatureFactory
|
||||
import com.android.settingslib.Utils as SettingsLibUtils
|
||||
import com.android.settingslib.spa.widget.button.ActionButton
|
||||
import com.android.settingslib.spaprivileged.model.app.hasFlag
|
||||
import com.android.settingslib.spaprivileged.model.app.isDisabledUntilUsed
|
||||
|
||||
class AppDisableButton(
|
||||
private val packageInfoPresenter: PackageInfoPresenter,
|
||||
) {
|
||||
private val context = packageInfoPresenter.context
|
||||
private val appButtonRepository = AppButtonRepository(context)
|
||||
private val resources = context.resources
|
||||
private val packageManager = context.packageManager
|
||||
private val userManager = context.getSystemService(UserManager::class.java)!!
|
||||
private val devicePolicyManager = context.getSystemService(DevicePolicyManager::class.java)!!
|
||||
private val applicationFeatureProvider =
|
||||
FeatureFactory.getFactory(context).getApplicationFeatureProvider(context)
|
||||
|
||||
private var openConfirmDialog by mutableStateOf(false)
|
||||
|
||||
fun getActionButton(packageInfo: PackageInfo): ActionButton? {
|
||||
val app = packageInfo.applicationInfo
|
||||
if (!app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) return null
|
||||
|
||||
return when {
|
||||
app.enabled && !app.isDisabledUntilUsed() -> {
|
||||
disableButton(enabled = isDisableButtonEnabled(packageInfo))
|
||||
}
|
||||
|
||||
else -> enableButton()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether a package can be disabled.
|
||||
*/
|
||||
private fun isDisableButtonEnabled(packageInfo: PackageInfo): Boolean {
|
||||
val packageName = packageInfo.packageName
|
||||
val app = packageInfo.applicationInfo
|
||||
return when {
|
||||
packageName in applicationFeatureProvider.keepEnabledPackages -> false
|
||||
|
||||
// Home launcher apps need special handling. In system ones we don't risk downgrading
|
||||
// because that can interfere with home-key resolution.
|
||||
packageName in appButtonRepository.getHomePackageInfo().homePackages -> false
|
||||
|
||||
// Try to prevent the user from bricking their phone by not allowing disabling of apps
|
||||
// signed with the system certificate.
|
||||
SettingsLibUtils.isSystemPackage(resources, packageManager, packageInfo) -> false
|
||||
|
||||
// If this is a device admin, it can't be disabled.
|
||||
appButtonRepository.isActiveAdmin(app) -> false
|
||||
|
||||
// We don't allow disabling DO/PO on *any* users if it's a system app, because
|
||||
// "disabling" is actually "downgrade to the system version + disable", and "downgrade"
|
||||
// will clear data on all users.
|
||||
Utils.isProfileOrDeviceOwner(userManager, devicePolicyManager, packageName) -> false
|
||||
|
||||
appButtonRepository.isDisallowControl(app) -> false
|
||||
|
||||
// system/vendor resource overlays can never be disabled.
|
||||
app.isResourceOverlay -> false
|
||||
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableButton(enabled: Boolean) = ActionButton(
|
||||
text = context.getString(R.string.disable_text),
|
||||
imageVector = Icons.Outlined.HideSource,
|
||||
enabled = enabled,
|
||||
) { openConfirmDialog = true }
|
||||
|
||||
private fun enableButton() = ActionButton(
|
||||
text = context.getString(R.string.enable_text),
|
||||
imageVector = Icons.Outlined.ArrowCircleDown,
|
||||
) { packageInfoPresenter.enable() }
|
||||
|
||||
@Composable
|
||||
fun DisableConfirmDialog() {
|
||||
if (!openConfirmDialog) return
|
||||
AlertDialog(
|
||||
onDismissRequest = { openConfirmDialog = false },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
openConfirmDialog = false
|
||||
packageInfoPresenter.disable()
|
||||
},
|
||||
) {
|
||||
Text(stringResource(R.string.app_disable_dlg_positive))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { openConfirmDialog = false }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(R.string.app_disable_dlg_text))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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.appsettings
|
||||
|
||||
import android.app.settings.SettingsEnums
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.UserManager
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.WarningAmber
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.RestrictedLockUtils
|
||||
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal
|
||||
import com.android.settingslib.spa.widget.button.ActionButton
|
||||
import com.android.settingslib.spaprivileged.model.app.hasFlag
|
||||
import com.android.settingslib.spaprivileged.model.app.userId
|
||||
|
||||
class AppForceStopButton(
|
||||
private val packageInfoPresenter: PackageInfoPresenter,
|
||||
) {
|
||||
private val context = packageInfoPresenter.context
|
||||
private val appButtonRepository = AppButtonRepository(context)
|
||||
private val packageManager = context.packageManager
|
||||
|
||||
private var openConfirmDialog by mutableStateOf(false)
|
||||
|
||||
fun getActionButton(packageInfo: PackageInfo): ActionButton {
|
||||
val app = packageInfo.applicationInfo
|
||||
return ActionButton(
|
||||
text = context.getString(R.string.force_stop),
|
||||
imageVector = Icons.Outlined.WarningAmber,
|
||||
enabled = isForceStopButtonEnable(app),
|
||||
) { onForceStopButtonClicked(app) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether a package can be force stopped.
|
||||
*/
|
||||
private fun isForceStopButtonEnable(app: ApplicationInfo): Boolean = when {
|
||||
// User can't force stop device admin.
|
||||
appButtonRepository.isActiveAdmin(app) -> false
|
||||
|
||||
appButtonRepository.isDisallowControl(app) -> false
|
||||
|
||||
// If the app isn't explicitly stopped, then always show the force stop button.
|
||||
else -> !app.hasFlag(ApplicationInfo.FLAG_STOPPED)
|
||||
}
|
||||
|
||||
private fun onForceStopButtonClicked(app: ApplicationInfo) {
|
||||
packageInfoPresenter.logAction(SettingsEnums.ACTION_APP_INFO_FORCE_STOP)
|
||||
getAdminRestriction(app)?.let { admin ->
|
||||
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, admin)
|
||||
return
|
||||
}
|
||||
openConfirmDialog = true
|
||||
}
|
||||
|
||||
private fun getAdminRestriction(app: ApplicationInfo): EnforcedAdmin? = when {
|
||||
packageManager.isPackageStateProtected(app.packageName, app.userId) -> {
|
||||
RestrictedLockUtilsInternal.getDeviceOwner(context)
|
||||
}
|
||||
|
||||
else -> RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
|
||||
context, UserManager.DISALLOW_APPS_CONTROL, app.userId
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ForceStopConfirmDialog() {
|
||||
if (!openConfirmDialog) return
|
||||
AlertDialog(
|
||||
onDismissRequest = { openConfirmDialog = false },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
openConfirmDialog = false
|
||||
packageInfoPresenter.forceStop()
|
||||
},
|
||||
) {
|
||||
Text(stringResource(R.string.okay))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { openConfirmDialog = false }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(stringResource(R.string.force_stop_dlg_title))
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(R.string.force_stop_dlg_text))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.appsettings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Launch
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spa.widget.button.ActionButton
|
||||
import com.android.settingslib.spaprivileged.model.app.userHandle
|
||||
|
||||
class AppLaunchButton(private val context: Context) {
|
||||
private val packageManager = context.packageManager
|
||||
|
||||
fun getActionButton(packageInfo: PackageInfo): ActionButton? =
|
||||
packageManager.getLaunchIntentForPackage(packageInfo.packageName)?.let { intent ->
|
||||
launchButton(intent, packageInfo.applicationInfo)
|
||||
}
|
||||
|
||||
private fun launchButton(intent: Intent, app: ApplicationInfo) = ActionButton(
|
||||
text = context.getString(R.string.launch_instant_app),
|
||||
imageVector = Icons.Outlined.Launch,
|
||||
) {
|
||||
context.startActivityAsUser(intent, app.userHandle)
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,12 @@
|
||||
package com.android.settings.spa.app.appsettings
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
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
|
||||
@@ -34,7 +36,6 @@ import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
||||
import com.android.settingslib.spa.framework.compose.navigator
|
||||
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
|
||||
import com.android.settingslib.spa.widget.ui.Category
|
||||
import com.android.settingslib.spaprivileged.model.app.PackageManagers
|
||||
import com.android.settingslib.spaprivileged.model.app.toRoute
|
||||
import com.android.settingslib.spaprivileged.template.app.AppInfoProvider
|
||||
|
||||
@@ -53,22 +54,36 @@ object AppSettingsProvider : SettingsPageProvider {
|
||||
override fun Page(arguments: Bundle?) {
|
||||
val packageName = arguments!!.getString(PACKAGE_NAME)!!
|
||||
val userId = arguments.getInt(USER_ID)
|
||||
remember { PackageManagers.getPackageInfoAsUser(packageName, userId) }?.let {
|
||||
AppSettings(it)
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val packageInfoPresenter = remember {
|
||||
PackageInfoPresenter(context, packageName, userId, coroutineScope)
|
||||
}
|
||||
AppSettings(packageInfoPresenter)
|
||||
packageInfoPresenter.PageCloser()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun navigator(app: ApplicationInfo) = navigator(route = "$name/${app.toRoute()}")
|
||||
|
||||
/**
|
||||
* Gets the route to the App Settings page.
|
||||
*
|
||||
* Expose route to enable enter from non-SPA pages.
|
||||
*/
|
||||
fun getRoute(packageName: String, userId: Int): String = "$name/$packageName/$userId"
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppSettings(packageInfo: PackageInfo) {
|
||||
private fun AppSettings(packageInfoPresenter: PackageInfoPresenter) {
|
||||
val packageInfo = packageInfoPresenter.flow.collectAsState().value ?: return
|
||||
RegularScaffold(title = stringResource(R.string.application_info_label)) {
|
||||
val appInfoProvider = remember { AppInfoProvider(packageInfo) }
|
||||
|
||||
appInfoProvider.AppInfo()
|
||||
|
||||
AppButtons(packageInfoPresenter)
|
||||
|
||||
Category(title = stringResource(R.string.advanced_apps)) {
|
||||
val app = packageInfo.applicationInfo
|
||||
DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.appsettings
|
||||
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.app.settings.SettingsEnums
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.om.OverlayManager
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.os.UserManager
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import com.android.settings.R
|
||||
import com.android.settings.Utils
|
||||
import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminAdd
|
||||
import com.android.settingslib.RestrictedLockUtils
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal
|
||||
import com.android.settingslib.spa.widget.button.ActionButton
|
||||
import com.android.settingslib.spaprivileged.model.app.hasFlag
|
||||
import com.android.settingslib.spaprivileged.model.app.userHandle
|
||||
import com.android.settingslib.spaprivileged.model.app.userId
|
||||
|
||||
class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter) {
|
||||
private val context = packageInfoPresenter.context
|
||||
private val appButtonRepository = AppButtonRepository(context)
|
||||
private val userManager = context.getSystemService(UserManager::class.java)!!
|
||||
private val overlayManager = context.getSystemService(OverlayManager::class.java)!!
|
||||
private val devicePolicyManager = context.getSystemService(DevicePolicyManager::class.java)!!
|
||||
|
||||
fun getActionButton(packageInfo: PackageInfo): ActionButton? {
|
||||
val app = packageInfo.applicationInfo
|
||||
if (app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) return null
|
||||
return uninstallButton(app = app, enabled = isUninstallButtonEnabled(app))
|
||||
}
|
||||
|
||||
/** Gets whether a package can be uninstalled. */
|
||||
private fun isUninstallButtonEnabled(app: ApplicationInfo): Boolean = when {
|
||||
// When we have multiple users, there is a separate menu to uninstall for all users.
|
||||
!app.hasFlag(ApplicationInfo.FLAG_INSTALLED) && userManager.users.size >= 2 -> false
|
||||
|
||||
// Not allow to uninstall DO/PO.
|
||||
Utils.isProfileOrDeviceOwner(devicePolicyManager, app.packageName, app.userId) -> false
|
||||
|
||||
appButtonRepository.isDisallowControl(app) -> false
|
||||
|
||||
uninstallDisallowedDueToHomeApp(app.packageName) -> false
|
||||
|
||||
// Resource overlays can be uninstalled iff they are public (installed on /data) and
|
||||
// disabled. ("Enabled" means they are in use by resource management.)
|
||||
app.isEnabledResourceOverlay() -> false
|
||||
|
||||
else -> true
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package cannot be uninstalled due to home app restrictions.
|
||||
*
|
||||
* Home launcher apps need special handling, we can't allow uninstallation of the only home
|
||||
* app, and we don't want to allow uninstallation of an explicitly preferred one -- the user
|
||||
* can go to Home settings and pick a different one, after which we'll permit uninstallation
|
||||
* of the now-not-default one.
|
||||
*/
|
||||
private fun uninstallDisallowedDueToHomeApp(packageName: String): Boolean {
|
||||
val homePackageInfo = appButtonRepository.getHomePackageInfo()
|
||||
return when {
|
||||
packageName !in homePackageInfo.homePackages -> false
|
||||
|
||||
// Disallow uninstall when this is the only home app.
|
||||
homePackageInfo.homePackages.size == 1 -> true
|
||||
|
||||
// Disallow if this is the explicit default home app.
|
||||
else -> packageName == homePackageInfo.currentDefaultHome?.packageName
|
||||
}
|
||||
}
|
||||
|
||||
private fun ApplicationInfo.isEnabledResourceOverlay(): Boolean =
|
||||
isResourceOverlay &&
|
||||
overlayManager.getOverlayInfo(packageName, userHandle)?.isEnabled == true
|
||||
|
||||
private fun uninstallButton(app: ApplicationInfo, enabled: Boolean) = ActionButton(
|
||||
text = context.getString(R.string.uninstall_text),
|
||||
imageVector = Icons.Outlined.Delete,
|
||||
enabled = enabled,
|
||||
) { onUninstallClicked(app) }
|
||||
|
||||
private fun onUninstallClicked(app: ApplicationInfo) {
|
||||
if (appButtonRepository.isActiveAdmin(app)) {
|
||||
packageInfoPresenter.logAction(SettingsEnums.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN)
|
||||
val intent = Intent(context, DeviceAdminAdd::class.java).apply {
|
||||
putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME, app.packageName)
|
||||
}
|
||||
context.startActivityAsUser(intent, app.userHandle)
|
||||
return
|
||||
}
|
||||
RestrictedLockUtilsInternal.checkIfUninstallBlocked(
|
||||
context, app.packageName, app.userId
|
||||
)?.let { admin ->
|
||||
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, admin)
|
||||
return
|
||||
}
|
||||
startUninstallActivity(app)
|
||||
}
|
||||
|
||||
data class HomePackages(
|
||||
val homePackages: Set<String>,
|
||||
val currentDefaultHome: ComponentName?,
|
||||
)
|
||||
|
||||
private fun startUninstallActivity(app: ApplicationInfo) {
|
||||
val packageUri = Uri.parse("package:${app.packageName}")
|
||||
packageInfoPresenter.logAction(SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP)
|
||||
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri).apply {
|
||||
putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, !app.hasFlag(ApplicationInfo.FLAG_INSTALLED))
|
||||
}
|
||||
context.startActivityAsUser(intent, app.userHandle)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.appsettings
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.settings.SettingsEnums
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.UserHandle
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.android.settings.overlay.FeatureFactory
|
||||
import com.android.settingslib.spa.framework.compose.DisposableBroadcastReceiverAsUser
|
||||
import com.android.settingslib.spa.framework.compose.LocalNavController
|
||||
import com.android.settingslib.spaprivileged.model.app.PackageManagers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val TAG = "PackageInfoPresenter"
|
||||
|
||||
/**
|
||||
* Presenter which helps to present the status change of [PackageInfo].
|
||||
*/
|
||||
class PackageInfoPresenter(
|
||||
val context: Context,
|
||||
val packageName: String,
|
||||
val userId: Int,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
) {
|
||||
private val metricsFeatureProvider = FeatureFactory.getFactory(context).metricsFeatureProvider
|
||||
private val packageManager by lazy {
|
||||
context.createContextAsUser(UserHandle.of(userId), 0).packageManager
|
||||
}
|
||||
private val _flow: MutableStateFlow<PackageInfo?> = MutableStateFlow(null)
|
||||
|
||||
val flow: StateFlow<PackageInfo?> = _flow
|
||||
|
||||
init {
|
||||
notifyChange()
|
||||
}
|
||||
|
||||
private fun notifyChange() {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
_flow.value = getPackageInfo()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the page when the package is uninstalled.
|
||||
*/
|
||||
@Composable
|
||||
fun PageCloser() {
|
||||
val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_REMOVED).apply {
|
||||
addDataScheme("package")
|
||||
}
|
||||
val navController = LocalNavController.current
|
||||
DisposableBroadcastReceiverAsUser(userId, intentFilter) { intent ->
|
||||
if (packageName == intent.data?.schemeSpecificPart) {
|
||||
navController.navigateBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Enables this package. */
|
||||
fun enable() {
|
||||
logAction(SettingsEnums.ACTION_SETTINGS_ENABLE_APP)
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
packageManager.setApplicationEnabledSetting(
|
||||
packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0
|
||||
)
|
||||
notifyChange()
|
||||
}
|
||||
}
|
||||
|
||||
/** Disables this package. */
|
||||
fun disable() {
|
||||
logAction(SettingsEnums.ACTION_SETTINGS_DISABLE_APP)
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
packageManager.setApplicationEnabledSetting(
|
||||
packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0
|
||||
)
|
||||
notifyChange()
|
||||
}
|
||||
}
|
||||
|
||||
/** Force stops this package. */
|
||||
fun forceStop() {
|
||||
logAction(SettingsEnums.ACTION_APP_FORCE_STOP)
|
||||
coroutineScope.launch(Dispatchers.Default) {
|
||||
val activityManager = context.getSystemService(ActivityManager::class.java)!!
|
||||
Log.d(TAG, "Stopping package $packageName")
|
||||
activityManager.forceStopPackageAsUser(packageName, userId)
|
||||
notifyChange()
|
||||
}
|
||||
}
|
||||
|
||||
fun logAction(category: Int) {
|
||||
metricsFeatureProvider.action(context, category, packageName)
|
||||
}
|
||||
|
||||
private fun getPackageInfo() =
|
||||
PackageManagers.getPackageInfoAsUser(
|
||||
packageName = packageName,
|
||||
flags = PackageManager.MATCH_DISABLED_COMPONENTS or
|
||||
PackageManager.GET_SIGNATURES or
|
||||
PackageManager.GET_PERMISSIONS,
|
||||
userId = userId,
|
||||
)
|
||||
}
|
||||
@@ -1196,11 +1196,19 @@ public class UserSettings extends SettingsPreferenceFragment
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
final List<UserInfo> users = mUserManager.getAliveUsers()
|
||||
// Only users that can be switched to should show up here.
|
||||
// e.g. Managed profiles appear under Accounts Settings instead
|
||||
.stream().filter(UserInfo::supportsSwitchToByUser)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<UserInfo> users;
|
||||
if (mUserCaps.mUserSwitcherEnabled) {
|
||||
// Only users that can be switched to should show up here.
|
||||
// e.g. Managed profiles appear under Accounts Settings instead
|
||||
users = mUserManager.getAliveUsers().stream()
|
||||
.filter(UserInfo::supportsSwitchToByUser)
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
// Only current user will be displayed in case of multi-user switch is disabled
|
||||
users = List.of(mUserManager.getUserInfo(context.getUserId()));
|
||||
}
|
||||
|
||||
final ArrayList<Integer> missingIcons = new ArrayList<>();
|
||||
final ArrayList<UserPreference> userPreferences = new ArrayList<>();
|
||||
|
||||
@@ -1276,7 +1284,6 @@ public class UserSettings extends SettingsPreferenceFragment
|
||||
userPreferences.add(pref);
|
||||
}
|
||||
|
||||
|
||||
// Sort list of users by serialNum
|
||||
Collections.sort(userPreferences, UserPreference.SERIAL_NUMBER_COMPARATOR);
|
||||
|
||||
@@ -1299,7 +1306,6 @@ public class UserSettings extends SettingsPreferenceFragment
|
||||
// Remove everything from mUserListCategory and add new users.
|
||||
mUserListCategory.removeAll();
|
||||
|
||||
// If multi-user is disabled, just show top info and return.
|
||||
final Preference addUserOnLockScreen = getPreferenceScreen().findPreference(
|
||||
mAddUserWhenLockedPreferenceController.getPreferenceKey());
|
||||
mAddUserWhenLockedPreferenceController.updateState(addUserOnLockScreen);
|
||||
@@ -1311,16 +1317,11 @@ public class UserSettings extends SettingsPreferenceFragment
|
||||
final Preference multiUserTopIntroPreference = getPreferenceScreen().findPreference(
|
||||
mMultiUserTopIntroPreferenceController.getPreferenceKey());
|
||||
mMultiUserTopIntroPreferenceController.updateState(multiUserTopIntroPreference);
|
||||
mUserListCategory.setVisible(mUserCaps.mUserSwitcherEnabled);
|
||||
updateGuestPreferences();
|
||||
updateGuestCategory(context, users);
|
||||
updateAddUser(context);
|
||||
updateAddSupervisedUser(context);
|
||||
|
||||
if (!mUserCaps.mUserSwitcherEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (UserPreference userPreference : userPreferences) {
|
||||
userPreference.setOrder(Preference.DEFAULT_ORDER);
|
||||
mUserListCategory.addPreference(userPreference);
|
||||
|
||||
@@ -579,9 +579,13 @@ public class DataProcessorTest {
|
||||
ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*consumePercentage=*/ 25.0,
|
||||
/*foregroundUsageTimeInMs=*/ 50, /*backgroundUsageTimeInMs=*/ 60);
|
||||
verify(mMetricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, 3);
|
||||
.action(mContext.getApplicationContext(),
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT,
|
||||
3);
|
||||
verify(mMetricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, 0);
|
||||
.action(mContext.getApplicationContext(),
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT,
|
||||
0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -674,9 +678,13 @@ public class DataProcessorTest {
|
||||
assertThat(resultMap.get(0).get(0)).isNotNull();
|
||||
assertThat(resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL)).isNotNull();
|
||||
verify(mMetricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, 2);
|
||||
.action(mContext.getApplicationContext(),
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT,
|
||||
2);
|
||||
verify(mMetricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, 0);
|
||||
.action(mContext.getApplicationContext(),
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT,
|
||||
0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -739,9 +747,13 @@ public class DataProcessorTest {
|
||||
assertThat(resultMap.get(0).get(0)).isNotNull();
|
||||
assertThat(resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL)).isNotNull();
|
||||
verify(mMetricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, 1);
|
||||
.action(mContext.getApplicationContext(),
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT,
|
||||
1);
|
||||
verify(mMetricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, 0);
|
||||
.action(mContext.getApplicationContext(),
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT,
|
||||
0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -814,9 +826,13 @@ public class DataProcessorTest {
|
||||
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 50.0,
|
||||
/*foregroundUsageTimeInMs=*/ 10, /*backgroundUsageTimeInMs=*/ 20);
|
||||
verify(mMetricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, 1);
|
||||
.action(mContext.getApplicationContext(),
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT,
|
||||
1);
|
||||
verify(mMetricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, 1);
|
||||
.action(mContext.getApplicationContext(),
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT,
|
||||
1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -889,9 +905,13 @@ public class DataProcessorTest {
|
||||
resultEntry = resultDiffData.getAppDiffEntryList().get(1);
|
||||
assertThat(resultEntry.mBackgroundUsageTimeInMs).isEqualTo(0);
|
||||
verify(mMetricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, 2);
|
||||
.action(mContext.getApplicationContext(),
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT,
|
||||
2);
|
||||
verify(mMetricsFeatureProvider)
|
||||
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, 0);
|
||||
.action(mContext.getApplicationContext(),
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT,
|
||||
0);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -177,6 +177,7 @@ public class UserSettingsTest {
|
||||
doReturn(prefs).when(mMockPreferenceManager).getSharedPreferences();
|
||||
doReturn(mContext).when(mMockPreferenceManager).getContext();
|
||||
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
|
||||
doReturn(ACTIVE_USER_ID).when(mContext).getUserId();
|
||||
|
||||
mFragment.mMePreference = mMePreference;
|
||||
mFragment.mAddUser = mAddUserPreference;
|
||||
@@ -443,6 +444,7 @@ public class UserSettingsTest {
|
||||
|
||||
@Test
|
||||
public void updateUserList_userSwitcherDisabled_shouldNotShowAddUser() {
|
||||
givenUsers(getAdminUser(true));
|
||||
mUserCapabilities.mCanAddUser = true;
|
||||
mUserCapabilities.mUserSwitcherEnabled = false;
|
||||
|
||||
@@ -453,6 +455,7 @@ public class UserSettingsTest {
|
||||
|
||||
@Test
|
||||
public void updateUserList_userSwitcherDisabled_shouldNotShowAddGuest() {
|
||||
givenUsers(getAdminUser(true));
|
||||
mUserCapabilities.mCanAddGuest = true;
|
||||
mUserCapabilities.mUserSwitcherEnabled = false;
|
||||
|
||||
@@ -511,6 +514,47 @@ public class UserSettingsTest {
|
||||
assertThat(userPref.getOnPreferenceClickListener()).isSameInstanceAs(mFragment);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateUserList_existingSecondaryUser_shouldAddOnlyCurrUser_MultiUserIsDisabled() {
|
||||
givenUsers(getAdminUser(true), getSecondaryUser(false));
|
||||
mUserCapabilities.mUserSwitcherEnabled = false;
|
||||
|
||||
mFragment.updateUserList();
|
||||
|
||||
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
|
||||
verify(mFragment.mUserListCategory, times(1))
|
||||
.addPreference(captor.capture());
|
||||
|
||||
List<UserPreference> userPrefs = captor.getAllValues();
|
||||
assertThat(userPrefs.size()).isEqualTo(1);
|
||||
assertThat(userPrefs.get(0)).isSameInstanceAs(mMePreference);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateUserList_existingSecondaryUser_shouldAddSecondaryUser_MultiUserIsEnabled() {
|
||||
givenUsers(getAdminUser(true), getSecondaryUser(false));
|
||||
|
||||
mFragment.updateUserList();
|
||||
|
||||
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
|
||||
verify(mFragment.mUserListCategory, times(2))
|
||||
.addPreference(captor.capture());
|
||||
|
||||
List<UserPreference> userPrefs = captor.getAllValues();
|
||||
UserPreference adminPref = userPrefs.get(0);
|
||||
UserPreference secondaryPref = userPrefs.get(1);
|
||||
|
||||
assertThat(userPrefs.size()).isEqualTo(2);
|
||||
assertThat(adminPref).isSameInstanceAs(mMePreference);
|
||||
assertThat(secondaryPref.getUserId()).isEqualTo(INACTIVE_SECONDARY_USER_ID);
|
||||
assertThat(secondaryPref.getTitle()).isEqualTo(SECONDARY_USER_NAME);
|
||||
assertThat(secondaryPref.getIcon()).isNotNull();
|
||||
assertThat(secondaryPref.getKey()).isEqualTo("id=" + INACTIVE_SECONDARY_USER_ID);
|
||||
assertThat(secondaryPref.isEnabled()).isEqualTo(true);
|
||||
assertThat(secondaryPref.isSelectable()).isEqualTo(true);
|
||||
assertThat(secondaryPref.getOnPreferenceClickListener()).isSameInstanceAs(mFragment);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateUserList_existingRestrictedUser_shouldAddRestrictedUserPreference() {
|
||||
givenUsers(getAdminUser(true), getRestrictedUser(false));
|
||||
@@ -800,6 +844,8 @@ public class UserSettingsTest {
|
||||
List<UserInfo> users = Arrays.asList(userInfo);
|
||||
doReturn(users).when(mUserManager).getUsers();
|
||||
doReturn(users).when(mUserManager).getAliveUsers();
|
||||
users.forEach(user ->
|
||||
doReturn(user).when(mUserManager).getUserInfo(user.id));
|
||||
}
|
||||
|
||||
private static void removeFlag(UserInfo userInfo, int flag) {
|
||||
|
||||
Reference in New Issue
Block a user