diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a23eacaef2e..0be2e2efbac 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -481,7 +481,7 @@
+ android:value="com.android.settings.wifi.savedaccesspoints2.SavedAccessPointsWifiSettings2" />
+ android:textDirection="locale"/>
-
-
Languages
-
- Preferred language order
-
-
- System language
-
Remove
@@ -352,13 +346,13 @@
Preferred Language
- App languages
+ App Languages
Set the language for each app
- App language
+ App Language
Suggested languages
@@ -381,18 +375,6 @@
Only apps that support language selection are shown here.
-
- Your system, apps, and websites use the first supported language from your preferred languages.
-
-
- To select a language for each app, go to app language settings.
-
-
- Learn more about languages
-
-
- https://support.google.com/android?p=per_language_app_settings
-
- Remove selected language?
@@ -408,7 +390,7 @@
Keep at least one preferred language
- Not available as system language
+ May not be available in some apps
Move up
diff --git a/res/xml/languages.xml b/res/xml/languages.xml
deleted file mode 100644
index ed65df099d0..00000000000
--- a/res/xml/languages.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
index 1c12c6a2ed2..c7e74ef4ac3 100644
--- a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
@@ -182,9 +182,9 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
final TextView title = mLayoutPreference.findViewById(R.id.entity_header_title);
title.setText(mCachedDevice.getName());
final TextView summary = mLayoutPreference.findViewById(R.id.entity_header_summary);
- summary.setText(mCachedDevice.getConnectionSummary(true /* shortSummary */));
if (!mCachedDevice.isConnected() || mCachedDevice.isBusy()) {
+ summary.setText(mCachedDevice.getConnectionSummary(true /* shortSummary */));
updateDisconnectLayout();
return;
}
@@ -196,6 +196,9 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
mLayoutPreference.findViewById(R.id.layout_left).setVisibility(View.GONE);
mLayoutPreference.findViewById(R.id.layout_right).setVisibility(View.GONE);
+ summary.setText(mCachedDevice.getConnectionSummary(
+ BluetoothUtils.getIntMetaData(device, BluetoothDevice.METADATA_MAIN_BATTERY)
+ != BluetoothUtils.META_INT_ERROR));
updateSubLayout(mLayoutPreference.findViewById(R.id.layout_middle),
BluetoothDevice.METADATA_MAIN_ICON,
BluetoothDevice.METADATA_MAIN_BATTERY,
@@ -207,6 +210,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)
|| BluetoothUtils.getBooleanMetaData(device,
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+ summary.setText(mCachedDevice.getConnectionSummary(true /* shortSummary */));
updateSubLayout(mLayoutPreference.findViewById(R.id.layout_left),
BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON,
BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
index b3c2e3071af..ab9110d01ab 100644
--- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
+++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
@@ -43,6 +43,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+
class LocaleDragAndDropAdapter
extends RecyclerView.Adapter {
@@ -153,10 +154,8 @@ class LocaleDragAndDropAdapter
final LocaleDragCell dragCell = holder.getLocaleDragCell();
final String label = feedItem.getFullNameNative();
final String description = feedItem.getFullNameInUiLanguage();
-
dragCell.setLabelAndDescription(label, description);
dragCell.setLocalized(feedItem.isTranslated());
- dragCell.setCurrentDefault(feedItem.getLocale().equals(Locale.getDefault()));
dragCell.setMiniLabel(mNumberFormatter.format(i + 1));
dragCell.setShowCheckbox(mRemoveMode);
dragCell.setShowMiniLabel(!mRemoveMode);
diff --git a/src/com/android/settings/localepicker/LocaleDragCell.java b/src/com/android/settings/localepicker/LocaleDragCell.java
index 2f4cfefefb1..ea86189b018 100644
--- a/src/com/android/settings/localepicker/LocaleDragCell.java
+++ b/src/com/android/settings/localepicker/LocaleDragCell.java
@@ -33,7 +33,6 @@ class LocaleDragCell extends RelativeLayout {
private CheckBox mCheckbox;
private TextView mMiniLabel;
private TextView mLocalized;
- private TextView mCurrentDefault;
private ImageView mDragHandle;
public LocaleDragCell(Context context, AttributeSet attrs) {
@@ -45,7 +44,6 @@ class LocaleDragCell extends RelativeLayout {
super.onFinishInflate();
mLabel = (TextView) findViewById(R.id.label);
mLocalized = (TextView) findViewById(R.id.l10nWarn);
- mCurrentDefault = (TextView) findViewById(R.id.default_locale);
mMiniLabel = (TextView) findViewById(R.id.miniLabel);
mCheckbox = (CheckBox) findViewById(R.id.checkbox);
mDragHandle = (ImageView) findViewById(R.id.dragHandle);
@@ -102,14 +100,6 @@ class LocaleDragCell extends RelativeLayout {
invalidate();
}
- /**
- * Indicate current locale is system default.
- */
- public void setCurrentDefault(boolean isCurrentDefault) {
- mCurrentDefault.setVisibility(isCurrentDefault ? VISIBLE : GONE);
- invalidate();
- }
-
public ImageView getDragHandle() {
return mDragHandle;
}
diff --git a/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java b/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java
deleted file mode 100644
index 05c740139cc..00000000000
--- a/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.localepicker;
-
-import android.content.Context;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settingslib.HelpUtils;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.widget.FooterPreference;
-
-/**
- * A controller to update current locale information of application.
- */
-public class LocaleHelperPreferenceController extends AbstractPreferenceController {
- private static final String TAG = LocaleHelperPreferenceController.class.getSimpleName();
-
- private static final String KEY_FOOTER_LANGUAGE_PICKER = "footer_languages_picker";
-
- public LocaleHelperPreferenceController(Context context) {
- super(context);
- }
-
- @Override
- public boolean isAvailable() {
- return true;
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY_FOOTER_LANGUAGE_PICKER;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- FooterPreference footerPreference = screen.findPreference(getPreferenceKey());
- updateFooterPreference(footerPreference);
- }
-
- @VisibleForTesting
- void updateFooterPreference(FooterPreference footerPreference) {
- if (footerPreference != null) {
- footerPreference.setLearnMoreAction(v -> openLocaleLearnMoreLink());
- footerPreference.setLearnMoreText(mContext.getString(
- R.string.desc_locale_helper_footer_general));
- }
- }
-
- private void openLocaleLearnMoreLink() {
- mContext.startActivity(
- HelpUtils.getHelpIntent(
- mContext,
- mContext.getString(R.string.link_locale_picker_footer_learn_more),
- /*backupContext=*/""));
- }
-}
diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java
index 9db3468c9e1..eac2dd1df3c 100644
--- a/src/com/android/settings/localepicker/LocaleListEditor.java
+++ b/src/com/android/settings/localepicker/LocaleListEditor.java
@@ -36,7 +36,6 @@ import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
-import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.app.LocalePicker;
@@ -47,7 +46,6 @@ import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.search.SearchIndexableRaw;
-import com.android.settingslib.widget.LayoutPreference;
import java.util.ArrayList;
import java.util.List;
@@ -66,7 +64,6 @@ public class LocaleListEditor extends RestrictedSettingsFragment {
private static final int REQUEST_LOCALE_PICKER = 0;
private static final String INDEX_KEY_ADD_LANGUAGE = "add_language";
- private static final String KEY_LANGUAGES_PICKER = "languages_picker";
private LocaleDragAndDropAdapter mAdapter;
private Menu mMenu;
@@ -75,9 +72,6 @@ public class LocaleListEditor extends RestrictedSettingsFragment {
private boolean mShowingRemoveDialog;
private boolean mIsUiRestricted;
- private LayoutPreference mLocalePickerPreference;
- private LocaleHelperPreferenceController mLocaleHelperPreferenceController;
-
public LocaleListEditor() {
super(DISALLOW_CONFIG_LOCALE);
}
@@ -92,14 +86,6 @@ public class LocaleListEditor extends RestrictedSettingsFragment {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
- addPreferencesFromResource(R.xml.languages);
- final Activity activity = getActivity();
- activity.setTitle(R.string.language_picker_title);
- mLocaleHelperPreferenceController = new LocaleHelperPreferenceController(activity);
- final PreferenceScreen screen = getPreferenceScreen();
- mLocalePickerPreference = screen.findPreference(KEY_LANGUAGES_PICKER);
- mLocaleHelperPreferenceController.displayPreference(screen);
-
LocaleStore.fillCache(this.getContext());
final List feedsList = getUserLocaleList();
mAdapter = new LocaleDragAndDropAdapter(this.getContext(), feedsList);
@@ -107,8 +93,11 @@ public class LocaleListEditor extends RestrictedSettingsFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstState) {
- configureDragAndDrop(mLocalePickerPreference);
- return super.onCreateView(inflater, container, savedInstState);
+ final View result = super.onCreateView(inflater, container, savedInstState);
+ final View myLayout = inflater.inflate(R.layout.locale_order_list, (ViewGroup) result);
+
+ configureDragAndDrop(myLayout);
+ return result;
}
@Override
@@ -298,8 +287,8 @@ public class LocaleListEditor extends RestrictedSettingsFragment {
return result;
}
- private void configureDragAndDrop(LayoutPreference layout) {
- final RecyclerView list = layout.findViewById(R.id.dragList);
+ private void configureDragAndDrop(View view) {
+ final RecyclerView list = view.findViewById(R.id.dragList);
final LocaleLinearLayoutManager llm = new LocaleLinearLayoutManager(getContext(), mAdapter);
llm.setAutoMeasureEnabled(true);
list.setLayoutManager(llm);
@@ -308,7 +297,7 @@ public class LocaleListEditor extends RestrictedSettingsFragment {
mAdapter.setRecyclerView(list);
list.setAdapter(mAdapter);
- mAddLanguage = layout.findViewById(R.id.add_language);
+ mAddLanguage = view.findViewById(R.id.add_language);
mAddLanguage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
diff --git a/src/com/android/settings/spa/app/appsettings/AppButtonRepository.kt b/src/com/android/settings/spa/app/appsettings/AppButtonRepository.kt
index e1ee766603f..c5e84ae886d 100644
--- a/src/com/android/settings/spa/app/appsettings/AppButtonRepository.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppButtonRepository.kt
@@ -17,19 +17,18 @@
package com.android.settings.spa.app.appsettings
import android.app.ActivityManager
-import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
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
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
+import com.android.settingslib.spaprivileged.model.app.isDisallowControl
class AppButtonRepository(private val context: Context) {
private val packageManager = context.packageManager
- private val devicePolicyManager = context.getSystemService(DevicePolicyManager::class.java)!!
+ private val devicePolicyManager = context.devicePolicyManager
/**
* Checks whether the given application is disallowed from modifying.
@@ -41,20 +40,10 @@ class AppButtonRepository(private val context: Context) {
// 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
+ else -> app.isDisallowControl(context)
}
- /**
- * Checks whether the given application is an active admin.
- */
- fun isActiveAdmin(app: ApplicationInfo): Boolean =
- devicePolicyManager.packageHasActiveAdmins(app.packageName, app.userId)
-
- fun getHomePackageInfo(): AppUninstallButton.HomePackages {
+ fun getHomePackageInfo(): HomePackages {
val homePackages = mutableSetOf()
val homeActivities = ArrayList()
val currentDefaultHome = packageManager.getHomeActivities(homeActivities)
@@ -66,7 +55,7 @@ class AppButtonRepository(private val context: Context) {
homePackages.add(metaPackageName)
}
}
- return AppUninstallButton.HomePackages(homePackages, currentDefaultHome)
+ return HomePackages(homePackages, currentDefaultHome)
}
private fun signaturesMatch(packageName1: String, packageName2: String): Boolean = try {
@@ -75,4 +64,9 @@ class AppButtonRepository(private val context: Context) {
// e.g. named alternate package not found during lookup; this is an expected case sometimes
false
}
+
+ data class HomePackages(
+ val homePackages: Set,
+ val currentDefaultHome: ComponentName?,
+ )
}
diff --git a/src/com/android/settings/spa/app/appsettings/AppButtons.kt b/src/com/android/settings/spa/app/appsettings/AppButtons.kt
index 89f3b138df1..30fcdb77515 100644
--- a/src/com/android/settings/spa/app/appsettings/AppButtons.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppButtons.kt
@@ -32,9 +32,11 @@ fun AppButtons(packageInfoPresenter: PackageInfoPresenter) {
}
private class AppButtonsHolder(private val packageInfoPresenter: PackageInfoPresenter) {
- private val appLaunchButton = AppLaunchButton(context = packageInfoPresenter.context)
+ private val appLaunchButton = AppLaunchButton(packageInfoPresenter)
+ private val appInstallButton = AppInstallButton(packageInfoPresenter)
private val appDisableButton = AppDisableButton(packageInfoPresenter)
private val appUninstallButton = AppUninstallButton(packageInfoPresenter)
+ private val appClearButton = AppClearButton(packageInfoPresenter)
private val appForceStopButton = AppForceStopButton(packageInfoPresenter)
@Composable
@@ -46,14 +48,17 @@ private class AppButtonsHolder(private val packageInfoPresenter: PackageInfoPres
private fun getActionButtons(packageInfo: PackageInfo): List = listOfNotNull(
appLaunchButton.getActionButton(packageInfo),
+ appInstallButton.getActionButton(packageInfo),
appDisableButton.getActionButton(packageInfo),
appUninstallButton.getActionButton(packageInfo),
+ appClearButton.getActionButton(packageInfo),
appForceStopButton.getActionButton(packageInfo),
)
@Composable
fun Dialogs() {
appDisableButton.DisableConfirmDialog()
+ appClearButton.ClearConfirmDialog()
appForceStopButton.ForceStopConfirmDialog()
}
}
diff --git a/src/com/android/settings/spa/app/appsettings/AppClearButton.kt b/src/com/android/settings/spa/app/appsettings/AppClearButton.kt
new file mode 100644
index 00000000000..e16e09168d3
--- /dev/null
+++ b/src/com/android/settings/spa/app/appsettings/AppClearButton.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.material.icons.Icons
+import androidx.compose.material.icons.outlined.Delete
+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.spa.widget.button.ActionButton
+
+class AppClearButton(
+ private val packageInfoPresenter: PackageInfoPresenter,
+) {
+ private val context = packageInfoPresenter.context
+
+ private var openConfirmDialog by mutableStateOf(false)
+
+ fun getActionButton(packageInfo: PackageInfo): ActionButton? {
+ val app = packageInfo.applicationInfo
+ if (!app.isInstantApp) return null
+
+ return clearButton()
+ }
+
+ private fun clearButton() = ActionButton(
+ text = context.getString(R.string.clear_instant_app_data),
+ imageVector = Icons.Outlined.Delete,
+ ) { openConfirmDialog = true }
+
+ @Composable
+ fun ClearConfirmDialog() {
+ if (!openConfirmDialog) return
+ AlertDialog(
+ onDismissRequest = { openConfirmDialog = false },
+ confirmButton = {
+ TextButton(
+ onClick = {
+ openConfirmDialog = false
+ packageInfoPresenter.clearInstantApp()
+ },
+ ) {
+ Text(stringResource(R.string.clear_instant_app_data))
+ }
+ },
+ dismissButton = {
+ TextButton(onClick = { openConfirmDialog = false }) {
+ Text(stringResource(R.string.cancel))
+ }
+ },
+ title = {
+ Text(stringResource(R.string.clear_instant_app_data))
+ },
+ text = {
+ Text(stringResource(R.string.clear_instant_app_confirmation))
+ },
+ )
+ }
+}
diff --git a/src/com/android/settings/spa/app/appsettings/AppDisableButton.kt b/src/com/android/settings/spa/app/appsettings/AppDisableButton.kt
index ff3fbe3c10e..cc5a76e2f99 100644
--- a/src/com/android/settings/spa/app/appsettings/AppDisableButton.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppDisableButton.kt
@@ -16,10 +16,7 @@
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
@@ -34,10 +31,12 @@ 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.framework.common.devicePolicyManager
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.android.settingslib.spaprivileged.model.app.isActiveAdmin
import com.android.settingslib.spaprivileged.model.app.isDisabledUntilUsed
+import com.android.settingslib.Utils as SettingsLibUtils
class AppDisableButton(
private val packageInfoPresenter: PackageInfoPresenter,
@@ -46,8 +45,8 @@ class AppDisableButton(
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 userManager = context.userManager
+ private val devicePolicyManager = context.devicePolicyManager
private val applicationFeatureProvider =
FeatureFactory.getFactory(context).getApplicationFeatureProvider(context)
@@ -55,7 +54,7 @@ class AppDisableButton(
fun getActionButton(packageInfo: PackageInfo): ActionButton? {
val app = packageInfo.applicationInfo
- if (!app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) return null
+ if (!app.isSystemApp) return null
return when {
app.enabled && !app.isDisabledUntilUsed -> {
@@ -84,7 +83,7 @@ class AppDisableButton(
SettingsLibUtils.isSystemPackage(resources, packageManager, packageInfo) -> false
// If this is a device admin, it can't be disabled.
- appButtonRepository.isActiveAdmin(app) -> false
+ app.isActiveAdmin(context) -> 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"
diff --git a/src/com/android/settings/spa/app/appsettings/AppForceStopButton.kt b/src/com/android/settings/spa/app/appsettings/AppForceStopButton.kt
index 49191637677..c34eff027d5 100644
--- a/src/com/android/settings/spa/app/appsettings/AppForceStopButton.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppForceStopButton.kt
@@ -36,6 +36,7 @@ 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.isActiveAdmin
import com.android.settingslib.spaprivileged.model.app.userId
class AppForceStopButton(
@@ -61,7 +62,7 @@ class AppForceStopButton(
*/
private fun isForceStopButtonEnable(app: ApplicationInfo): Boolean = when {
// User can't force stop device admin.
- appButtonRepository.isActiveAdmin(app) -> false
+ app.isActiveAdmin(context) -> false
appButtonRepository.isDisallowControl(app) -> false
diff --git a/src/com/android/settings/spa/app/appsettings/AppInstallButton.kt b/src/com/android/settings/spa/app/appsettings/AppInstallButton.kt
new file mode 100644
index 00000000000..94290e6a2ad
--- /dev/null
+++ b/src/com/android/settings/spa/app/appsettings/AppInstallButton.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.FileDownload
+import com.android.settings.R
+import com.android.settings.applications.AppStoreUtil
+import com.android.settingslib.spa.widget.button.ActionButton
+import com.android.settingslib.spaprivileged.model.app.userHandle
+
+class AppInstallButton(private val packageInfoPresenter: PackageInfoPresenter) {
+ private val context = packageInfoPresenter.context
+
+ fun getActionButton(packageInfo: PackageInfo): ActionButton? {
+ val app = packageInfo.applicationInfo
+ if (!app.isInstantApp) return null
+
+ return AppStoreUtil.getAppStoreLink(packageInfoPresenter.contextAsUser, app.packageName)
+ ?.let { intent -> installButton(intent, app) }
+ }
+
+ private fun installButton(intent: Intent, app: ApplicationInfo) = ActionButton(
+ text = context.getString(R.string.install_text),
+ imageVector = Icons.Outlined.FileDownload,
+ ) {
+ context.startActivityAsUser(intent, app.userHandle)
+ }
+}
diff --git a/src/com/android/settings/spa/app/appsettings/AppLaunchButton.kt b/src/com/android/settings/spa/app/appsettings/AppLaunchButton.kt
index a983b675938..9f81661b03b 100644
--- a/src/com/android/settings/spa/app/appsettings/AppLaunchButton.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppLaunchButton.kt
@@ -16,7 +16,6 @@
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
@@ -26,11 +25,12 @@ 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
+class AppLaunchButton(packageInfoPresenter: PackageInfoPresenter) {
+ private val context = packageInfoPresenter.context
+ private val packageManagerAsUser = packageInfoPresenter.packageManagerAsUser
fun getActionButton(packageInfo: PackageInfo): ActionButton? =
- packageManager.getLaunchIntentForPackage(packageInfo.packageName)?.let { intent ->
+ packageManagerAsUser.getLaunchIntentForPackage(packageInfo.packageName)?.let { intent ->
launchButton(intent, packageInfo.applicationInfo)
}
diff --git a/src/com/android/settings/spa/app/appsettings/AppSettings.kt b/src/com/android/settings/spa/app/appsettings/AppSettings.kt
index 4c960e5a17a..615fa75d860 100644
--- a/src/com/android/settings/spa/app/appsettings/AppSettings.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppSettings.kt
@@ -60,7 +60,7 @@ object AppSettingsProvider : SettingsPageProvider {
PackageInfoPresenter(context, packageName, userId, coroutineScope)
}
AppSettings(packageInfoPresenter)
- packageInfoPresenter.PageCloser()
+ packageInfoPresenter.PackageRemoveDetector()
}
@Composable
@@ -77,7 +77,13 @@ object AppSettingsProvider : SettingsPageProvider {
@Composable
private fun AppSettings(packageInfoPresenter: PackageInfoPresenter) {
val packageInfo = packageInfoPresenter.flow.collectAsState().value ?: return
- RegularScaffold(title = stringResource(R.string.application_info_label)) {
+ val app = packageInfo.applicationInfo
+ RegularScaffold(
+ title = stringResource(R.string.application_info_label),
+ actions = {
+ AppSettingsMoreOptions(packageInfoPresenter, app)
+ }
+ ) {
val appInfoProvider = remember { AppInfoProvider(packageInfo) }
appInfoProvider.AppInfo()
@@ -85,7 +91,6 @@ private fun AppSettings(packageInfoPresenter: PackageInfoPresenter) {
AppButtons(packageInfoPresenter)
Category(title = stringResource(R.string.advanced_apps)) {
- val app = packageInfo.applicationInfo
DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
ModifySystemSettingsAppListProvider.InfoPageEntryItem(app)
PictureInPictureListProvider.InfoPageEntryItem(app)
diff --git a/src/com/android/settings/spa/app/appsettings/AppSettingsMoreOptions.kt b/src/com/android/settings/spa/app/appsettings/AppSettingsMoreOptions.kt
new file mode 100644
index 00000000000..d17ff33bf39
--- /dev/null
+++ b/src/com/android/settings/spa/app/appsettings/AppSettingsMoreOptions.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.pm.ApplicationInfo
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settings.Utils
+import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.android.settingslib.spaprivileged.model.app.PackageManagers
+import com.android.settingslib.spaprivileged.model.app.isActiveAdmin
+import com.android.settingslib.spaprivileged.model.app.isDisallowControl
+import com.android.settingslib.spaprivileged.model.app.userId
+
+@Composable
+fun AppSettingsMoreOptions(packageInfoPresenter: PackageInfoPresenter, app: ApplicationInfo) {
+ val context = LocalContext.current
+ // We don't allow uninstalling update for DO/PO if it's a system app, because it will clear data
+ // on all users. We also don't allow uninstalling for all users if it's DO/PO for any user.
+ val isProfileOrDeviceOwner = remember(app) {
+ Utils.isProfileOrDeviceOwner(
+ context.userManager, context.devicePolicyManager, app.packageName
+ )
+ }
+ if (isProfileOrDeviceOwner) return
+ val shownUninstallUpdates = remember(app) { isShowUninstallUpdates(context, app) }
+ val shownUninstallForAllUsers = remember(app) { isShowUninstallForAllUsers(context, app) }
+ if (!shownUninstallUpdates && !shownUninstallForAllUsers) return
+ MoreOptionsAction { onDismissRequest ->
+ if (shownUninstallUpdates) {
+ DropdownMenuItem(
+ text = { Text(stringResource(R.string.app_factory_reset)) },
+ onClick = {
+ onDismissRequest()
+ packageInfoPresenter.startUninstallActivity(forAllUsers = false)
+ },
+ )
+ }
+ if (shownUninstallForAllUsers) {
+ DropdownMenuItem(
+ text = { Text(stringResource(R.string.uninstall_all_users_text)) },
+ onClick = {
+ onDismissRequest()
+ packageInfoPresenter.startUninstallActivity(forAllUsers = true)
+ },
+ )
+ }
+ }
+}
+
+private fun isShowUninstallUpdates(context: Context, app: ApplicationInfo): Boolean =
+ app.isUpdatedSystemApp && context.userManager.isUserAdmin(app.userId) &&
+ !app.isDisallowControl(context) &&
+ !context.resources.getBoolean(R.bool.config_disable_uninstall_update)
+
+private fun isShowUninstallForAllUsers(context: Context, app: ApplicationInfo): Boolean =
+ app.userId == 0 && !app.isSystemApp && !app.isInstantApp && !app.isActiveAdmin(context) &&
+ isOtherUserHasInstallPackage(context, app)
+
+private fun isOtherUserHasInstallPackage(context: Context, app: ApplicationInfo): Boolean =
+ context.userManager.aliveUsers
+ .filter { it.id != app.userId }
+ .any { PackageManagers.isPackageInstalledAsUser(app.packageName, it.id) }
diff --git a/src/com/android/settings/spa/app/appsettings/AppUninstallButton.kt b/src/com/android/settings/spa/app/appsettings/AppUninstallButton.kt
index 53b36b99d11..cdb2cd01e8f 100644
--- a/src/com/android/settings/spa/app/appsettings/AppUninstallButton.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppUninstallButton.kt
@@ -16,7 +16,6 @@
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
@@ -24,7 +23,6 @@ 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
@@ -33,27 +31,27 @@ import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminAd
import com.android.settingslib.RestrictedLockUtils
import com.android.settingslib.RestrictedLockUtilsInternal
import com.android.settingslib.spa.widget.button.ActionButton
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
import com.android.settingslib.spaprivileged.model.app.hasFlag
+import com.android.settingslib.spaprivileged.model.app.isActiveAdmin
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)!!
+ private val devicePolicyManager = context.devicePolicyManager
fun getActionButton(packageInfo: PackageInfo): ActionButton? {
val app = packageInfo.applicationInfo
- if (app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) return null
+ if (app.isSystemApp || app.isInstantApp) 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
+ !app.hasFlag(ApplicationInfo.FLAG_INSTALLED) -> false
// Not allow to uninstall DO/PO.
Utils.isProfileOrDeviceOwner(devicePolicyManager, app.packageName, app.userId) -> false
@@ -101,7 +99,7 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
) { onUninstallClicked(app) }
private fun onUninstallClicked(app: ApplicationInfo) {
- if (appButtonRepository.isActiveAdmin(app)) {
+ if (app.isActiveAdmin(context)) {
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)
@@ -115,20 +113,6 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, admin)
return
}
- startUninstallActivity(app)
- }
-
- data class HomePackages(
- val homePackages: Set,
- 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)
+ packageInfoPresenter.startUninstallActivity()
}
}
diff --git a/src/com/android/settings/spa/app/appsettings/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appsettings/PackageInfoPresenter.kt
index eb39c4ff636..299c0a7dea1 100644
--- a/src/com/android/settings/spa/app/appsettings/PackageInfoPresenter.kt
+++ b/src/com/android/settings/spa/app/appsettings/PackageInfoPresenter.kt
@@ -23,6 +23,7 @@ import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
+import android.net.Uri
import android.os.UserHandle
import android.util.Log
import androidx.compose.runtime.Composable
@@ -48,9 +49,8 @@ class PackageInfoPresenter(
private val coroutineScope: CoroutineScope,
) {
private val metricsFeatureProvider = FeatureFactory.getFactory(context).metricsFeatureProvider
- private val packageManager by lazy {
- context.createContextAsUser(UserHandle.of(userId), 0).packageManager
- }
+ val contextAsUser by lazy { context.createContextAsUser(UserHandle.of(userId), 0) }
+ val packageManagerAsUser: PackageManager by lazy { contextAsUser.packageManager }
private val _flow: MutableStateFlow = MutableStateFlow(null)
val flow: StateFlow = _flow
@@ -66,17 +66,23 @@ class PackageInfoPresenter(
}
/**
- * Closes the page when the package is uninstalled.
+ * Detects the package removed event.
*/
@Composable
- fun PageCloser() {
+ fun PackageRemoveDetector() {
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()
+ val packageInfo = flow.value
+ if (packageInfo != null && packageInfo.applicationInfo.isSystemApp) {
+ // System app still exists after uninstalling the updates, refresh the page.
+ notifyChange()
+ } else {
+ navController.navigateBack()
+ }
}
}
}
@@ -85,7 +91,7 @@ class PackageInfoPresenter(
fun enable() {
logAction(SettingsEnums.ACTION_SETTINGS_ENABLE_APP)
coroutineScope.launch(Dispatchers.IO) {
- packageManager.setApplicationEnabledSetting(
+ packageManagerAsUser.setApplicationEnabledSetting(
packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0
)
notifyChange()
@@ -96,13 +102,32 @@ class PackageInfoPresenter(
fun disable() {
logAction(SettingsEnums.ACTION_SETTINGS_DISABLE_APP)
coroutineScope.launch(Dispatchers.IO) {
- packageManager.setApplicationEnabledSetting(
+ packageManagerAsUser.setApplicationEnabledSetting(
packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0
)
notifyChange()
}
}
+ /** Starts the uninstallation activity. */
+ fun startUninstallActivity(forAllUsers: Boolean = false) {
+ logAction(SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP)
+ val packageUri = Uri.parse("package:${packageName}")
+ val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri).apply {
+ putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, forAllUsers)
+ }
+ context.startActivityAsUser(intent, UserHandle.of(userId))
+ }
+
+ /** Clears this instant app. */
+ fun clearInstantApp() {
+ logAction(SettingsEnums.ACTION_SETTINGS_CLEAR_INSTANT_APP)
+ coroutineScope.launch(Dispatchers.IO) {
+ packageManagerAsUser.deletePackageAsUser(packageName, null, 0, userId)
+ notifyChange()
+ }
+ }
+
/** Force stops this package. */
fun forceStop() {
logAction(SettingsEnums.ACTION_APP_FORCE_STOP)
diff --git a/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java b/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java
deleted file mode 100644
index 31b8e794480..00000000000
--- a/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.localepicker;
-
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.os.Looper;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.settingslib.widget.FooterPreference;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidJUnit4.class)
-public class LocaleHelperPreferenceControllerTest {
- private Context mContext;
- private LocaleHelperPreferenceController mLocaleHelperPreferenceController;
-
- @Mock
- private FooterPreference mMockFooterPreference;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
- mContext = ApplicationProvider.getApplicationContext();
- mLocaleHelperPreferenceController = new LocaleHelperPreferenceController(mContext);
- }
-
- @Test
- public void updateFooterPreference_setFooterPreference_hasClickAction() {
- mLocaleHelperPreferenceController.updateFooterPreference(mMockFooterPreference);
- verify(mMockFooterPreference).setLearnMoreText(anyString());
- }
-}