From 5d0057615aad046b50895e98d140180c7bd10955 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Wed, 6 Apr 2022 12:06:08 +0800 Subject: [PATCH 1/8] Fix title clipped for "Custom settings for schedule" Set rule to header, switch and action buttons controllers before their isAvailable() is called in onCreate(). Fix: 228262001 Test: manual Change-Id: Idc0ea779ab768497a8426ac847c4bbb2c6c4faea --- ...tomaticRuleHeaderPreferenceController.java | 13 +++--- ...tomaticRuleSwitchPreferenceController.java | 10 ++--- .../zen/ZenModeRuleSettingsBase.java | 21 ++++++---- .../ZenRuleButtonsPreferenceController.java | 17 ++++---- .../zen/ZenModeEventRuleSettingsTest.java | 42 +++++-------------- 5 files changed, 40 insertions(+), 63 deletions(-) diff --git a/src/com/android/settings/notification/zen/ZenAutomaticRuleHeaderPreferenceController.java b/src/com/android/settings/notification/zen/ZenAutomaticRuleHeaderPreferenceController.java index 813955558ca..a0c33dfb4b8 100644 --- a/src/com/android/settings/notification/zen/ZenAutomaticRuleHeaderPreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenAutomaticRuleHeaderPreferenceController.java @@ -38,10 +38,8 @@ import com.android.settingslib.widget.LayoutPreference; public class ZenAutomaticRuleHeaderPreferenceController extends AbstractZenModePreferenceController implements PreferenceControllerMixin { - private final String KEY = PREF_KEY_APP_HEADER; private final PreferenceFragmentCompat mFragment; private AutomaticZenRule mRule; - private String mId; private EntityHeaderController mController; public ZenAutomaticRuleHeaderPreferenceController(Context context, @@ -52,7 +50,11 @@ public class ZenAutomaticRuleHeaderPreferenceController extends AbstractZenModeP @Override public String getPreferenceKey() { - return KEY; + return PREF_KEY_APP_HEADER; + } + + void setRule(AutomaticZenRule rule) { + mRule = rule; } @Override @@ -96,9 +98,4 @@ public class ZenAutomaticRuleHeaderPreferenceController extends AbstractZenModeP return null; } - - protected void onResume(AutomaticZenRule rule, String id) { - mRule = rule; - mId = id; - } } diff --git a/src/com/android/settings/notification/zen/ZenAutomaticRuleSwitchPreferenceController.java b/src/com/android/settings/notification/zen/ZenAutomaticRuleSwitchPreferenceController.java index 703ffae49a4..fa36dde5171 100644 --- a/src/com/android/settings/notification/zen/ZenAutomaticRuleSwitchPreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenAutomaticRuleSwitchPreferenceController.java @@ -48,6 +48,11 @@ public class ZenAutomaticRuleSwitchPreferenceController extends return KEY; } + void setIdAndRule(String id, AutomaticZenRule rule) { + mId = id; + mRule = rule; + } + @Override public boolean isAvailable() { return mRule != null && mId != null; @@ -74,11 +79,6 @@ public class ZenAutomaticRuleSwitchPreferenceController extends } } - public void onResume(AutomaticZenRule rule, String id) { - mRule = rule; - mId = id; - } - public void updateState(Preference preference) { if (mRule != null) { mSwitchBar.updateStatus(mRule.isEnabled()); diff --git a/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java b/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java index 5ce8b48b27c..57684f63a2f 100644 --- a/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java +++ b/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java @@ -43,7 +43,6 @@ public abstract class ZenModeRuleSettingsBase extends ZenModeSettingsBase { private final String CUSTOM_BEHAVIOR_KEY = "zen_custom_setting"; - protected Context mContext; protected boolean mDisableListeners; protected AutomaticZenRule mRule; protected String mId; @@ -58,9 +57,8 @@ public abstract class ZenModeRuleSettingsBase extends ZenModeSettingsBase { abstract protected void updateControlsInternal(); @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - mContext = getActivity(); + public void onAttach(Context context) { + super.onAttach(context); final Intent intent = getActivity().getIntent(); if (DEBUG) Log.d(TAG, "onCreate getIntent()=" + intent); @@ -78,7 +76,14 @@ public abstract class ZenModeRuleSettingsBase extends ZenModeSettingsBase { } if (DEBUG) Log.d(TAG, "mId=" + mId); - if (refreshRuleOrFinish()) { + refreshRuleOrFinish(); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + if (isFinishingOrDestroyed()) { return; } @@ -128,15 +133,12 @@ public abstract class ZenModeRuleSettingsBase extends ZenModeSettingsBase { protected void updateHeader() { final PreferenceScreen screen = getPreferenceScreen(); - mSwitch.onResume(mRule, mId); mSwitch.displayPreference(screen); updatePreference(mSwitch); - mHeader.onResume(mRule, mId); mHeader.displayPreference(screen); updatePreference(mHeader); - mActionButtons.onResume(mRule, mId); mActionButtons.displayPreference(screen); updatePreference(mActionButtons); } @@ -157,6 +159,9 @@ public abstract class ZenModeRuleSettingsBase extends ZenModeSettingsBase { private boolean refreshRuleOrFinish() { mRule = getZenRule(); if (DEBUG) Log.d(TAG, "mRule=" + mRule); + mHeader.setRule(mRule); + mSwitch.setIdAndRule(mId, mRule); + mActionButtons.setIdAndRule(mId, mRule); if (!setRule(mRule)) { toastAndFinish(); return true; diff --git a/src/com/android/settings/notification/zen/ZenRuleButtonsPreferenceController.java b/src/com/android/settings/notification/zen/ZenRuleButtonsPreferenceController.java index 1b57fed4e6e..3b7cde09cf0 100644 --- a/src/com/android/settings/notification/zen/ZenRuleButtonsPreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenRuleButtonsPreferenceController.java @@ -39,11 +39,9 @@ public class ZenRuleButtonsPreferenceController extends AbstractZenModePreferenc implements PreferenceControllerMixin { public static final String KEY = "zen_action_buttons"; - private AutomaticZenRule mRule; + private final PreferenceFragmentCompat mFragment; private String mId; - private PreferenceFragmentCompat mFragment; - private ActionButtonsPreference mButtonsPref; - + private AutomaticZenRule mRule; public ZenRuleButtonsPreferenceController(Context context, PreferenceFragmentCompat fragment, Lifecycle lc) { @@ -51,6 +49,10 @@ public class ZenRuleButtonsPreferenceController extends AbstractZenModePreferenc mFragment = fragment; } + void setIdAndRule(String id, AutomaticZenRule rule) { + mId = id; + mRule = rule; + } @Override public boolean isAvailable() { @@ -60,7 +62,7 @@ public class ZenRuleButtonsPreferenceController extends AbstractZenModePreferenc @Override public void displayPreference(PreferenceScreen screen) { if (isAvailable()) { - mButtonsPref = ((ActionButtonsPreference) screen.findPreference(KEY)) + ((ActionButtonsPreference) screen.findPreference(KEY)) .setButton1Text(R.string.zen_mode_rule_name_edit) .setButton1Icon(com.android.internal.R.drawable.ic_mode_edit) .setButton1OnClickListener(new EditRuleNameClickListener()) @@ -116,9 +118,4 @@ public class ZenRuleButtonsPreferenceController extends AbstractZenModePreferenc }); } } - - protected void onResume(AutomaticZenRule rule, String id) { - mRule = rule; - mId = id; - } } diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeEventRuleSettingsTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeEventRuleSettingsTest.java index f0409ce93f8..eeb53cff369 100644 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeEventRuleSettingsTest.java +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeEventRuleSettingsTest.java @@ -18,31 +18,27 @@ package com.android.settings.notification.zen; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.robolectric.RuntimeEnvironment.application; -import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Looper; import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; -import org.robolectric.shadows.ShadowApplication; import org.robolectric.shadows.ShadowToast; import java.util.ArrayList; @@ -57,42 +53,32 @@ public class ZenModeEventRuleSettingsTest { @Mock private Intent mIntent; - @Mock - private NotificationManager mNotificationManager; - - private TestFragment mFragment; + private ZenModeEventRuleSettings mFragment; private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); - ShadowApplication shadowApplication = ShadowApplication.getInstance(); - shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); - mContext = application; + mContext = ApplicationProvider.getApplicationContext(); - mFragment = spy(new TestFragment()); - mFragment.onAttach(application); - - doReturn(mActivity).when(mFragment).getActivity(); - - Resources res = application.getResources(); - - doReturn(res).when(mFragment).getResources(); + Resources res = mContext.getResources(); when(mActivity.getTheme()).thenReturn(res.newTheme()); when(mActivity.getIntent()).thenReturn(mIntent); when(mActivity.getResources()).thenReturn(res); when(mActivity.getMainLooper()).thenReturn(mock(Looper.class)); + + mFragment = spy(new ZenModeEventRuleSettings()); + when(mFragment.getActivity()).thenReturn(mActivity); when(mFragment.getContext()).thenReturn(mContext); + when(mFragment.getResources()).thenReturn(res); + mFragment.onAttach(mContext); } @Test - @Ignore - public void onCreate_noRuleId_shouldToastAndFinishAndNoCrash() { + public void onAttach_noRuleId_shouldToastAndFinishAndNoCrash() { final String expected = mContext.getString(R.string.zen_mode_rule_not_found_text); - mFragment.onCreate(null); - // verify the toast assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(expected); @@ -110,12 +96,4 @@ public class ZenModeEventRuleSettingsTest { mFragment.addCalendar(1234, "calName", 3, calendarsList); assertThat(calendarsList.size()).isEqualTo(1); } - - private static class TestFragment extends ZenModeEventRuleSettings { - - @Override - protected Object getSystemService(final String name) { - return null; - } - } } From 05e824e9e09a3087e65e2e4953994b9052269502 Mon Sep 17 00:00:00 2001 From: Victor Truong Date: Thu, 14 Apr 2022 17:38:26 +0000 Subject: [PATCH 2/8] Set Active dream to not be clickable when active. Setting Active dream to not be clickable to stop Talkback from prompting user to "Double tap to activate". Bug: 228573813 Test: Manually tested on device. Change-Id: I5f646bcf82d8c4172127f0739b6c0d7af890dabb --- src/com/android/settings/dream/DreamAdapter.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/dream/DreamAdapter.java b/src/com/android/settings/dream/DreamAdapter.java index c595c099286..590b282b25a 100644 --- a/src/com/android/settings/dream/DreamAdapter.java +++ b/src/com/android/settings/dream/DreamAdapter.java @@ -105,13 +105,6 @@ public class DreamAdapter extends RecyclerView.Adapter icon.setBounds(0, 0, iconSize, iconSize); mTitleView.setCompoundDrawablesRelative(icon, null, null, null); - if (item.isActive()) { - mLastSelectedPos = position; - itemView.setSelected(true); - } else { - itemView.setSelected(false); - } - mCustomizeButton.setOnClickListener(v -> item.onCustomizeClicked()); mCustomizeButton.setVisibility( item.allowCustomization() && mEnabled ? View.VISIBLE : View.GONE); @@ -125,6 +118,15 @@ public class DreamAdapter extends RecyclerView.Adapter notifyItemChanged(position); }); + if (item.isActive()) { + mLastSelectedPos = position; + itemView.setSelected(true); + itemView.setClickable(false); + } else { + itemView.setSelected(false); + itemView.setClickable(true); + } + setEnabledStateOnViews(itemView, mEnabled); } From d8b90650d397e8c2bcd7ccae318b2e8e03b989fa Mon Sep 17 00:00:00 2001 From: Lucas Silva Date: Thu, 14 Apr 2022 14:56:51 -0400 Subject: [PATCH 3/8] Update dream settings categories, to fix padding. The extra PreferenceCategory is adding some unnecessary padding, so removing it. Instead of using a preference category to enable/disable all items, we just iterate through all the preferences when enabling/disabling items. Bug: 227139218 Test: locally on device Change-Id: I403295fbccb7b135b7d603cd1fc713c4c0189569 --- res/xml/dream_fragment_overview.xml | 39 +++++++++---------- .../settings/dream/DreamPickerController.java | 24 ++++-------- .../android/settings/dream/DreamSettings.java | 28 ++++++++----- 3 files changed, 44 insertions(+), 47 deletions(-) diff --git a/res/xml/dream_fragment_overview.xml b/res/xml/dream_fragment_overview.xml index 0ca4869865b..9a401a36a2b 100644 --- a/res/xml/dream_fragment_overview.xml +++ b/res/xml/dream_fragment_overview.xml @@ -26,28 +26,25 @@ settings:searchable="false"/> - - - + android:title="@string/dream_picker_category"> + + - - - - + + + diff --git a/src/com/android/settings/dream/DreamPickerController.java b/src/com/android/settings/dream/DreamPickerController.java index 20d8aeb46a4..261db6c8ef8 100644 --- a/src/com/android/settings/dream/DreamPickerController.java +++ b/src/com/android/settings/dream/DreamPickerController.java @@ -16,12 +16,9 @@ package com.android.settings.dream; -import static com.android.settings.dream.DreamMainSwitchPreferenceController.MAIN_SWITCH_PREF_KEY; - import android.app.settings.SettingsEnums; import android.content.Context; import android.graphics.drawable.Drawable; -import android.widget.Switch; import androidx.annotation.Nullable; import androidx.preference.Preference; @@ -35,8 +32,6 @@ import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.dream.DreamBackend; import com.android.settingslib.dream.DreamBackend.DreamInfo; import com.android.settingslib.widget.LayoutPreference; -import com.android.settingslib.widget.MainSwitchPreference; -import com.android.settingslib.widget.OnMainSwitchChangeListener; import java.util.List; import java.util.stream.Collectors; @@ -44,8 +39,7 @@ import java.util.stream.Collectors; /** * Controller for the dream picker where the user can select a screensaver. */ -public class DreamPickerController extends BasePreferenceController implements - OnMainSwitchChangeListener { +public class DreamPickerController extends BasePreferenceController { private final DreamBackend mBackend; private final MetricsFeatureProvider mMetricsFeatureProvider; @@ -92,10 +86,13 @@ public class DreamPickerController extends BasePreferenceController implements new GridSpacingItemDecoration(mContext, R.dimen.dream_preference_card_padding)); recyclerView.setHasFixedSize(true); recyclerView.setAdapter(mAdapter); + } - final Preference mainSwitchPref = screen.findPreference(MAIN_SWITCH_PREF_KEY); - if (mainSwitchPref instanceof MainSwitchPreference) { - ((MainSwitchPreference) mainSwitchPref).addOnSwitchChangeListener(this); + @Override + public void updateState(Preference preference) { + super.updateState(preference); + if (mAdapter != null) { + mAdapter.setEnabled(preference.isEnabled()); } } @@ -108,13 +105,6 @@ public class DreamPickerController extends BasePreferenceController implements .orElse(null); } - @Override - public void onSwitchChanged(Switch switchView, boolean isChecked) { - if (mAdapter != null) { - mAdapter.setEnabled(isChecked); - } - } - private class DreamItem implements IDreamItem { DreamInfo mDreamInfo; diff --git a/src/com/android/settings/dream/DreamSettings.java b/src/com/android/settings/dream/DreamSettings.java index d30f50ff91b..1d12c1aecd5 100644 --- a/src/com/android/settings/dream/DreamSettings.java +++ b/src/com/android/settings/dream/DreamSettings.java @@ -32,7 +32,7 @@ import android.widget.Button; import android.widget.Switch; import androidx.annotation.VisibleForTesting; -import androidx.preference.PreferenceCategory; +import androidx.preference.Preference; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; @@ -57,10 +57,7 @@ public class DreamSettings extends DashboardFragment implements OnMainSwitchChan static final String EITHER_CHARGING_OR_DOCKED = "either_charging_or_docked"; static final String NEVER_DREAM = "never"; - private static final String MAIN_PREF_CATEGORY = "dream_main_category"; - private MainSwitchPreference mMainSwitchPreference; - private PreferenceCategory mMainPrefCategory; private Button mPreviewButton; private RecyclerView mRecyclerView; @@ -152,6 +149,22 @@ public class DreamSettings extends DashboardFragment implements OnMainSwitchChan return controllers; } + private void setAllPreferencesEnabled(boolean isEnabled) { + getPreferenceControllers().forEach(controllers -> { + controllers.forEach(controller -> { + final String prefKey = controller.getPreferenceKey(); + if (prefKey.equals(MAIN_SWITCH_PREF_KEY)) { + return; + } + final Preference pref = findPreference(prefKey); + if (pref != null) { + pref.setEnabled(isEnabled); + controller.updateState(pref); + } + }); + }); + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -163,10 +176,7 @@ public class DreamSettings extends DashboardFragment implements OnMainSwitchChan mMainSwitchPreference.addOnSwitchChangeListener(this); } - mMainPrefCategory = findPreference(MAIN_PREF_CATEGORY); - if (mMainPrefCategory != null) { - mMainPrefCategory.setEnabled(dreamBackend.isEnabled()); - } + setAllPreferencesEnabled(dreamBackend.isEnabled()); } @Override @@ -194,7 +204,7 @@ public class DreamSettings extends DashboardFragment implements OnMainSwitchChan @Override public void onSwitchChanged(Switch switchView, boolean isChecked) { - mMainPrefCategory.setEnabled(isChecked); + setAllPreferencesEnabled(isChecked); mPreviewButton.setVisibility(isChecked ? View.VISIBLE : View.GONE); updatePaddingForPreviewButton(); } From 50520c2d3f96f4ba37cc80e95efc9c2fc420c296 Mon Sep 17 00:00:00 2001 From: menghanli Date: Fri, 15 Apr 2022 08:00:36 +0800 Subject: [PATCH 4/8] Fix inconsistent shortcut strings on accessibility setting and edit dialog Root cause: Base class provides an override function for accessibility settings shortcut preference. But, it cannot update to the edit dialog. Solution: Refine the resilience function and base class apply into accessibility settings shortcut preference and edit dailog. Bug: 228830417 Test: Manual testing on all accessibility page and edit dialog Change-Id: I84bc63a39cd9cfa7e12944dff20ee6b92879008d --- .../AccessibilityShortcutPreferenceFragment.java | 11 ++++------- .../ToggleColorInversionPreferenceFragment.java | 4 ++-- .../ToggleDaltonizerPreferenceFragment.java | 4 ++-- .../ToggleFeaturePreferenceFragment.java | 12 ++++-------- .../ToggleReduceBrightColorsPreferenceFragment.java | 4 ++-- .../ToggleScreenMagnificationPreferenceFragment.java | 4 ++-- .../android/settings/gestures/OneHandedSettings.java | 5 ++--- 7 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java index 34e1fb0557d..5546ba665a3 100644 --- a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java +++ b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java @@ -132,8 +132,8 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF mShortcutPreference.setPersistent(false); mShortcutPreference.setKey(getShortcutPreferenceKey()); mShortcutPreference.setOnClickCallback(this); + mShortcutPreference.setTitle(getShortcutTitle()); - updateShortcutTitle(mShortcutPreference); getPreferenceScreen().addPreference(mShortcutPreference); mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> { @@ -192,13 +192,11 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF final Dialog dialog; switch (dialogId) { case DialogEnums.EDIT_SHORTCUT: - final CharSequence dialogTitle = getPrefContext().getString( - R.string.accessibility_shortcut_title, getLabelName()); final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent()) ? AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC_SUW : AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC; dialog = AccessibilityDialogUtils.showEditShortcutDialog( - getPrefContext(), dialogType, dialogTitle, + getPrefContext(), dialogType, getShortcutTitle(), this::callOnAlertDialogCheckboxClicked); setupEditShortcutDialog(dialog); return dialog; @@ -213,9 +211,8 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF } } - protected void updateShortcutTitle(ShortcutPreference shortcutPreference) { - final CharSequence title = getString(R.string.accessibility_shortcut_title, getLabelName()); - shortcutPreference.setTitle(title); + protected CharSequence getShortcutTitle() { + return getString(R.string.accessibility_shortcut_title, getLabelName()); } @Override diff --git a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java index 58837147969..71f47ccbd63 100644 --- a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java @@ -75,8 +75,8 @@ public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePrefere } @Override - protected void updateShortcutTitle(ShortcutPreference shortcutPreference) { - shortcutPreference.setTitle(R.string.accessibility_display_inversion_shortcut_title); + protected CharSequence getShortcutTitle() { + return getText(R.string.accessibility_display_inversion_shortcut_title); } @Override diff --git a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java index 183da964cfc..ac4408fad89 100644 --- a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java @@ -185,8 +185,8 @@ public final class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePrefe } @Override - protected void updateShortcutTitle(ShortcutPreference shortcutPreference) { - shortcutPreference.setTitle(R.string.accessibility_daltonizer_shortcut_title); + protected CharSequence getShortcutTitle() { + return getText(R.string.accessibility_daltonizer_shortcut_title); } @Override diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java index bd8ee667571..43687a509c7 100644 --- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java @@ -270,12 +270,10 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference public Dialog onCreateDialog(int dialogId) { switch (dialogId) { case DialogEnums.EDIT_SHORTCUT: - final CharSequence dialogTitle = getPrefContext().getString( - R.string.accessibility_shortcut_title, mPackageName); final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent()) ? DialogType.EDIT_SHORTCUT_GENERIC_SUW : DialogType.EDIT_SHORTCUT_GENERIC; mDialog = AccessibilityDialogUtils.showEditShortcutDialog( - getPrefContext(), dialogType, dialogTitle, + getPrefContext(), dialogType, getShortcutTitle(), this::callOnAlertDialogCheckboxClicked); setupEditShortcutDialog(mDialog); return mDialog; @@ -340,9 +338,8 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference switchPreference.setTitle(title); } - protected void updateShortcutTitle(ShortcutPreference shortcutPreference) { - final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName); - shortcutPreference.setTitle(title); + protected CharSequence getShortcutTitle() { + return getString(R.string.accessibility_shortcut_title, mPackageName); } protected void onPreferenceToggled(String preferenceKey, boolean enabled) { @@ -515,8 +512,7 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference mShortcutPreference.setPersistent(false); mShortcutPreference.setKey(getShortcutPreferenceKey()); mShortcutPreference.setOnClickCallback(this); - - updateShortcutTitle(mShortcutPreference); + mShortcutPreference.setTitle(getShortcutTitle()); final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); generalCategory.addPreference(mShortcutPreference); diff --git a/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java index 93884b766ea..4e990f4d373 100644 --- a/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java @@ -168,8 +168,8 @@ public class ToggleReduceBrightColorsPreferenceFragment extends ToggleFeaturePre } @Override - protected void updateShortcutTitle(ShortcutPreference shortcutPreference) { - shortcutPreference.setTitle(R.string.reduce_bright_colors_shortcut_title); + protected CharSequence getShortcutTitle() { + return getText(R.string.reduce_bright_colors_shortcut_title); } @Override diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index bf47658f96b..d9a02c00a8a 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -479,8 +479,8 @@ public class ToggleScreenMagnificationPreferenceFragment extends } @Override - protected void updateShortcutTitle(ShortcutPreference shortcutPreference) { - shortcutPreference.setTitle(R.string.accessibility_screen_magnification_shortcut_title); + protected CharSequence getShortcutTitle() { + return getText(R.string.accessibility_screen_magnification_shortcut_title); } @Override diff --git a/src/com/android/settings/gestures/OneHandedSettings.java b/src/com/android/settings/gestures/OneHandedSettings.java index 13ccbd6919b..e1b9b817f95 100644 --- a/src/com/android/settings/gestures/OneHandedSettings.java +++ b/src/com/android/settings/gestures/OneHandedSettings.java @@ -27,7 +27,6 @@ import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.settings.R; import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment; import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType; -import com.android.settings.accessibility.ShortcutPreference; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.widget.IllustrationPreference; import com.android.settingslib.widget.MainSwitchPreference; @@ -88,8 +87,8 @@ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment { } @Override - protected void updateShortcutTitle(ShortcutPreference shortcutPreference) { - shortcutPreference.setTitle(R.string.one_handed_mode_shortcut_title); + protected CharSequence getShortcutTitle() { + return getText(R.string.one_handed_mode_shortcut_title); } @Override From 3ce806d8707387d46e04e8689dd88487cea6b37e Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Sat, 9 Apr 2022 21:45:04 +0800 Subject: [PATCH 5/8] Fix scrolling position not kept in Trusted Credentials For example when changing display states. Save view states to solve this issue. - Save current tab. (System / User) - Save group expanded state. (Personal / Work) - Save scrolling position of each group. Also updated to ViewPager2 and updated the styles. Bug: 204839552 Test: manual Change-Id: Ibeda50b50e7dfd2ba071b75fe2aa88ef560f4c88 --- res/layout/trusted_credentials.xml | 82 +- .../TrustedCredentialsDialogBuilder.java | 2 +- .../settings/TrustedCredentialsFragment.java | 1030 ++++++++++++++++ .../settings/TrustedCredentialsSettings.java | 1061 ++--------------- 4 files changed, 1128 insertions(+), 1047 deletions(-) create mode 100644 src/com/android/settings/TrustedCredentialsFragment.java diff --git a/res/layout/trusted_credentials.xml b/res/layout/trusted_credentials.xml index 31f5f40a81f..3663a797282 100644 --- a/res/layout/trusted_credentials.xml +++ b/res/layout/trusted_credentials.xml @@ -13,78 +13,26 @@ See the License for the specific language governing permissions and limitations under the License. --> - + + - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone"> - + \ No newline at end of file diff --git a/src/com/android/settings/TrustedCredentialsDialogBuilder.java b/src/com/android/settings/TrustedCredentialsDialogBuilder.java index 806da92099b..0dc8c256761 100644 --- a/src/com/android/settings/TrustedCredentialsDialogBuilder.java +++ b/src/com/android/settings/TrustedCredentialsDialogBuilder.java @@ -34,7 +34,7 @@ import android.widget.Spinner; import androidx.appcompat.app.AlertDialog; import com.android.internal.widget.LockPatternUtils; -import com.android.settings.TrustedCredentialsSettings.CertHolder; +import com.android.settings.TrustedCredentialsFragment.CertHolder; import com.android.settingslib.RestrictedLockUtils; import java.security.cert.X509Certificate; diff --git a/src/com/android/settings/TrustedCredentialsFragment.java b/src/com/android/settings/TrustedCredentialsFragment.java new file mode 100644 index 00000000000..ca565a46c1e --- /dev/null +++ b/src/com/android/settings/TrustedCredentialsFragment.java @@ -0,0 +1,1030 @@ +/* + * 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; + +import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER; +import static android.widget.LinearLayout.LayoutParams.MATCH_PARENT; +import static android.widget.LinearLayout.LayoutParams.WRAP_CONTENT; + +import android.annotation.UiThread; +import android.app.Activity; +import android.app.KeyguardManager; +import android.app.admin.DevicePolicyManager; +import android.app.settings.SettingsEnums; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.UserInfo; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.net.http.SslCertificate; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; +import android.security.IKeyChainService; +import android.security.KeyChain; +import android.security.KeyChain.KeyChainConnection; +import android.util.ArraySet; +import android.util.Log; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.Switch; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.UnlaunchableAppActivity; +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.TrustedCredentialsSettings.Tab; +import com.android.settings.core.InstrumentedFragment; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.IntConsumer; + +/** + * Fragment to display trusted credentials settings for one tab. + */ +public class TrustedCredentialsFragment extends InstrumentedFragment + implements TrustedCredentialsDialogBuilder.DelegateInterface { + + public static final String ARG_POSITION = "tab"; + public static final String ARG_SHOW_NEW_FOR_USER = "ARG_SHOW_NEW_FOR_USER"; + + private static final String TAG = "TrustedCredentialsFragment"; + + private DevicePolicyManager mDevicePolicyManager; + private UserManager mUserManager; + private KeyguardManager mKeyguardManager; + private int mTrustAllCaUserId; + + private static final String SAVED_CONFIRMED_CREDENTIAL_USERS = "ConfirmedCredentialUsers"; + private static final String SAVED_CONFIRMING_CREDENTIAL_USER = "ConfirmingCredentialUser"; + private static final int REQUEST_CONFIRM_CREDENTIALS = 1; + + private GroupAdapter mGroupAdapter; + private AliasOperation mAliasOperation; + private ArraySet mConfirmedCredentialUsers; + private int mConfirmingCredentialUser; + private IntConsumer mConfirmingCredentialListener; + private final Set mAliasLoaders = new ArraySet<>(2); + @GuardedBy("mKeyChainConnectionByProfileId") + private final SparseArray + mKeyChainConnectionByProfileId = new SparseArray<>(); + private ViewGroup mFragmentView; + + private final BroadcastReceiver mWorkProfileChangedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) + || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) + || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { + mGroupAdapter.load(); + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Activity activity = getActivity(); + mDevicePolicyManager = activity.getSystemService(DevicePolicyManager.class); + mUserManager = activity.getSystemService(UserManager.class); + mKeyguardManager = activity.getSystemService(KeyguardManager.class); + mTrustAllCaUserId = activity.getIntent().getIntExtra(ARG_SHOW_NEW_FOR_USER, + UserHandle.USER_NULL); + mConfirmedCredentialUsers = new ArraySet<>(2); + mConfirmingCredentialUser = UserHandle.USER_NULL; + if (savedInstanceState != null) { + mConfirmingCredentialUser = savedInstanceState.getInt(SAVED_CONFIRMING_CREDENTIAL_USER, + UserHandle.USER_NULL); + ArrayList users = savedInstanceState.getIntegerArrayList( + SAVED_CONFIRMED_CREDENTIAL_USERS); + if (users != null) { + mConfirmedCredentialUsers.addAll(users); + } + } + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); + filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED); + activity.registerReceiver(mWorkProfileChangedReceiver, filter); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putIntegerArrayList(SAVED_CONFIRMED_CREDENTIAL_USERS, new ArrayList<>( + mConfirmedCredentialUsers)); + outState.putInt(SAVED_CONFIRMING_CREDENTIAL_USER, mConfirmingCredentialUser); + mGroupAdapter.saveState(outState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { + mFragmentView = (ViewGroup) inflater.inflate(R.layout.trusted_credentials, parent, false); + + ViewGroup contentView = mFragmentView.findViewById(R.id.content); + + mGroupAdapter = new GroupAdapter( + requireArguments().getInt(ARG_POSITION) == 0 ? Tab.SYSTEM : Tab.USER); + int profilesSize = mGroupAdapter.getGroupCount(); + for (int i = 0; i < profilesSize; i++) { + Bundle childState = savedInstanceState == null ? null + : savedInstanceState.getBundle(mGroupAdapter.getKey(i)); + createChildView(inflater, contentView, childState, i); + } + return mFragmentView; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.TRUSTED_CREDENTIALS; + } + + private void createChildView( + LayoutInflater inflater, ViewGroup parent, Bundle childState, int i) { + boolean isWork = mGroupAdapter.getUserInfoByGroup(i).isManagedProfile(); + ChildAdapter adapter = mGroupAdapter.createChildAdapter(i); + + LinearLayout containerView = (LinearLayout) inflater.inflate( + R.layout.trusted_credential_list_container, parent, false); + adapter.setContainerView(containerView, childState); + + int profilesSize = mGroupAdapter.getGroupCount(); + adapter.showHeader(profilesSize > 1); + adapter.showDivider(isWork); + adapter.setExpandIfAvailable(profilesSize <= 2 || !isWork, childState); + if (isWork) { + parent.addView(containerView); + } else { + parent.addView(containerView, 0); + } + } + + @Override + public void onResume() { + super.onResume(); + mFragmentView.requestLayout(); + } + + @Override + public void onDestroy() { + getActivity().unregisterReceiver(mWorkProfileChangedReceiver); + for (AdapterData.AliasLoader aliasLoader : mAliasLoaders) { + aliasLoader.cancel(true); + } + mAliasLoaders.clear(); + if (mAliasOperation != null) { + mAliasOperation.cancel(true); + mAliasOperation = null; + } + closeKeyChainConnections(); + super.onDestroy(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CONFIRM_CREDENTIALS) { + int userId = mConfirmingCredentialUser; + IntConsumer listener = mConfirmingCredentialListener; + // reset them before calling the listener because the listener may call back to start + // activity again. (though it should never happen.) + mConfirmingCredentialUser = UserHandle.USER_NULL; + mConfirmingCredentialListener = null; + if (resultCode == Activity.RESULT_OK) { + mConfirmedCredentialUsers.add(userId); + if (listener != null) { + listener.accept(userId); + } + } + } + } + + private void closeKeyChainConnections() { + synchronized (mKeyChainConnectionByProfileId) { + int n = mKeyChainConnectionByProfileId.size(); + for (int i = 0; i < n; ++i) { + mKeyChainConnectionByProfileId.valueAt(i).close(); + } + mKeyChainConnectionByProfileId.clear(); + } + } + + /** + * Start work challenge activity. + * + * @return true if screenlock exists + */ + private boolean startConfirmCredential(int userId) { + Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null, userId); + if (newIntent == null) { + return false; + } + mConfirmingCredentialUser = userId; + startActivityForResult(newIntent, REQUEST_CONFIRM_CREDENTIALS); + return true; + } + + /** + * Adapter for expandable list view of certificates. Groups in the view correspond to profiles + * whereas children correspond to certificates. + */ + private class GroupAdapter extends BaseExpandableListAdapter implements + ExpandableListView.OnGroupClickListener, ExpandableListView.OnChildClickListener { + private final AdapterData mData; + private final ArrayList mChildAdapters = new ArrayList<>(); + + private GroupAdapter(Tab tab) { + mData = new AdapterData(tab, this); + load(); + } + + @Override + public int getGroupCount() { + return mData.mCertHoldersByUserId.size(); + } + + @Override + public int getChildrenCount(int groupPosition) { + List certHolders = mData.mCertHoldersByUserId.valueAt(groupPosition); + if (certHolders != null) { + return certHolders.size(); + } + return 0; + } + + @Override + public UserHandle getGroup(int groupPosition) { + return new UserHandle(mData.mCertHoldersByUserId.keyAt(groupPosition)); + } + + @Override + public CertHolder getChild(int groupPosition, int childPosition) { + return mData.mCertHoldersByUserId.get(getUserIdByGroup(groupPosition)).get( + childPosition); + } + + @Override + public long getGroupId(int groupPosition) { + return getUserIdByGroup(groupPosition); + } + + private int getUserIdByGroup(int groupPosition) { + return mData.mCertHoldersByUserId.keyAt(groupPosition); + } + + public UserInfo getUserInfoByGroup(int groupPosition) { + return mUserManager.getUserInfo(getUserIdByGroup(groupPosition)); + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, + ViewGroup parent) { + if (convertView == null) { + LayoutInflater inflater = (LayoutInflater) getActivity() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + convertView = Utils.inflateCategoryHeader(inflater, parent); + } + + TextView title = convertView.findViewById(android.R.id.title); + if (getUserInfoByGroup(groupPosition).isManagedProfile()) { + title.setText(mDevicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER, + () -> getString(R.string.category_work))); + } else { + title.setText(mDevicePolicyManager.getResources().getString( + PERSONAL_CATEGORY_HEADER, + () -> getString(R.string.category_personal))); + + } + title.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END); + + return convertView; + } + + @Override + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, + View convertView, ViewGroup parent) { + return getViewForCertificate(getChild(groupPosition, childPosition), mData.mTab, + convertView, parent); + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + @Override + public boolean onChildClick(ExpandableListView expandableListView, View view, + int groupPosition, int childPosition, long id) { + showCertDialog(getChild(groupPosition, childPosition)); + return true; + } + + @Override + public boolean onGroupClick(ExpandableListView expandableListView, View view, + int groupPosition, long id) { + return !checkGroupExpandableAndStartWarningActivity(groupPosition); + } + + public void load() { + mData.new AliasLoader().execute(); + } + + public void remove(CertHolder certHolder) { + mData.remove(certHolder); + } + + ChildAdapter createChildAdapter(int groupPosition) { + ChildAdapter childAdapter = new ChildAdapter(this, groupPosition); + mChildAdapters.add(childAdapter); + return childAdapter; + } + + public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition) { + return checkGroupExpandableAndStartWarningActivity(groupPosition, true); + } + + public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition, + boolean startActivity) { + UserHandle groupUser = getGroup(groupPosition); + int groupUserId = groupUser.getIdentifier(); + if (mUserManager.isQuietModeEnabled(groupUser)) { + if (startActivity) { + Intent intent = + UnlaunchableAppActivity.createInQuietModeDialogIntent(groupUserId); + getActivity().startActivity(intent); + } + return false; + } else if (!mUserManager.isUserUnlocked(groupUser)) { + LockPatternUtils lockPatternUtils = new LockPatternUtils(getActivity()); + if (lockPatternUtils.isSeparateProfileChallengeEnabled(groupUserId)) { + if (startActivity) { + startConfirmCredential(groupUserId); + } + return false; + } + } + return true; + } + + private View getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView, + ViewGroup parent) { + ViewHolder holder; + if (convertView == null) { + holder = new ViewHolder(); + LayoutInflater inflater = LayoutInflater.from(getActivity()); + convertView = inflater.inflate(R.layout.trusted_credential, parent, false); + convertView.setTag(holder); + holder.mSubjectPrimaryView = + convertView.findViewById(R.id.trusted_credential_subject_primary); + holder.mSubjectSecondaryView = + convertView.findViewById(R.id.trusted_credential_subject_secondary); + holder.mSwitch = convertView.findViewById(R.id.trusted_credential_status); + holder.mSwitch.setOnClickListener(view -> { + removeOrInstallCert((CertHolder) view.getTag()); + }); + } else { + holder = (ViewHolder) convertView.getTag(); + } + holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary); + holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary); + if (mTab.mSwitch) { + holder.mSwitch.setChecked(!certHolder.mDeleted); + holder.mSwitch.setEnabled(!mUserManager.hasUserRestriction( + UserManager.DISALLOW_CONFIG_CREDENTIALS, + new UserHandle(certHolder.mProfileId))); + holder.mSwitch.setVisibility(View.VISIBLE); + holder.mSwitch.setTag(certHolder); + } + return convertView; + } + + private void saveState(Bundle outState) { + for (int groupPosition = 0, mChildAdaptersSize = mChildAdapters.size(); + groupPosition < mChildAdaptersSize; groupPosition++) { + ChildAdapter childAdapter = mChildAdapters.get(groupPosition); + outState.putBundle(getKey(groupPosition), childAdapter.saveState()); + } + } + + @NonNull + private String getKey(int groupPosition) { + return "Group" + getUserIdByGroup(groupPosition); + } + + private class ViewHolder { + private TextView mSubjectPrimaryView; + private TextView mSubjectSecondaryView; + private Switch mSwitch; + } + } + + private class ChildAdapter extends BaseAdapter implements View.OnClickListener, + AdapterView.OnItemClickListener { + private static final String KEY_CONTAINER = "Container"; + private static final String KEY_IS_LIST_EXPANDED = "IsListExpanded"; + private final int[] mGroupExpandedStateSet = {com.android.internal.R.attr.state_expanded}; + private final int[] mEmptyStateSet = {}; + private final LinearLayout.LayoutParams mHideContainerLayoutParams = + new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0f); + private final LinearLayout.LayoutParams mHideListLayoutParams = + new LinearLayout.LayoutParams(MATCH_PARENT, 0); + private final LinearLayout.LayoutParams mShowLayoutParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, MATCH_PARENT, 1f); + private final GroupAdapter mParent; + private final int mGroupPosition; + /* + * This class doesn't hold the actual data. Events should notify parent. + * When notifying DataSet events in this class, events should be forwarded to mParent. + * i.e. this.notifyDataSetChanged -> mParent.notifyDataSetChanged -> mObserver.onChanged + * -> outsideObservers.onChanged() (e.g. ListView) + */ + private final DataSetObserver mObserver = new DataSetObserver() { + @Override + public void onChanged() { + super.onChanged(); + TrustedCredentialsFragment.ChildAdapter.super.notifyDataSetChanged(); + } + + @Override + public void onInvalidated() { + super.onInvalidated(); + TrustedCredentialsFragment.ChildAdapter.super.notifyDataSetInvalidated(); + } + }; + + private boolean mIsListExpanded = true; + private LinearLayout mContainerView; + private ViewGroup mHeaderView; + private ListView mListView; + private ImageView mIndicatorView; + + private ChildAdapter(GroupAdapter parent, int groupPosition) { + mParent = parent; + mGroupPosition = groupPosition; + mParent.registerDataSetObserver(mObserver); + } + + @Override + public int getCount() { + return mParent.getChildrenCount(mGroupPosition); + } + + @Override + public CertHolder getItem(int position) { + return mParent.getChild(mGroupPosition, position); + } + + @Override + public long getItemId(int position) { + return mParent.getChildId(mGroupPosition, position); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return mParent.getChildView(mGroupPosition, position, false, convertView, parent); + } + + // DataSet events + @Override + public void notifyDataSetChanged() { + // Don't call super as the parent will propagate this event back later in mObserver + mParent.notifyDataSetChanged(); + } + + @Override + public void notifyDataSetInvalidated() { + // Don't call super as the parent will propagate this event back later in mObserver + mParent.notifyDataSetInvalidated(); + } + + // View related codes + @Override + public void onClick(View view) { + mIsListExpanded = checkGroupExpandableAndStartWarningActivity() && !mIsListExpanded; + refreshViews(); + } + + @Override + public void onItemClick(AdapterView adapterView, View view, int pos, long id) { + showCertDialog(getItem(pos)); + } + + public void setContainerView(LinearLayout containerView, Bundle savedState) { + mContainerView = containerView; + // Handle manually because multiple groups with same id elements. + mContainerView.setSaveFromParentEnabled(false); + + mListView = mContainerView.findViewById(R.id.cert_list); + mListView.setAdapter(this); + mListView.setOnItemClickListener(this); + mListView.setItemsCanFocus(true); + + mHeaderView = mContainerView.findViewById(R.id.header_view); + mHeaderView.setOnClickListener(this); + + mIndicatorView = mHeaderView.findViewById(R.id.group_indicator); + mIndicatorView.setImageDrawable(getGroupIndicator()); + + FrameLayout headerContentContainer = + mHeaderView.findViewById(R.id.header_content_container); + headerContentContainer.addView( + mParent.getGroupView(mGroupPosition, true /* parent ignores it */, null, + headerContentContainer)); + + if (savedState != null) { + SparseArray containerStates = + savedState.getSparseParcelableArray(KEY_CONTAINER, Parcelable.class); + if (containerStates != null) { + mContainerView.restoreHierarchyState(containerStates); + } + } + } + + public void showHeader(boolean showHeader) { + mHeaderView.setVisibility(showHeader ? View.VISIBLE : View.GONE); + } + + public void showDivider(boolean showDivider) { + View dividerView = mHeaderView.findViewById(R.id.header_divider); + dividerView.setVisibility(showDivider ? View.VISIBLE : View.GONE); + } + + public void setExpandIfAvailable(boolean expanded, Bundle savedState) { + if (savedState != null) { + expanded = savedState.getBoolean(KEY_IS_LIST_EXPANDED); + } + mIsListExpanded = expanded && mParent.checkGroupExpandableAndStartWarningActivity( + mGroupPosition, false /* startActivity */); + refreshViews(); + } + + private boolean checkGroupExpandableAndStartWarningActivity() { + return mParent.checkGroupExpandableAndStartWarningActivity(mGroupPosition); + } + + private void refreshViews() { + mIndicatorView.setImageState(mIsListExpanded ? mGroupExpandedStateSet + : mEmptyStateSet, false); + mListView.setLayoutParams(mIsListExpanded ? mShowLayoutParams + : mHideListLayoutParams); + mContainerView.setLayoutParams(mIsListExpanded ? mShowLayoutParams + : mHideContainerLayoutParams); + } + + // Get group indicator from styles of ExpandableListView + private Drawable getGroupIndicator() { + TypedArray a = getActivity().obtainStyledAttributes(null, + com.android.internal.R.styleable.ExpandableListView, + com.android.internal.R.attr.expandableListViewStyle, 0); + Drawable groupIndicator = a.getDrawable( + com.android.internal.R.styleable.ExpandableListView_groupIndicator); + a.recycle(); + return groupIndicator; + } + + private Bundle saveState() { + Bundle bundle = new Bundle(); + SparseArray states = new SparseArray<>(); + mContainerView.saveHierarchyState(states); + bundle.putSparseParcelableArray(KEY_CONTAINER, states); + bundle.putBoolean(KEY_IS_LIST_EXPANDED, mIsListExpanded); + return bundle; + } + } + + private class AdapterData { + private final SparseArray> mCertHoldersByUserId = + new SparseArray<>(); + private final Tab mTab; + private final GroupAdapter mAdapter; + + private AdapterData(Tab tab, GroupAdapter adapter) { + mAdapter = adapter; + mTab = tab; + } + + private class AliasLoader extends AsyncTask>> { + private ProgressBar mProgressBar; + private View mContentView; + private Context mContext; + + AliasLoader() { + mContext = getActivity(); + mAliasLoaders.add(this); + List profiles = mUserManager.getUserProfiles(); + for (UserHandle profile : profiles) { + mCertHoldersByUserId.put(profile.getIdentifier(), new ArrayList<>()); + } + } + + private boolean shouldSkipProfile(UserHandle userHandle) { + return mUserManager.isQuietModeEnabled(userHandle) + || !mUserManager.isUserUnlocked(userHandle.getIdentifier()); + } + + @Override + protected void onPreExecute() { + mProgressBar = mFragmentView.findViewById(R.id.progress); + mContentView = mFragmentView.findViewById(R.id.content); + mProgressBar.setVisibility(View.VISIBLE); + mContentView.setVisibility(View.GONE); + } + + @Override + protected SparseArray> doInBackground(Void... params) { + SparseArray> certHoldersByProfile = + new SparseArray<>(); + try { + synchronized (mKeyChainConnectionByProfileId) { + List profiles = mUserManager.getUserProfiles(); + // First we get all aliases for all profiles in order to show progress + // correctly. Otherwise this could all be in a single loop. + SparseArray> aliasesByProfileId = + new SparseArray<>(profiles.size()); + int max = 0; + int progress = 0; + for (UserHandle profile : profiles) { + int profileId = profile.getIdentifier(); + if (shouldSkipProfile(profile)) { + continue; + } + KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, + profile); + // Saving the connection for later use on the certificate dialog. + mKeyChainConnectionByProfileId.put(profileId, keyChainConnection); + IKeyChainService service = keyChainConnection.getService(); + List aliases = mTab.getAliases(service); + if (isCancelled()) { + return new SparseArray<>(); + } + max += aliases.size(); + aliasesByProfileId.put(profileId, aliases); + } + for (UserHandle profile : profiles) { + int profileId = profile.getIdentifier(); + List aliases = aliasesByProfileId.get(profileId); + if (isCancelled()) { + return new SparseArray<>(); + } + KeyChainConnection keyChainConnection = + mKeyChainConnectionByProfileId.get( + profileId); + if (shouldSkipProfile(profile) || aliases == null + || keyChainConnection == null) { + certHoldersByProfile.put(profileId, new ArrayList<>(0)); + continue; + } + IKeyChainService service = keyChainConnection.getService(); + List certHolders = new ArrayList<>(max); + for (String alias : aliases) { + byte[] encodedCertificate = service.getEncodedCaCertificate(alias, + true); + X509Certificate cert = KeyChain.toCertificate(encodedCertificate); + certHolders.add(new CertHolder(service, mAdapter, + mTab, alias, cert, profileId)); + publishProgress(++progress, max); + } + Collections.sort(certHolders); + certHoldersByProfile.put(profileId, certHolders); + } + return certHoldersByProfile; + } + } catch (RemoteException e) { + Log.e(TAG, "Remote exception while loading aliases.", e); + return new SparseArray<>(); + } catch (InterruptedException e) { + Log.e(TAG, "InterruptedException while loading aliases.", e); + return new SparseArray<>(); + } + } + + @Override + protected void onProgressUpdate(Integer... progressAndMax) { + int progress = progressAndMax[0]; + int max = progressAndMax[1]; + if (max != mProgressBar.getMax()) { + mProgressBar.setMax(max); + } + mProgressBar.setProgress(progress); + } + + @Override + protected void onPostExecute(SparseArray> certHolders) { + mCertHoldersByUserId.clear(); + int n = certHolders.size(); + for (int i = 0; i < n; ++i) { + mCertHoldersByUserId.put(certHolders.keyAt(i), certHolders.valueAt(i)); + } + mAdapter.notifyDataSetChanged(); + mProgressBar.setVisibility(View.GONE); + mContentView.setVisibility(View.VISIBLE); + mProgressBar.setProgress(0); + mAliasLoaders.remove(this); + showTrustAllCaDialogIfNeeded(); + } + + private boolean isUserTabAndTrustAllCertMode() { + return isTrustAllCaCertModeInProgress() && mTab == Tab.USER; + } + + @UiThread + private void showTrustAllCaDialogIfNeeded() { + if (!isUserTabAndTrustAllCertMode()) { + return; + } + List certHolders = mCertHoldersByUserId.get(mTrustAllCaUserId); + if (certHolders == null) { + return; + } + + List unapprovedUserCertHolders = new ArrayList<>(); + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + for (CertHolder cert : certHolders) { + if (cert != null && !dpm.isCaCertApproved(cert.mAlias, mTrustAllCaUserId)) { + unapprovedUserCertHolders.add(cert); + } + } + + if (unapprovedUserCertHolders.size() == 0) { + Log.w(TAG, "no cert is pending approval for user " + mTrustAllCaUserId); + return; + } + showTrustAllCaDialog(unapprovedUserCertHolders); + } + } + + public void remove(CertHolder certHolder) { + if (mCertHoldersByUserId != null) { + List certs = mCertHoldersByUserId.get(certHolder.mProfileId); + if (certs != null) { + certs.remove(certHolder); + } + } + } + } + + /* package */ static class CertHolder implements Comparable { + public int mProfileId; + private final IKeyChainService mService; + private final GroupAdapter mAdapter; + private final Tab mTab; + private final String mAlias; + private final X509Certificate mX509Cert; + + private final SslCertificate mSslCert; + private final String mSubjectPrimary; + private final String mSubjectSecondary; + private boolean mDeleted; + + private CertHolder(IKeyChainService service, + GroupAdapter adapter, + Tab tab, + String alias, + X509Certificate x509Cert, + int profileId) { + mProfileId = profileId; + mService = service; + mAdapter = adapter; + mTab = tab; + mAlias = alias; + mX509Cert = x509Cert; + + mSslCert = new SslCertificate(x509Cert); + + String cn = mSslCert.getIssuedTo().getCName(); + String o = mSslCert.getIssuedTo().getOName(); + String ou = mSslCert.getIssuedTo().getUName(); + // if we have a O, use O as primary subject, secondary prefer CN over OU + // if we don't have an O, use CN as primary, empty secondary + // if we don't have O or CN, use DName as primary, empty secondary + if (!o.isEmpty()) { + if (!cn.isEmpty()) { + mSubjectPrimary = o; + mSubjectSecondary = cn; + } else { + mSubjectPrimary = o; + mSubjectSecondary = ou; + } + } else { + if (!cn.isEmpty()) { + mSubjectPrimary = cn; + mSubjectSecondary = ""; + } else { + mSubjectPrimary = mSslCert.getIssuedTo().getDName(); + mSubjectSecondary = ""; + } + } + try { + mDeleted = mTab.deleted(mService, mAlias); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception while checking if alias " + mAlias + " is deleted.", + e); + mDeleted = false; + } + } + + @Override + public int compareTo(CertHolder o) { + int primary = this.mSubjectPrimary.compareToIgnoreCase(o.mSubjectPrimary); + if (primary != 0) { + return primary; + } + return this.mSubjectSecondary.compareToIgnoreCase(o.mSubjectSecondary); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof CertHolder)) { + return false; + } + CertHolder other = (CertHolder) o; + return mAlias.equals(other.mAlias); + } + + @Override + public int hashCode() { + return mAlias.hashCode(); + } + + public int getUserId() { + return mProfileId; + } + + public String getAlias() { + return mAlias; + } + + public boolean isSystemCert() { + return mTab == Tab.SYSTEM; + } + + public boolean isDeleted() { + return mDeleted; + } + } + + + private boolean isTrustAllCaCertModeInProgress() { + return mTrustAllCaUserId != UserHandle.USER_NULL; + } + + private void showTrustAllCaDialog(List unapprovedCertHolders) { + CertHolder[] arr = + unapprovedCertHolders.toArray(new CertHolder[unapprovedCertHolders.size()]); + new TrustedCredentialsDialogBuilder(getActivity(), this) + .setCertHolders(arr) + .setOnDismissListener(dialogInterface -> { + // Avoid starting dialog again after Activity restart. + getActivity().getIntent().removeExtra(ARG_SHOW_NEW_FOR_USER); + mTrustAllCaUserId = UserHandle.USER_NULL; + }) + .show(); + } + + private void showCertDialog(final CertHolder certHolder) { + new TrustedCredentialsDialogBuilder(getActivity(), this) + .setCertHolder(certHolder) + .show(); + } + + @Override + public List getX509CertsFromCertHolder(CertHolder certHolder) { + List certificates = null; + try { + synchronized (mKeyChainConnectionByProfileId) { + KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get( + certHolder.mProfileId); + IKeyChainService service = keyChainConnection.getService(); + List chain = service.getCaCertificateChainAliases(certHolder.mAlias, true); + certificates = new ArrayList<>(chain.size()); + for (String s : chain) { + byte[] encodedCertificate = service.getEncodedCaCertificate(s, true); + X509Certificate certificate = KeyChain.toCertificate(encodedCertificate); + certificates.add(certificate); + } + } + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException while retrieving certificate chain for root " + + certHolder.mAlias, ex); + } + return certificates; + } + + @Override + public void removeOrInstallCert(CertHolder certHolder) { + new AliasOperation(certHolder).execute(); + } + + @Override + public boolean startConfirmCredentialIfNotConfirmed(int userId, + IntConsumer onCredentialConfirmedListener) { + if (mConfirmedCredentialUsers.contains(userId)) { + // Credential has been confirmed. Don't start activity. + return false; + } + + boolean result = startConfirmCredential(userId); + if (result) { + mConfirmingCredentialListener = onCredentialConfirmedListener; + } + return result; + } + + private class AliasOperation extends AsyncTask { + private final CertHolder mCertHolder; + + private AliasOperation(CertHolder certHolder) { + mCertHolder = certHolder; + mAliasOperation = this; + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + synchronized (mKeyChainConnectionByProfileId) { + KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get( + mCertHolder.mProfileId); + IKeyChainService service = keyChainConnection.getService(); + if (mCertHolder.mDeleted) { + byte[] bytes = mCertHolder.mX509Cert.getEncoded(); + service.installCaCertificate(bytes); + return true; + } else { + return service.deleteCaCertificate(mCertHolder.mAlias); + } + } + } catch (CertificateEncodingException | SecurityException | IllegalStateException + | RemoteException e) { + Log.w(TAG, "Error while toggling alias " + mCertHolder.mAlias, e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean ok) { + if (ok) { + if (mCertHolder.mTab.mSwitch) { + mCertHolder.mDeleted = !mCertHolder.mDeleted; + } else { + mCertHolder.mAdapter.remove(mCertHolder); + } + mCertHolder.mAdapter.notifyDataSetChanged(); + } else { + // bail, reload to reset to known state + mCertHolder.mAdapter.load(); + } + mAliasOperation = null; + } + } +} diff --git a/src/com/android/settings/TrustedCredentialsSettings.java b/src/com/android/settings/TrustedCredentialsSettings.java index 735cb3ba650..a88019ed9e4 100644 --- a/src/com/android/settings/TrustedCredentialsSettings.java +++ b/src/com/android/settings/TrustedCredentialsSettings.java @@ -16,121 +16,114 @@ package com.android.settings; -import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER; -import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER; -import static android.widget.LinearLayout.LayoutParams.MATCH_PARENT; -import static android.widget.LinearLayout.LayoutParams.WRAP_CONTENT; - -import android.animation.LayoutTransition; -import android.annotation.UiThread; -import android.app.Activity; -import android.app.KeyguardManager; -import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.UserInfo; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.drawable.Drawable; -import android.net.http.SslCertificate; -import android.os.AsyncTask; import android.os.Bundle; import android.os.RemoteException; -import android.os.UserHandle; -import android.os.UserManager; import android.security.IKeyChainService; -import android.security.KeyChain; -import android.security.KeyChain.KeyChainConnection; -import android.util.ArraySet; -import android.util.Log; -import android.util.SparseArray; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.BaseExpandableListAdapter; -import android.widget.ExpandableListView; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.Switch; -import android.widget.TabHost; -import android.widget.TextView; -import com.android.internal.annotations.GuardedBy; -import com.android.internal.app.UnlaunchableAppActivity; -import com.android.internal.widget.LockPatternUtils; -import com.android.settings.core.InstrumentedFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; + +import com.android.settings.dashboard.DashboardFragment; + +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; +import com.google.common.collect.ImmutableList; -import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Set; -import java.util.function.IntConsumer; -public class TrustedCredentialsSettings extends InstrumentedFragment - implements TrustedCredentialsDialogBuilder.DelegateInterface { - - public static final String ARG_SHOW_NEW_FOR_USER = "ARG_SHOW_NEW_FOR_USER"; +/** + * Main fragment to display trusted credentials settings. + */ +public class TrustedCredentialsSettings extends DashboardFragment { private static final String TAG = "TrustedCredentialsSettings"; - private DevicePolicyManager mDevicePolicyManager; - private UserManager mUserManager; - private KeyguardManager mKeyguardManager; - private int mTrustAllCaUserId; + public static final String ARG_SHOW_NEW_FOR_USER = "ARG_SHOW_NEW_FOR_USER"; + + static final ImmutableList TABS = ImmutableList.of(Tab.SYSTEM, Tab.USER); - private static final String SAVED_CONFIRMED_CREDENTIAL_USERS = "ConfirmedCredentialUsers"; - private static final String SAVED_CONFIRMING_CREDENTIAL_USER = "ConfirmingCredentialUser"; private static final String USER_ACTION = "com.android.settings.TRUSTED_CREDENTIALS_USER"; - private static final int REQUEST_CONFIRM_CREDENTIALS = 1; @Override public int getMetricsCategory() { return SettingsEnums.TRUSTED_CREDENTIALS; } - private enum Tab { - SYSTEM("system", - R.string.trusted_credentials_system_tab, - R.id.system_tab, - R.id.system_progress, - R.id.system_content, - true), - USER("user", - R.string.trusted_credentials_user_tab, - R.id.user_tab, - R.id.user_progress, - R.id.user_content, - false); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActivity().setTitle(R.string.trusted_credentials); + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.placeholder_preference_screen; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + View tabContainer = view.findViewById(R.id.tab_container); + tabContainer.setVisibility(View.VISIBLE); + + ViewPager2 viewPager = tabContainer.findViewById(R.id.view_pager); + viewPager.setAdapter(new FragmentAdapter(this)); + viewPager.setUserInputEnabled(false); + + Intent intent = getActivity().getIntent(); + if (intent != null && USER_ACTION.equals(intent.getAction())) { + viewPager.setCurrentItem(TABS.indexOf(Tab.USER), false); + } + + TabLayout tabLayout = tabContainer.findViewById(R.id.tabs); + new TabLayoutMediator(tabLayout, viewPager, false, false, + (tab, position) -> tab.setText(TABS.get(position).mLabel)).attach(); + } + + private static class FragmentAdapter extends FragmentStateAdapter { + FragmentAdapter(Fragment fragment) { + super(fragment); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + TrustedCredentialsFragment fragment = new TrustedCredentialsFragment(); + Bundle args = new Bundle(); + args.putInt(TrustedCredentialsFragment.ARG_POSITION, position); + fragment.setArguments(args); + return fragment; + } + + @Override + public int getItemCount() { + return TrustedCredentialsSettings.TABS.size(); + } + } + + enum Tab { + SYSTEM(R.string.trusted_credentials_system_tab, true), + USER(R.string.trusted_credentials_user_tab, false); - private final String mTag; private final int mLabel; - private final int mView; - private final int mProgress; - private final int mContentView; - private final boolean mSwitch; + final boolean mSwitch; - private Tab(String tag, int label, int view, int progress, int contentView, - boolean withSwitch) { - mTag = tag; + Tab(int label, boolean withSwitch) { mLabel = label; - mView = view; - mProgress = progress; - mContentView = contentView; mSwitch = withSwitch; } - private List getAliases(IKeyChainService service) throws RemoteException { + List getAliases(IKeyChainService service) throws RemoteException { switch (this) { case SYSTEM: { return service.getSystemCaAliases().getList(); @@ -140,7 +133,8 @@ public class TrustedCredentialsSettings extends InstrumentedFragment } throw new AssertionError(); } - private boolean deleted(IKeyChainService service, String alias) throws RemoteException { + + boolean deleted(IKeyChainService service, String alias) throws RemoteException { switch (this) { case SYSTEM: return !service.containsCaAlias(alias); @@ -150,895 +144,4 @@ public class TrustedCredentialsSettings extends InstrumentedFragment throw new AssertionError(); } } - - private TabHost mTabHost; - private ArrayList mGroupAdapters = new ArrayList<>(2); - private AliasOperation mAliasOperation; - private ArraySet mConfirmedCredentialUsers; - private int mConfirmingCredentialUser; - private IntConsumer mConfirmingCredentialListener; - private Set mAliasLoaders = new ArraySet(2); - @GuardedBy("mKeyChainConnectionByProfileId") - private final SparseArray - mKeyChainConnectionByProfileId = new SparseArray(); - - private BroadcastReceiver mWorkProfileChangedReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || - Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || - Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { - for (GroupAdapter adapter : mGroupAdapters) { - adapter.load(); - } - } - } - - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Activity activity = getActivity(); - mDevicePolicyManager = activity.getSystemService(DevicePolicyManager.class); - mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE); - mKeyguardManager = (KeyguardManager) activity - .getSystemService(Context.KEYGUARD_SERVICE); - mTrustAllCaUserId = activity.getIntent().getIntExtra(ARG_SHOW_NEW_FOR_USER, - UserHandle.USER_NULL); - mConfirmedCredentialUsers = new ArraySet<>(2); - mConfirmingCredentialUser = UserHandle.USER_NULL; - if (savedInstanceState != null) { - mConfirmingCredentialUser = savedInstanceState.getInt(SAVED_CONFIRMING_CREDENTIAL_USER, - UserHandle.USER_NULL); - ArrayList users = savedInstanceState.getIntegerArrayList( - SAVED_CONFIRMED_CREDENTIAL_USERS); - if (users != null) { - mConfirmedCredentialUsers.addAll(users); - } - } - - mConfirmingCredentialListener = null; - - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED); - activity.registerReceiver(mWorkProfileChangedReceiver, filter); - - activity.setTitle(R.string.trusted_credentials); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putIntegerArrayList(SAVED_CONFIRMED_CREDENTIAL_USERS, new ArrayList<>( - mConfirmedCredentialUsers)); - outState.putInt(SAVED_CONFIRMING_CREDENTIAL_USER, mConfirmingCredentialUser); - } - - @Override public View onCreateView( - LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { - mTabHost = (TabHost) inflater.inflate(R.layout.trusted_credentials, parent, false); - mTabHost.setup(); - addTab(Tab.SYSTEM); - // TODO add Install button on Tab.USER to go to CertInstaller like KeyChainActivity - addTab(Tab.USER); - if (getActivity().getIntent() != null && - USER_ACTION.equals(getActivity().getIntent().getAction())) { - mTabHost.setCurrentTabByTag(Tab.USER.mTag); - } - return mTabHost; - } - @Override - public void onDestroy() { - getActivity().unregisterReceiver(mWorkProfileChangedReceiver); - for (AdapterData.AliasLoader aliasLoader : mAliasLoaders) { - aliasLoader.cancel(true); - } - mAliasLoaders.clear(); - mGroupAdapters.clear(); - if (mAliasOperation != null) { - mAliasOperation.cancel(true); - mAliasOperation = null; - } - closeKeyChainConnections(); - super.onDestroy(); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == REQUEST_CONFIRM_CREDENTIALS) { - int userId = mConfirmingCredentialUser; - IntConsumer listener = mConfirmingCredentialListener; - // reset them before calling the listener because the listener may call back to start - // activity again. (though it should never happen.) - mConfirmingCredentialUser = UserHandle.USER_NULL; - mConfirmingCredentialListener = null; - if (resultCode == Activity.RESULT_OK) { - mConfirmedCredentialUsers.add(userId); - if (listener != null) { - listener.accept(userId); - } - } - } - } - - private void closeKeyChainConnections() { - synchronized (mKeyChainConnectionByProfileId) { - final int n = mKeyChainConnectionByProfileId.size(); - for (int i = 0; i < n; ++i) { - mKeyChainConnectionByProfileId.valueAt(i).close(); - } - mKeyChainConnectionByProfileId.clear(); - } - } - - private void addTab(Tab tab) { - TabHost.TabSpec systemSpec = mTabHost.newTabSpec(tab.mTag) - .setIndicator(getActivity().getString(tab.mLabel)) - .setContent(tab.mView); - mTabHost.addTab(systemSpec); - - final GroupAdapter groupAdapter = new GroupAdapter(tab); - mGroupAdapters.add(groupAdapter); - final int profilesSize = groupAdapter.getGroupCount(); - - // Add a transition for non-visibility events like resizing the pane. - final ViewGroup contentView = (ViewGroup) mTabHost.findViewById(tab.mContentView); - contentView.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); - - final LayoutInflater inflater = LayoutInflater.from(getActivity()); - for (int i = 0; i < groupAdapter.getGroupCount(); i++) { - final boolean isWork = groupAdapter.getUserInfoByGroup(i).isManagedProfile(); - final ChildAdapter adapter = groupAdapter.getChildAdapter(i); - - final LinearLayout containerView = (LinearLayout) inflater - .inflate(R.layout.trusted_credential_list_container, contentView, false); - adapter.setContainerView(containerView); - - adapter.showHeader(profilesSize > 1); - adapter.showDivider(isWork); - adapter.setExpandIfAvailable(profilesSize <= 2 ? true : !isWork); - if (isWork) { - contentView.addView(containerView); - } else { - contentView.addView(containerView, 0); - } - } - } - - /** - * Start work challenge activity. - * @return true if screenlock exists - */ - private boolean startConfirmCredential(int userId) { - final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null, - userId); - if (newIntent == null) { - return false; - } - mConfirmingCredentialUser = userId; - startActivityForResult(newIntent, REQUEST_CONFIRM_CREDENTIALS); - return true; - } - - /** - * Adapter for expandable list view of certificates. Groups in the view correspond to profiles - * whereas children correspond to certificates. - */ - private class GroupAdapter extends BaseExpandableListAdapter implements - ExpandableListView.OnGroupClickListener, ExpandableListView.OnChildClickListener, - View.OnClickListener { - private final AdapterData mData; - - private GroupAdapter(Tab tab) { - mData = new AdapterData(tab, this); - load(); - } - - @Override - public int getGroupCount() { - return mData.mCertHoldersByUserId.size(); - } - @Override - public int getChildrenCount(int groupPosition) { - List certHolders = mData.mCertHoldersByUserId.valueAt(groupPosition); - if (certHolders != null) { - return certHolders.size(); - } - return 0; - } - @Override - public UserHandle getGroup(int groupPosition) { - return new UserHandle(mData.mCertHoldersByUserId.keyAt(groupPosition)); - } - @Override - public CertHolder getChild(int groupPosition, int childPosition) { - return mData.mCertHoldersByUserId.get(getUserIdByGroup(groupPosition)).get( - childPosition); - } - @Override - public long getGroupId(int groupPosition) { - return getUserIdByGroup(groupPosition); - } - private int getUserIdByGroup(int groupPosition) { - return mData.mCertHoldersByUserId.keyAt(groupPosition); - } - public UserInfo getUserInfoByGroup(int groupPosition) { - return mUserManager.getUserInfo(getUserIdByGroup(groupPosition)); - } - @Override - public long getChildId(int groupPosition, int childPosition) { - return childPosition; - } - @Override - public boolean hasStableIds() { - return false; - } - @Override - public View getGroupView(int groupPosition, boolean isExpanded, View convertView, - ViewGroup parent) { - if (convertView == null) { - LayoutInflater inflater = (LayoutInflater) getActivity() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = Utils.inflateCategoryHeader(inflater, parent); - } - - final TextView title = (TextView) convertView.findViewById(android.R.id.title); - if (getUserInfoByGroup(groupPosition).isManagedProfile()) { - title.setText(mDevicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER, - () -> getString(R.string.category_work))); - } else { - title.setText(mDevicePolicyManager.getResources().getString( - PERSONAL_CATEGORY_HEADER, - () -> getString(R.string.category_personal))); - - } - title.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END); - - return convertView; - } - @Override - public View getChildView(int groupPosition, int childPosition, boolean isLastChild, - View convertView, ViewGroup parent) { - return getViewForCertificate(getChild(groupPosition, childPosition), mData.mTab, - convertView, parent); - } - @Override - public boolean isChildSelectable(int groupPosition, int childPosition) { - return true; - } - - @Override - public boolean onChildClick(ExpandableListView expandableListView, View view, - int groupPosition, int childPosition, long id) { - showCertDialog(getChild(groupPosition, childPosition)); - return true; - } - - /** - * Called when the switch on a system certificate is clicked. This will toggle whether it - * is trusted as a credential. - */ - @Override - public void onClick(View view) { - CertHolder holder = (CertHolder) view.getTag(); - removeOrInstallCert(holder); - } - - @Override - public boolean onGroupClick(ExpandableListView expandableListView, View view, - int groupPosition, long id) { - return !checkGroupExpandableAndStartWarningActivity(groupPosition); - } - - public void load() { - mData.new AliasLoader().execute(); - } - - public void remove(CertHolder certHolder) { - mData.remove(certHolder); - } - - public void setExpandableListView(ExpandableListView lv) { - lv.setAdapter(this); - lv.setOnGroupClickListener(this); - lv.setOnChildClickListener(this); - lv.setVisibility(View.VISIBLE); - } - - public ChildAdapter getChildAdapter(int groupPosition) { - return new ChildAdapter(this, groupPosition); - } - - public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition) { - return checkGroupExpandableAndStartWarningActivity(groupPosition, true); - } - - public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition, - boolean startActivity) { - final UserHandle groupUser = getGroup(groupPosition); - final int groupUserId = groupUser.getIdentifier(); - if (mUserManager.isQuietModeEnabled(groupUser)) { - final Intent intent = UnlaunchableAppActivity.createInQuietModeDialogIntent( - groupUserId); - if (startActivity) { - getActivity().startActivity(intent); - } - return false; - } else if (!mUserManager.isUserUnlocked(groupUser)) { - final LockPatternUtils lockPatternUtils = new LockPatternUtils( - getActivity()); - if (lockPatternUtils.isSeparateProfileChallengeEnabled(groupUserId)) { - if (startActivity) { - startConfirmCredential(groupUserId); - } - return false; - } - } - return true; - } - - private View getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView, - ViewGroup parent) { - ViewHolder holder; - if (convertView == null) { - holder = new ViewHolder(); - LayoutInflater inflater = LayoutInflater.from(getActivity()); - convertView = inflater.inflate(R.layout.trusted_credential, parent, false); - convertView.setTag(holder); - holder.mSubjectPrimaryView = (TextView) - convertView.findViewById(R.id.trusted_credential_subject_primary); - holder.mSubjectSecondaryView = (TextView) - convertView.findViewById(R.id.trusted_credential_subject_secondary); - holder.mSwitch = (Switch) convertView.findViewById( - R.id.trusted_credential_status); - holder.mSwitch.setOnClickListener(this); - } else { - holder = (ViewHolder) convertView.getTag(); - } - holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary); - holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary); - if (mTab.mSwitch) { - holder.mSwitch.setChecked(!certHolder.mDeleted); - holder.mSwitch.setEnabled(!mUserManager.hasUserRestriction( - UserManager.DISALLOW_CONFIG_CREDENTIALS, - new UserHandle(certHolder.mProfileId))); - holder.mSwitch.setVisibility(View.VISIBLE); - holder.mSwitch.setTag(certHolder); - } - return convertView; - } - - private class ViewHolder { - private TextView mSubjectPrimaryView; - private TextView mSubjectSecondaryView; - private Switch mSwitch; - } - } - - private class ChildAdapter extends BaseAdapter implements View.OnClickListener, - AdapterView.OnItemClickListener { - private final int[] GROUP_EXPANDED_STATE_SET = {com.android.internal.R.attr.state_expanded}; - private final int[] EMPTY_STATE_SET = {}; - private final LinearLayout.LayoutParams HIDE_CONTAINER_LAYOUT_PARAMS = - new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0f); - private final LinearLayout.LayoutParams HIDE_LIST_LAYOUT_PARAMS = - new LinearLayout.LayoutParams(MATCH_PARENT, 0); - private final LinearLayout.LayoutParams SHOW_LAYOUT_PARAMS = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, MATCH_PARENT, 1f); - private final GroupAdapter mParent; - private final int mGroupPosition; - /* - * This class doesn't hold the actual data. Events should notify parent. - * When notifying DataSet events in this class, events should be forwarded to mParent. - * i.e. this.notifyDataSetChanged -> mParent.notifyDataSetChanged -> mObserver.onChanged - * -> outsideObservers.onChanged() (e.g. ListView) - */ - private final DataSetObserver mObserver = new DataSetObserver() { - @Override - public void onChanged() { - super.onChanged(); - ChildAdapter.super.notifyDataSetChanged(); - } - @Override - public void onInvalidated() { - super.onInvalidated(); - ChildAdapter.super.notifyDataSetInvalidated(); - } - }; - - private boolean mIsListExpanded = true; - private LinearLayout mContainerView; - private ViewGroup mHeaderView; - private ListView mListView; - private ImageView mIndicatorView; - - private ChildAdapter(GroupAdapter parent, int groupPosition) { - mParent = parent; - mGroupPosition = groupPosition; - mParent.registerDataSetObserver(mObserver); - } - - @Override public int getCount() { - return mParent.getChildrenCount(mGroupPosition); - } - @Override public CertHolder getItem(int position) { - return mParent.getChild(mGroupPosition, position); - } - @Override public long getItemId(int position) { - return mParent.getChildId(mGroupPosition, position); - } - @Override public View getView(int position, View convertView, ViewGroup parent) { - return mParent.getChildView(mGroupPosition, position, false, convertView, parent); - } - // DataSet events - @Override - public void notifyDataSetChanged() { - // Don't call super as the parent will propagate this event back later in mObserver - mParent.notifyDataSetChanged(); - } - @Override - public void notifyDataSetInvalidated() { - // Don't call super as the parent will propagate this event back later in mObserver - mParent.notifyDataSetInvalidated(); - } - - // View related codes - @Override - public void onClick(View view) { - mIsListExpanded = checkGroupExpandableAndStartWarningActivity() && !mIsListExpanded; - refreshViews(); - } - - @Override - public void onItemClick(AdapterView adapterView, View view, int pos, long id) { - showCertDialog(getItem(pos)); - } - - public void setContainerView(LinearLayout containerView) { - mContainerView = containerView; - - mListView = (ListView) mContainerView.findViewById(R.id.cert_list); - mListView.setAdapter(this); - mListView.setOnItemClickListener(this); - mListView.setItemsCanFocus(true); - - mHeaderView = (ViewGroup) mContainerView.findViewById(R.id.header_view); - mHeaderView.setOnClickListener(this); - - mIndicatorView = (ImageView) mHeaderView.findViewById(R.id.group_indicator); - mIndicatorView.setImageDrawable(getGroupIndicator()); - - FrameLayout headerContentContainer = (FrameLayout) - mHeaderView.findViewById(R.id.header_content_container); - headerContentContainer.addView( - mParent.getGroupView(mGroupPosition, true /* parent ignores it */, null, - headerContentContainer)); - } - - public void showHeader(boolean showHeader) { - mHeaderView.setVisibility(showHeader ? View.VISIBLE : View.GONE); - } - - public void showDivider(boolean showDivider) { - View dividerView = mHeaderView.findViewById(R.id.header_divider); - dividerView.setVisibility(showDivider ? View.VISIBLE : View.GONE ); - } - - public void setExpandIfAvailable(boolean expanded) { - mIsListExpanded = expanded && mParent.checkGroupExpandableAndStartWarningActivity( - mGroupPosition, false /* startActivity */); - refreshViews(); - } - - private boolean checkGroupExpandableAndStartWarningActivity() { - return mParent.checkGroupExpandableAndStartWarningActivity(mGroupPosition); - } - - private void refreshViews() { - mIndicatorView.setImageState(mIsListExpanded ? GROUP_EXPANDED_STATE_SET - : EMPTY_STATE_SET, false); - mListView.setLayoutParams(mIsListExpanded ? SHOW_LAYOUT_PARAMS - : HIDE_LIST_LAYOUT_PARAMS); - mContainerView.setLayoutParams(mIsListExpanded ? SHOW_LAYOUT_PARAMS - : HIDE_CONTAINER_LAYOUT_PARAMS); - } - - // Get group indicator from styles of ExpandableListView - private Drawable getGroupIndicator() { - final TypedArray a = getActivity().obtainStyledAttributes(null, - com.android.internal.R.styleable.ExpandableListView, - com.android.internal.R.attr.expandableListViewStyle, 0); - Drawable groupIndicator = a.getDrawable( - com.android.internal.R.styleable.ExpandableListView_groupIndicator); - a.recycle(); - return groupIndicator; - } - } - - private class AdapterData { - private final SparseArray> mCertHoldersByUserId = - new SparseArray>(); - private final Tab mTab; - private final GroupAdapter mAdapter; - - private AdapterData(Tab tab, GroupAdapter adapter) { - mAdapter = adapter; - mTab = tab; - } - - private class AliasLoader extends AsyncTask>> { - private ProgressBar mProgressBar; - private View mContentView; - private Context mContext; - - public AliasLoader() { - mContext = getActivity(); - mAliasLoaders.add(this); - List profiles = mUserManager.getUserProfiles(); - for (UserHandle profile : profiles) { - mCertHoldersByUserId.put(profile.getIdentifier(), new ArrayList()); - } - } - - private boolean shouldSkipProfile(UserHandle userHandle) { - return mUserManager.isQuietModeEnabled(userHandle) - || !mUserManager.isUserUnlocked(userHandle.getIdentifier()); - } - - @Override protected void onPreExecute() { - View content = mTabHost.getTabContentView(); - mProgressBar = (ProgressBar) content.findViewById(mTab.mProgress); - mContentView = content.findViewById(mTab.mContentView); - mProgressBar.setVisibility(View.VISIBLE); - mContentView.setVisibility(View.GONE); - } - @Override protected SparseArray> doInBackground(Void... params) { - SparseArray> certHoldersByProfile = - new SparseArray>(); - try { - synchronized(mKeyChainConnectionByProfileId) { - List profiles = mUserManager.getUserProfiles(); - final int n = profiles.size(); - // First we get all aliases for all profiles in order to show progress - // correctly. Otherwise this could all be in a single loop. - SparseArray> aliasesByProfileId = new SparseArray< - List>(n); - int max = 0; - int progress = 0; - for (int i = 0; i < n; ++i) { - UserHandle profile = profiles.get(i); - int profileId = profile.getIdentifier(); - if (shouldSkipProfile(profile)) { - continue; - } - KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, - profile); - // Saving the connection for later use on the certificate dialog. - mKeyChainConnectionByProfileId.put(profileId, keyChainConnection); - IKeyChainService service = keyChainConnection.getService(); - List aliases = mTab.getAliases(service); - if (isCancelled()) { - return new SparseArray>(); - } - max += aliases.size(); - aliasesByProfileId.put(profileId, aliases); - } - for (int i = 0; i < n; ++i) { - UserHandle profile = profiles.get(i); - int profileId = profile.getIdentifier(); - List aliases = aliasesByProfileId.get(profileId); - if (isCancelled()) { - return new SparseArray>(); - } - KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get( - profileId); - if (shouldSkipProfile(profile) || aliases == null - || keyChainConnection == null) { - certHoldersByProfile.put(profileId, new ArrayList(0)); - continue; - } - IKeyChainService service = keyChainConnection.getService(); - List certHolders = new ArrayList(max); - final int aliasMax = aliases.size(); - for (int j = 0; j < aliasMax; ++j) { - String alias = aliases.get(j); - byte[] encodedCertificate = service.getEncodedCaCertificate(alias, - true); - X509Certificate cert = KeyChain.toCertificate(encodedCertificate); - certHolders.add(new CertHolder(service, mAdapter, - mTab, alias, cert, profileId)); - publishProgress(++progress, max); - } - Collections.sort(certHolders); - certHoldersByProfile.put(profileId, certHolders); - } - return certHoldersByProfile; - } - } catch (RemoteException e) { - Log.e(TAG, "Remote exception while loading aliases.", e); - return new SparseArray>(); - } catch (InterruptedException e) { - Log.e(TAG, "InterruptedException while loading aliases.", e); - return new SparseArray>(); - } - } - @Override protected void onProgressUpdate(Integer... progressAndMax) { - int progress = progressAndMax[0]; - int max = progressAndMax[1]; - if (max != mProgressBar.getMax()) { - mProgressBar.setMax(max); - } - mProgressBar.setProgress(progress); - } - @Override protected void onPostExecute(SparseArray> certHolders) { - mCertHoldersByUserId.clear(); - final int n = certHolders.size(); - for (int i = 0; i < n; ++i) { - mCertHoldersByUserId.put(certHolders.keyAt(i), certHolders.valueAt(i)); - } - mAdapter.notifyDataSetChanged(); - mProgressBar.setVisibility(View.GONE); - mContentView.setVisibility(View.VISIBLE); - mProgressBar.setProgress(0); - mAliasLoaders.remove(this); - showTrustAllCaDialogIfNeeded(); - } - - private boolean isUserTabAndTrustAllCertMode() { - return isTrustAllCaCertModeInProgress() && mTab == Tab.USER; - } - - @UiThread - private void showTrustAllCaDialogIfNeeded() { - if (!isUserTabAndTrustAllCertMode()) { - return; - } - List certHolders = mCertHoldersByUserId.get(mTrustAllCaUserId); - if (certHolders == null) { - return; - } - - List unapprovedUserCertHolders = new ArrayList<>(); - final DevicePolicyManager dpm = mContext.getSystemService( - DevicePolicyManager.class); - for (CertHolder cert : certHolders) { - if (cert != null && !dpm.isCaCertApproved(cert.mAlias, mTrustAllCaUserId)) { - unapprovedUserCertHolders.add(cert); - } - } - - if (unapprovedUserCertHolders.size() == 0) { - Log.w(TAG, "no cert is pending approval for user " + mTrustAllCaUserId); - return; - } - showTrustAllCaDialog(unapprovedUserCertHolders); - } - } - - public void remove(CertHolder certHolder) { - if (mCertHoldersByUserId != null) { - final List certs = mCertHoldersByUserId.get(certHolder.mProfileId); - if (certs != null) { - certs.remove(certHolder); - } - } - } - } - - /* package */ static class CertHolder implements Comparable { - public int mProfileId; - private final IKeyChainService mService; - private final GroupAdapter mAdapter; - private final Tab mTab; - private final String mAlias; - private final X509Certificate mX509Cert; - - private final SslCertificate mSslCert; - private final String mSubjectPrimary; - private final String mSubjectSecondary; - private boolean mDeleted; - - private CertHolder(IKeyChainService service, - GroupAdapter adapter, - Tab tab, - String alias, - X509Certificate x509Cert, - int profileId) { - mProfileId = profileId; - mService = service; - mAdapter = adapter; - mTab = tab; - mAlias = alias; - mX509Cert = x509Cert; - - mSslCert = new SslCertificate(x509Cert); - - String cn = mSslCert.getIssuedTo().getCName(); - String o = mSslCert.getIssuedTo().getOName(); - String ou = mSslCert.getIssuedTo().getUName(); - // if we have a O, use O as primary subject, secondary prefer CN over OU - // if we don't have an O, use CN as primary, empty secondary - // if we don't have O or CN, use DName as primary, empty secondary - if (!o.isEmpty()) { - if (!cn.isEmpty()) { - mSubjectPrimary = o; - mSubjectSecondary = cn; - } else { - mSubjectPrimary = o; - mSubjectSecondary = ou; - } - } else { - if (!cn.isEmpty()) { - mSubjectPrimary = cn; - mSubjectSecondary = ""; - } else { - mSubjectPrimary = mSslCert.getIssuedTo().getDName(); - mSubjectSecondary = ""; - } - } - try { - mDeleted = mTab.deleted(mService, mAlias); - } catch (RemoteException e) { - Log.e(TAG, "Remote exception while checking if alias " + mAlias + " is deleted.", - e); - mDeleted = false; - } - } - @Override public int compareTo(CertHolder o) { - int primary = this.mSubjectPrimary.compareToIgnoreCase(o.mSubjectPrimary); - if (primary != 0) { - return primary; - } - return this.mSubjectSecondary.compareToIgnoreCase(o.mSubjectSecondary); - } - @Override public boolean equals(Object o) { - if (!(o instanceof CertHolder)) { - return false; - } - CertHolder other = (CertHolder) o; - return mAlias.equals(other.mAlias); - } - @Override public int hashCode() { - return mAlias.hashCode(); - } - - public int getUserId() { - return mProfileId; - } - - public String getAlias() { - return mAlias; - } - - public boolean isSystemCert() { - return mTab == Tab.SYSTEM; - } - - public boolean isDeleted() { - return mDeleted; - } - } - - - private boolean isTrustAllCaCertModeInProgress() { - return mTrustAllCaUserId != UserHandle.USER_NULL; - } - - private void showTrustAllCaDialog(List unapprovedCertHolders) { - final CertHolder[] arr = unapprovedCertHolders.toArray( - new CertHolder[unapprovedCertHolders.size()]); - new TrustedCredentialsDialogBuilder(getActivity(), this) - .setCertHolders(arr) - .setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialogInterface) { - // Avoid starting dialog again after Activity restart. - getActivity().getIntent().removeExtra(ARG_SHOW_NEW_FOR_USER); - mTrustAllCaUserId = UserHandle.USER_NULL; - } - }) - .show(); - } - - private void showCertDialog(final CertHolder certHolder) { - new TrustedCredentialsDialogBuilder(getActivity(), this) - .setCertHolder(certHolder) - .show(); - } - - @Override - public List getX509CertsFromCertHolder(CertHolder certHolder) { - List certificates = null; - try { - synchronized (mKeyChainConnectionByProfileId) { - KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get( - certHolder.mProfileId); - IKeyChainService service = keyChainConnection.getService(); - List chain = service.getCaCertificateChainAliases(certHolder.mAlias, true); - final int n = chain.size(); - certificates = new ArrayList(n); - for (int i = 0; i < n; ++i) { - byte[] encodedCertificate = service.getEncodedCaCertificate(chain.get(i), true); - X509Certificate certificate = KeyChain.toCertificate(encodedCertificate); - certificates.add(certificate); - } - } - } catch (RemoteException ex) { - Log.e(TAG, "RemoteException while retrieving certificate chain for root " - + certHolder.mAlias, ex); - } - return certificates; - } - - @Override - public void removeOrInstallCert(CertHolder certHolder) { - new AliasOperation(certHolder).execute(); - } - - @Override - public boolean startConfirmCredentialIfNotConfirmed(int userId, - IntConsumer onCredentialConfirmedListener) { - if (mConfirmedCredentialUsers.contains(userId)) { - // Credential has been confirmed. Don't start activity. - return false; - } - - boolean result = startConfirmCredential(userId); - if (result) { - mConfirmingCredentialListener = onCredentialConfirmedListener; - } - return result; - } - - private class AliasOperation extends AsyncTask { - private final CertHolder mCertHolder; - - private AliasOperation(CertHolder certHolder) { - mCertHolder = certHolder; - mAliasOperation = this; - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - synchronized (mKeyChainConnectionByProfileId) { - KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get( - mCertHolder.mProfileId); - IKeyChainService service = keyChainConnection.getService(); - if (mCertHolder.mDeleted) { - byte[] bytes = mCertHolder.mX509Cert.getEncoded(); - service.installCaCertificate(bytes); - return true; - } else { - return service.deleteCaCertificate(mCertHolder.mAlias); - } - } - } catch (CertificateEncodingException | SecurityException | IllegalStateException - | RemoteException e) { - Log.w(TAG, "Error while toggling alias " + mCertHolder.mAlias, e); - return false; - } - } - - @Override - protected void onPostExecute(Boolean ok) { - if (ok) { - if (mCertHolder.mTab.mSwitch) { - mCertHolder.mDeleted = !mCertHolder.mDeleted; - } else { - mCertHolder.mAdapter.remove(mCertHolder); - } - mCertHolder.mAdapter.notifyDataSetChanged(); - } else { - // bail, reload to reset to known state - mCertHolder.mAdapter.load(); - } - mAliasOperation = null; - } - } } From e431df2036153c739bfe9a6e589632dc5a16f1f4 Mon Sep 17 00:00:00 2001 From: ykhung Date: Fri, 15 Apr 2022 14:56:21 +0800 Subject: [PATCH 6/8] Remove singeLine attribute in the usage chart divider Remove the android:singleLine attribute in the expand divider layout to support other language to avoid the content is truncated. old screenshot: https://screenshot.googleplex.com/7JCnrQAXJM8zjMM Bug: 229265247 Test: N/A Change-Id: I9038e69a9ab035b4e0599ca20e11fbccd471f76f --- res/layout/preference_expand_divider.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/res/layout/preference_expand_divider.xml b/res/layout/preference_expand_divider.xml index ce3d2e76f26..9b766881183 100644 --- a/res/layout/preference_expand_divider.xml +++ b/res/layout/preference_expand_divider.xml @@ -34,7 +34,6 @@ android:layout_height="wrap_content" android:layout_weight="1" android:paddingEnd="4dp" - android:singleLine="true" android:textAlignment="viewStart" style="@style/PreferenceCategoryTitleTextStyle"/> From b50430e09db36b6b4c5b63841caeeed91d318cdd Mon Sep 17 00:00:00 2001 From: SongFerngWang Date: Fri, 15 Apr 2022 08:45:06 +0800 Subject: [PATCH 7/8] [LE unicast] Change the default value for LeContactSharingEnabled Bug: 229322144 Test: build pass. Change-Id: I7673397b9f04df8acb551bdad42dea1f3ece72ff --- .../bluetooth/BluetoothDetailsProfilesController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index 36b571897fa..f0809ad84be 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -92,7 +92,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll mProfilesContainer = (PreferenceCategory)screen.findPreference(getPreferenceKey()); mProfilesContainer.setLayoutResource(R.layout.preference_bluetooth_profile_category); mIsLeContactSharingEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, - SettingsUIDeviceConfig.BT_LE_AUDIO_CONTACT_SHARING_ENABLED, false); + SettingsUIDeviceConfig.BT_LE_AUDIO_CONTACT_SHARING_ENABLED, true); // Call refresh here even though it will get called later in onResume, to avoid the // list of switches appearing to "pop" into the page. refresh(); @@ -515,4 +515,4 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll public String getPreferenceKey() { return KEY_PROFILES_GROUP; } -} \ No newline at end of file +} From 1c6695951f13275b9c962b490da570f7c0da69ae Mon Sep 17 00:00:00 2001 From: ykhung Date: Fri, 15 Apr 2022 18:52:22 +0800 Subject: [PATCH 8/8] Use the custom name in the battery attribution if it is unknown item Keep the queried name is null to use the custom provided battery attribution name (avoid to replace it with POWER_COMPONENT_ prefix name), such that we can show "TPU", "GPU" ... attribution. old screenshot: https://screenshot.googleplex.com/8KGNxW2eHFY85zw new screenshot: https://screenshot.googleplex.com/8KVj3jBjShfa8wK Bug: 227749579 Test: make RunSettingsRoboTests Change-Id: I22fba3c252a92f7d64670ad5d0f6f3548374968a --- src/com/android/settings/fuelgauge/BatteryEntry.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryEntry.java b/src/com/android/settings/fuelgauge/BatteryEntry.java index 5c9d07128d0..7bb5a4e9aa6 100644 --- a/src/com/android/settings/fuelgauge/BatteryEntry.java +++ b/src/com/android/settings/fuelgauge/BatteryEntry.java @@ -625,8 +625,9 @@ public class BatteryEntry { iconId = R.drawable.ic_settings_phone_idle; break; default: - name = DebugUtils.constantToString(BatteryConsumer.class, "POWER_COMPONENT_", - powerComponentId); + Log.w(TAG, "unknown attribute:" + DebugUtils.constantToString( + BatteryConsumer.class, "POWER_COMPONENT_", powerComponentId)); + name = null; iconId = R.drawable.ic_power_system; break; }