diff --git a/res/values/dimens.xml b/res/values/dimens.xml index dc23c3d8d06..28ab8f8323e 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -348,6 +348,7 @@ 12dp 16dp 12dp + 0dp 12dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 629394aba41..2ad35e0b77d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4321,6 +4321,20 @@ result from their use. + + + There is an issue erasing the downloaded SIMs + + Please reboot the device and try again. If you continue factory reset, the downloaded SIMs may remain on the device. + + Reboot + + Continue factory reset? + + Downloaded SIMs will remain on device. + + Factory reset + Your device and personal data are more vulnerable diff --git a/src/com/android/settings/MasterClearConfirm.java b/src/com/android/settings/MasterClearConfirm.java index 3ace43621b6..676789edc8d 100644 --- a/src/com/android/settings/MasterClearConfirm.java +++ b/src/com/android/settings/MasterClearConfirm.java @@ -21,20 +21,26 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.ActionBar; import android.app.Activity; +import android.app.AlertDialog; import android.app.ProgressDialog; import android.app.admin.DevicePolicyManager; import android.app.admin.FactoryResetProtectionPolicy; import android.app.settings.SettingsEnums; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; +import android.os.PowerManager; import android.os.UserHandle; import android.os.UserManager; import android.service.oemlock.OemLockManager; import android.service.persistentdata.PersistentDataBlockManager; +import android.telephony.TelephonyManager; +import android.telephony.UiccSlotInfo; +import android.telephony.euicc.EuiccManager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -54,6 +60,8 @@ import com.google.android.setupcompat.template.FooterButton.ButtonType; import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupdesign.GlifLayout; +import java.util.Arrays; + /** * Confirm and execute a reset of the device to a clean "just out of the box" * state. Multiple confirmations are required: first, a general "are you sure @@ -83,6 +91,89 @@ public class MasterClearConfirm extends InstrumentedFragment { return; } + // If the eSIM slot is in an error state, display a dialog to warn users that their eSIM + // profiles may not be fully deleted during FDR. + if (shouldShowEsimEraseFailureDialog()) { + Log.e(TAG, "eUICC card is in an error state. Display a dialog to warn the user."); + showEsimErrorDialog(); + return; + } + + performFactoryReset(); + } + + /** + * Returns true if the user choose to erase eSIM profile but the eUICC card is in an error + * state. + */ + private boolean shouldShowEsimEraseFailureDialog() { + EuiccManager euiccManager = getActivity().getSystemService(EuiccManager.class); + TelephonyManager telephonyManager = + getActivity().getSystemService(TelephonyManager.class); + + if (euiccManager == null || !euiccManager.isEnabled()) { + Log.i( + TAG, + "eSIM manager is disabled. No need to check eSIM slot before FDR."); + return false; + } + if (!mEraseEsims) { + Log.i( + TAG, + "eSIM does not need to be reset. No need to check eSIM slot before FDR."); + return false; + } + UiccSlotInfo[] slotInfos = telephonyManager.getUiccSlotsInfo(); + if (slotInfos == null) { + Log.i(TAG, "Unable to get UICC slots."); + return false; + } + // If getIsEuicc() returns false for an eSIM slot, it means the eSIM is in the error + // state. + return Arrays.stream(slotInfos).anyMatch( + slot -> slot != null && !slot.isRemovable() && !slot.getIsEuicc()); + } + + private void showEsimErrorDialog() { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.fdr_esim_failure_title) + .setMessage(R.string.fdr_esim_failure_text) + .setNeutralButton(R.string.dlg_cancel, + (DialogInterface.OnClickListener) (dialog, which) -> { + dialog.dismiss(); + }) + .setNegativeButton(R.string.fdr_esim_failure_reboot_btn, + (DialogInterface.OnClickListener) (dialog, which) -> { + dialog.dismiss(); + PowerManager pm = (PowerManager) getActivity() + .getSystemService(Context.POWER_SERVICE); + pm.reboot(null); + }) + .setPositiveButton(R.string.lockpassword_continue_label, + (DialogInterface.OnClickListener) (dialog, which) -> { + dialog.dismiss(); + showContinueFdrDialog(); + }) + .show(); + } + + private void showContinueFdrDialog() { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.fdr_continue_title) + .setMessage(R.string.fdr_continue_text) + .setNegativeButton(R.string.dlg_cancel, + (DialogInterface.OnClickListener) (dialog, which) -> { + dialog.dismiss(); + }) + .setPositiveButton(R.string.fdr_continue_btn, + (DialogInterface.OnClickListener) (dialog, which) -> { + dialog.dismiss(); + performFactoryReset(); + }) + .show(); + } + + private void performFactoryReset() { final PersistentDataBlockManager pdbManager = (PersistentDataBlockManager) getActivity().getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProvider.java b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProvider.java index 7f29ecb836e..684e6585709 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProvider.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProvider.java @@ -24,6 +24,9 @@ public interface ContextualCardFeatureProvider { /** Get contextual cards from the card provider */ Cursor getContextualCards(); + /** Get the default contextual card to display */ + ContextualCard getDefaultContextualCard(); + /** * Mark a specific {@link ContextualCard} as dismissed with dismissal signal in the database * to indicate that the card has been dismissed. diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java index 5059d90a15e..643625bf190 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java @@ -54,6 +54,11 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP return cursor; } + @Override + public ContextualCard getDefaultContextualCard() { + return null; + } + @Override public int markCardAsDismissed(Context context, String cardName) { final SQLiteDatabase db = CardDatabaseHelper.getInstance(mContext).getWritableDatabase(); diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java index e4a644cf780..81ea45a818c 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java @@ -50,7 +50,7 @@ import java.util.stream.Collectors; public class ContextualCardLoader extends AsyncLoaderCompat> { @VisibleForTesting - static final int DEFAULT_CARD_COUNT = 3; + static final int DEFAULT_CARD_COUNT = 1; @VisibleForTesting static final String CONTEXTUAL_CARD_COUNT = "contextual_card_count"; static final int CARD_CONTENT_LOADER_ID = 1; @@ -131,7 +131,7 @@ public class ContextualCardLoader extends AsyncLoaderCompat final List visibleCards = new ArrayList<>(); final List hiddenCards = new ArrayList<>(); - final int maxCardCount = getCardCount(); + final int maxCardCount = getCardCount(mContext); eligibleCards.forEach(card -> { if (card.getCategory() != STICKY_VALUE) { return; @@ -164,14 +164,23 @@ public class ContextualCardLoader extends AsyncLoaderCompat SettingsEnums.ACTION_CONTEXTUAL_CARD_NOT_SHOW, ContextualCardLogUtils.buildCardListLog(hiddenCards)); } + + // Add a default card if no other visible cards + if (visibleCards.isEmpty() && maxCardCount == 1) { + final ContextualCard defaultCard = FeatureFactory.getFactory(mContext) + .getContextualCardFeatureProvider(mContext).getDefaultContextualCard(); + if (defaultCard != null) { + Log.i(TAG, "Default card: " + defaultCard.getSliceUri()); + visibleCards.add(defaultCard); + } + } return visibleCards; } - @VisibleForTesting - int getCardCount() { + static int getCardCount(Context context) { // Return the card count if Settings.Global has KEY_CONTEXTUAL_CARD_COUNT key, // otherwise return the default one. - return Settings.Global.getInt(mContext.getContentResolver(), + return Settings.Global.getInt(context.getContentResolver(), CONTEXTUAL_CARD_COUNT, DEFAULT_CARD_COUNT); } diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java index ac35017baab..00755296d0a 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java @@ -122,6 +122,10 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo Log.w(TAG, "Legacy suggestion contextual card enabled, skipping contextual cards."); return; } + if (ContextualCardLoader.getCardCount(mContext) <= 0) { + Log.w(TAG, "Card count is zero, skipping contextual cards."); + return; + } mStartTime = System.currentTimeMillis(); final CardContentLoaderCallbacks cardContentLoaderCallbacks = new CardContentLoaderCallbacks(mContext); diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java b/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java index b9bc43b32b5..cf6f53c7075 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java @@ -16,7 +16,10 @@ package com.android.settings.homepage.contextualcards; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + import android.content.Context; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -131,7 +134,22 @@ public class ContextualCardsAdapter extends RecyclerView.Adapter cards = getContextualCardList().stream().limit(2) .collect(Collectors.toList()); doReturn(cards).when(mContextualCardLoader).filterEligibleCards(anyList()); final List result = mContextualCardLoader.getDisplayableCards(cards); - assertThat(result).hasSize(cards.size()); + assertThat(result).hasSize(Math.min(cards.size(), DEFAULT_CARD_COUNT)); } @Test - public void getDisplayableCards_fourEligibleCards_shouldShowDefaultCardCount() { + public void getDisplayableCards_fourEligibleCards_notExceedDefaultCardCount() { final List cards = getContextualCardList().stream().limit(4) .collect(Collectors.toList()); doReturn(cards).when(mContextualCardLoader).filterEligibleCards(anyList()); final List result = mContextualCardLoader.getDisplayableCards(cards); - assertThat(result).hasSize(DEFAULT_CARD_COUNT); + assertThat(result).hasSize(Math.min(cards.size(), DEFAULT_CARD_COUNT)); } @Test @@ -139,7 +139,7 @@ public class ContextualCardLoaderTest { @Test public void getCardCount_noConfiguredCardCount_returnDefaultCardCount() { - assertThat(mContextualCardLoader.getCardCount()).isEqualTo(DEFAULT_CARD_COUNT); + assertThat(mContextualCardLoader.getCardCount(mContext)).isEqualTo(DEFAULT_CARD_COUNT); } @Test @@ -148,7 +148,7 @@ public class ContextualCardLoaderTest { Settings.Global.putLong(mContext.getContentResolver(), ContextualCardLoader.CONTEXTUAL_CARD_COUNT, configCount); - assertThat(mContextualCardLoader.getCardCount()).isEqualTo(configCount); + assertThat(mContextualCardLoader.getCardCount(mContext)).isEqualTo(configCount); } private List getContextualCardList() { diff --git a/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java index 54b7c2db358..fe1bb11b1b2 100644 --- a/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java @@ -30,6 +30,7 @@ import android.content.Context; import android.net.NetworkCapabilities; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; +import android.provider.Settings; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; @@ -77,6 +78,9 @@ public class ContextualWifiSliceTest { SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); mWifiManager.setWifiEnabled(true); + // Set WifiSlice expandable + Settings.Global.putInt(mContext.getContentResolver(), + ContextualWifiSlice.CONTEXTUAL_WIFI_EXPANDABLE, 1); mWifiSlice = new ContextualWifiSlice(mContext); } @@ -126,6 +130,18 @@ public class ContextualWifiSliceTest { assertThat(ContextualWifiSlice.getApRowCount()).isEqualTo(COLLAPSED_ROW_COUNT); } + @Test + public void getWifiSlice_notExpandable_shouldCollapseSlice() { + Settings.Global.putInt(mContext.getContentResolver(), + ContextualWifiSlice.CONTEXTUAL_WIFI_EXPANDABLE, 0); + mWifiSlice.sApRowCollapsed = false; + + final Slice wifiSlice = mWifiSlice.getSlice(); + + assertWifiHeader(wifiSlice); + assertThat(ContextualWifiSlice.getApRowCount()).isEqualTo(COLLAPSED_ROW_COUNT); + } + @Test public void getWifiSlice_contextualWifiSlice_shouldReturnContextualWifiSliceUri() { mWifiSlice.sActiveUiSession = mFeatureFactory.slicesFeatureProvider.getUiSessionToken();