From be1fb77da5b52469f0cbc0aa76addae225133ef6 Mon Sep 17 00:00:00 2001 From: Gil Cukierman Date: Fri, 28 Apr 2023 15:40:46 +0000 Subject: [PATCH 01/12] Enable null cipher toggle by default Test: atest GsmCdmaPhoneTest NullAlgorithmsPreferenceController Test: Manual test on multiple targets and cf Bug: 262063621 Change-Id: Ic10dbe552eb48eb19efc53e2430a7a1eb081a50e --- .../network/telephony/NullAlgorithmsPreferenceController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/network/telephony/NullAlgorithmsPreferenceController.java b/src/com/android/settings/network/telephony/NullAlgorithmsPreferenceController.java index be5b01ee83b..7306aaf25f5 100644 --- a/src/com/android/settings/network/telephony/NullAlgorithmsPreferenceController.java +++ b/src/com/android/settings/network/telephony/NullAlgorithmsPreferenceController.java @@ -59,7 +59,7 @@ public class NullAlgorithmsPreferenceController extends TelephonyTogglePreferenc } if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CELLULAR_SECURITY, - TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, false)) { + TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, true)) { Log.i(LOG_TAG, "Null cipher toggle is disabled by DeviceConfig"); return CONDITIONALLY_UNAVAILABLE; } From 3aae28c46369dca51d665cd49bd0711c8f2bfb9e Mon Sep 17 00:00:00 2001 From: Wenhui Yang Date: Mon, 1 May 2023 20:06:27 +0000 Subject: [PATCH 02/12] Fix overlap in udfps_enroll_enrolling Fix the view overlap on R4 device when setting up fingerprint in device registration or the font size set to largest. Please see ag/21478828 for reference. Test: Manual - Set up fingerprint in device registration and observe the views don't overlap. Set up fingerprint in Settings with font size set to largest and observe the views don't overlap. Bug: 275119187 Change-Id: I82d2a3941211801e2c4615ba9ba5a917f3cd9ea1 --- .../FingerprintEnrollEnrolling.java | 87 ++++++++++--------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 62e191e5284..4cd7283eed6 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -259,10 +259,10 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { if (mCanAssumeUdfps) { int rotation = getApplicationContext().getDisplay().getRotation(); + final GlifLayout layout = (GlifLayout) getLayoutInflater().inflate( + R.layout.udfps_enroll_enrolling, null, false); switch (rotation) { case Surface.ROTATION_90: - final GlifLayout layout = (GlifLayout) getLayoutInflater().inflate( - R.layout.udfps_enroll_enrolling, null, false); final LinearLayout layoutContainer = layout.findViewById( R.id.layout_container); final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( @@ -287,53 +287,56 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { case Surface.ROTATION_0: case Surface.ROTATION_180: - case Surface.ROTATION_270: - default: - final GlifLayout defaultLayout = (GlifLayout) getLayoutInflater().inflate( - R.layout.udfps_enroll_enrolling, null, false); if (FeatureFlagUtils.isEnabled(getApplicationContext(), FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) { final UdfpsEnrollView udfpsEnrollView = addUdfpsEnrollView(props.get(0)); - if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { - // In the portrait mode, set layout_container's height 0, so it's - // always shown at the bottom of the screen. - // Add udfps enroll view into layout_container instead of - // udfps_enroll_enrolling, so that when the content is too long to - // make udfps_enroll_enrolling larger than the screen, udfps enroll - // view could still be set to right position by setting bottom margin to - // its parent view (layout_container) because it's always at the - // bottom of the screen. - final FrameLayout portraitLayoutContainer = defaultLayout.findViewById( - R.id.layout_container); - final ViewGroup.LayoutParams containerLp = - portraitLayoutContainer.getLayoutParams(); - containerLp.height = 0; + // In the portrait mode, set layout_container's height 0, so it's + // always shown at the bottom of the screen. + // Add udfps enroll view into layout_container instead of + // udfps_enroll_enrolling, so that when the content is too long to + // make udfps_enroll_enrolling larger than the screen, udfps enroll + // view could still be set to right position by setting bottom margin to + // its parent view (layout_container) because it's always at the + // bottom of the screen. + final FrameLayout portraitLayoutContainer = layout.findViewById( + R.id.layout_container); + final ViewGroup.LayoutParams containerLp = + portraitLayoutContainer.getLayoutParams(); + containerLp.height = 0; - // In the portrait mode, the title and lottie animation view may - // overlap when title needs three lines, so adding some paddings - // between them, and adjusting the fp progress view here accordingly. - final int layoutLottieAnimationPadding = (int) getResources() - .getDimension(R.dimen.udfps_lottie_padding_top); - portraitLayoutContainer.setPadding(0, - layoutLottieAnimationPadding, 0, 0); - final ImageView progressView = udfpsEnrollView.findViewById( - R.id.udfps_enroll_animation_fp_progress_view); - progressView.setPadding(0, -(layoutLottieAnimationPadding), - 0, layoutLottieAnimationPadding); - final ImageView fingerprintView = udfpsEnrollView.findViewById( - R.id.udfps_enroll_animation_fp_view); - fingerprintView.setPadding(0, -layoutLottieAnimationPadding, - 0, layoutLottieAnimationPadding); + // In the portrait mode, the title and lottie animation view may + // overlap when title needs three lines, so adding some paddings + // between them, and adjusting the fp progress view here accordingly. + final int layoutLottieAnimationPadding = (int) getResources() + .getDimension(R.dimen.udfps_lottie_padding_top); + portraitLayoutContainer.setPadding(0, + layoutLottieAnimationPadding, 0, 0); + final ImageView progressView = udfpsEnrollView.findViewById( + R.id.udfps_enroll_animation_fp_progress_view); + progressView.setPadding(0, -(layoutLottieAnimationPadding), + 0, layoutLottieAnimationPadding); + final ImageView fingerprintView = udfpsEnrollView.findViewById( + R.id.udfps_enroll_animation_fp_view); + fingerprintView.setPadding(0, -layoutLottieAnimationPadding, + 0, layoutLottieAnimationPadding); - portraitLayoutContainer.addView(udfpsEnrollView); - setOnHoverListener(false, defaultLayout, udfpsEnrollView); - } else if (rotation == Surface.ROTATION_270) { - defaultLayout.addView(udfpsEnrollView); - setOnHoverListener(true, defaultLayout, udfpsEnrollView); - } + portraitLayoutContainer.addView(udfpsEnrollView); + setOnHoverListener(false, layout, udfpsEnrollView); } - setContentView(defaultLayout); + setContentView(layout); + break; + + case Surface.ROTATION_270: + default: + if (FeatureFlagUtils.isEnabled(getApplicationContext(), + FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) { + final UdfpsEnrollView udfpsEnrollView = addUdfpsEnrollView(props.get(0)); + layout.addView(udfpsEnrollView); + setOnHoverListener(true, layout, udfpsEnrollView); + } + + setContentView(layout); break; } setDescriptionText(R.string.security_settings_udfps_enroll_start_message); From 1815ba7828802397a3d93345daf42b3b1a2e3611 Mon Sep 17 00:00:00 2001 From: Charles Chen Date: Wed, 26 Apr 2023 17:21:18 +0800 Subject: [PATCH 03/12] Disable Settings large-screen optimization Disable the optimization that is automatically enabled when including Window Extensions through Activity Embedding. The optimization causes to launch an additional trampoline activity for deep links, even when the screen size is smaller than required by Activity Embedding rules. This CL also rename isActivityEmbeddingEnabled to shouldEnableLargeScreenOptimization because the method may report false even if the device supports the feature but Settings app doesn't for performance concern. Bug: 267725145 Bug: 279461170 Test: Build, perf metrics Test: atest LauncherIronwoodIntegrationTests:android.platform.test.scenario.launcher.integration_tests.HomescreenCustomizationTests Test: atest SettingsMultiPaneDeepLinkTest.java Change-Id: I38e8c62a61a344eb7a5cbfd1cd2c2137357d1a87 --- .../android/settings/SettingsApplication.java | 5 +-- .../android/settings/SettingsInitialize.java | 4 +- .../ActivityEmbeddingUtils.java | 41 +++++++++++++++++-- .../homepage/SettingsHomepageActivity.java | 3 +- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/com/android/settings/SettingsApplication.java b/src/com/android/settings/SettingsApplication.java index 49cb85e2392..7d8055d6337 100644 --- a/src/com/android/settings/SettingsApplication.java +++ b/src/com/android/settings/SettingsApplication.java @@ -22,9 +22,8 @@ import android.net.Uri; import android.provider.Settings; import android.util.FeatureFlagUtils; -import androidx.window.embedding.SplitController; - import com.android.settings.activityembedding.ActivityEmbeddingRulesController; +import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.core.instrumentation.ElapsedTimeUtils; import com.android.settings.homepage.SettingsHomepageActivity; import com.android.settings.spa.SettingsSpaEnvironment; @@ -53,7 +52,7 @@ public class SettingsApplication extends Application { setSpaEnvironment(); if (FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN) - && SplitController.getInstance(this).isSplitSupported()) { + && ActivityEmbeddingUtils.isSettingsSplitEnabled(this)) { if (WizardManagerHelper.isUserSetupComplete(this)) { new ActivityEmbeddingRulesController(this).initRules(); } else { diff --git a/src/com/android/settings/SettingsInitialize.java b/src/com/android/settings/SettingsInitialize.java index 1d235233f0e..37a564b7c6a 100644 --- a/src/com/android/settings/SettingsInitialize.java +++ b/src/com/android/settings/SettingsInitialize.java @@ -37,9 +37,9 @@ import android.os.UserManager; import android.util.Log; import androidx.annotation.VisibleForTesting; -import androidx.window.embedding.SplitController; import com.android.settings.Settings.CreateShortcutActivity; +import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.homepage.DeepLinkHomepageActivity; import com.android.settings.search.SearchStateReceiver; import com.android.settingslib.utils.ThreadUtils; @@ -166,7 +166,7 @@ public class SettingsInitialize extends BroadcastReceiver { DeepLinkHomepageActivity.class); final ComponentName searchStateReceiver = new ComponentName(context, SearchStateReceiver.class); - final int enableState = SplitController.getInstance(context).isSplitSupported() + final int enableState = ActivityEmbeddingUtils.isSettingsSplitEnabled(context) ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; pm.setComponentEnabledSetting(deepLinkHome, enableState, PackageManager.DONT_KILL_APP); diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java b/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java index a0dac5a9ec5..ae890f87632 100644 --- a/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java +++ b/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java @@ -18,6 +18,7 @@ package com.android.settings.activityembedding; import android.app.Activity; import android.content.Context; +import android.os.SystemProperties; import android.util.DisplayMetrics; import android.util.FeatureFlagUtils; import android.util.Log; @@ -39,6 +40,21 @@ public class ActivityEmbeddingUtils { private static final int MIN_SMALLEST_SCREEN_SPLIT_WIDTH_DP = 600; // The minimum width of the activity to show the regular homepage layout. private static final float MIN_REGULAR_HOMEPAGE_LAYOUT_WIDTH_DP = 380f; + + /** + * Indicates whether to enable large screen optimization if the device supports + * the Activity Embedding split feature. + *

+ * Note that the large screen optimization won't be enabled if the device doesn't support the + * Activity Embedding feature regardless of this property value. + * + * @see androidx.window.embedding.SplitController#getSplitSupportStatus + * @see androidx.window.embedding.SplitController.SplitSupportStatus#SPLIT_AVAILABLE + * @see androidx.window.embedding.SplitController.SplitSupportStatus#SPLIT_UNAVAILABLE + */ + private static final boolean SHOULD_ENABLE_LARGE_SCREEN_OPTIMIZATION = + SystemProperties.getBoolean("persist.settings.large_screen_opt.enabled", true); + private static final String TAG = "ActivityEmbeddingUtils"; /** Get the smallest width dp of the window when the split should be used. */ @@ -62,18 +78,35 @@ public class ActivityEmbeddingUtils { return context.getResources().getFloat(R.dimen.config_activity_embed_split_ratio); } - /** Whether to support embedding activity feature. */ + /** + * Returns {@code true} to indicate that Settings app support the Activity Embedding feature on + * this device. Returns {@code false}, otherwise. + */ + public static boolean isSettingsSplitEnabled(Context context) { + return SHOULD_ENABLE_LARGE_SCREEN_OPTIMIZATION + && SplitController.getInstance(context).getSplitSupportStatus() + == SplitController.SplitSupportStatus.SPLIT_AVAILABLE; + } + + /** + * Checks whether to support embedding activity feature with following conditions: + *

    + *
  • Whether {@link #isSettingsSplitEnabled(Context)}
  • + *
  • Whether {@link FeatureFlagUtils#SETTINGS_SUPPORT_LARGE_SCREEN} is enabled
  • + *
  • Whether User setup is completed
  • + *
+ */ public static boolean isEmbeddingActivityEnabled(Context context) { boolean isFlagEnabled = FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN); - boolean isSplitSupported = SplitController.getInstance(context).isSplitSupported(); + boolean isSettingsSplitSupported = isSettingsSplitEnabled(context); boolean isUserSetupComplete = WizardManagerHelper.isUserSetupComplete(context); Log.d(TAG, "isFlagEnabled = " + isFlagEnabled); - Log.d(TAG, "isSplitSupported = " + isSplitSupported); + Log.d(TAG, "isSettingsSplitSupported = " + isSettingsSplitSupported); Log.d(TAG, "isUserSetupComplete = " + isUserSetupComplete); - return isFlagEnabled && isSplitSupported && isUserSetupComplete; + return isFlagEnabled && isSettingsSplitSupported && isUserSetupComplete; } /** Whether to show the regular or simplified homepage layout. */ diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 7713e270425..03bc1b32fb2 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -58,7 +58,6 @@ import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import androidx.window.embedding.ActivityEmbeddingController; -import androidx.window.embedding.SplitController; import androidx.window.embedding.SplitRule; import com.android.settings.R; @@ -429,7 +428,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements private boolean shouldLaunchDeepLinkIntentToRight() { if (!FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN) - || !SplitController.getInstance(this).isSplitSupported()) { + || !ActivityEmbeddingUtils.isSettingsSplitEnabled(this)) { return false; } From a1af0493780344ed2be130a4dd7a2be38949be6b Mon Sep 17 00:00:00 2001 From: Vania Januar Date: Thu, 27 Apr 2023 15:51:13 +0100 Subject: [PATCH 04/12] Guard against null input device in StylusDeviceUpdater Bug: 279170207 Test: StylusDeviceUpdaterTest Change-Id: Ib4a3f12eaceb87370c800a9dc7eda819c706cd3b --- .../stylus/StylusDeviceUpdater.java | 9 ++++++++- .../stylus/StylusDeviceUpdaterTest.java | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java b/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java index da6e1789b0c..947c5acc83b 100644 --- a/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java +++ b/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java @@ -100,6 +100,8 @@ public class StylusDeviceUpdater implements InputManager.InputDeviceListener, @Override public void onInputDeviceAdded(int deviceId) { InputDevice inputDevice = mInputManager.getInputDevice(deviceId); + if (inputDevice == null) return; + if (inputDevice.supportsSource(InputDevice.SOURCE_STYLUS) && !inputDevice.isExternal()) { try { @@ -121,7 +123,10 @@ public class StylusDeviceUpdater implements InputManager.InputDeviceListener, @Override public void onInputDeviceChanged(int deviceId) { - if (mInputManager.getInputDevice(deviceId).supportsSource(InputDevice.SOURCE_STYLUS)) { + InputDevice inputDevice = mInputManager.getInputDevice(deviceId); + if (inputDevice == null) return; + + if (inputDevice.supportsSource(InputDevice.SOURCE_STYLUS)) { forceUpdate(); } } @@ -189,6 +194,8 @@ public class StylusDeviceUpdater implements InputManager.InputDeviceListener, boolean hasConnectedBluetoothStylusDevice() { for (int deviceId : mInputManager.getInputDeviceIds()) { InputDevice device = mInputManager.getInputDevice(deviceId); + if (device == null) continue; + if (device.supportsSource(InputDevice.SOURCE_STYLUS) && mInputManager.getInputDeviceBluetoothAddress(deviceId) != null) { return true; diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdaterTest.java index 7fb17e2eb2c..afdbb8beba2 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdaterTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.content.Context; import android.content.Intent; @@ -108,6 +109,15 @@ public class StylusDeviceUpdaterTest { any()); } + @Test + public void onInputDeviceAdded_null_doesNothing() { + doReturn(null).when(mInputManager).getInputDevice(0); + mStylusDeviceUpdater.onInputDeviceAdded(0); + + verify(mInputManager).getInputDevice(0); + verifyNoMoreInteractions(mInputManager); + } + @Test public void onInputDeviceAdded_internalStylus_registersBatteryListener() { mStylusDeviceUpdater.onInputDeviceAdded(1); @@ -124,6 +134,15 @@ public class StylusDeviceUpdaterTest { any()); } + @Test + public void onInputDeviceChanged_null_doesNothing() { + doReturn(null).when(mInputManager).getInputDevice(0); + mStylusDeviceUpdater.onInputDeviceChanged(0); + + verify(mInputManager).getInputDevice(0); + verifyNoMoreInteractions(mInputManager); + } + @Test public void click_usiPreference_launchUsiDetailsPage() { doReturn(mSettingsActivity).when(mDashboardFragment).getContext(); From 9814931fd8c26be2cd96a0315db8fab75974465c Mon Sep 17 00:00:00 2001 From: Diya Bera Date: Tue, 2 May 2023 09:48:57 -0700 Subject: [PATCH 05/12] Remove "Try again" from fingerprint error dialog Test: Manual (Start enrollment and don't complete it for 10 minutes; observe the dialog; screenshot in bug) Bug: 279648300 Change-Id: I88280e3a5d47735285d89174ad8bcb0da8d39282 --- res/values/strings.xml | 2 +- .../FingerprintEnrollEnrolling.java | 2 +- .../FingerprintEnrollFindSensor.java | 2 +- .../fingerprint/FingerprintErrorDialog.java | 63 ++++--------------- 4 files changed, 14 insertions(+), 55 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index e02d2a75762..de804fa9c05 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1024,7 +1024,7 @@ Fingerprint setup timed out - Try again now or set up your fingerprint later in Settings + You can set up your fingerprint later in Settings. Fingerprint enrollment didn\'t work. Try again or use a different finger. diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 62e191e5284..7b49c566065 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -514,7 +514,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { // showErrorDialog() will cause onWindowFocusChanged(false), set mIsCanceled to false // before showErrorDialog() to prevent that another error dialog is triggered again. mIsCanceled = true; - FingerprintErrorDialog.showErrorDialog(this, errorMsgId, mCanAssumeUdfps); + FingerprintErrorDialog.showErrorDialog(this, errorMsgId); cancelEnrollment(); stopIconAnimation(); stopListenOrientationEvent(); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java index 89bda20cd7a..e54403170eb 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java @@ -329,7 +329,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements if (mNextClicked && errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { proceedToEnrolling(false /* cancelEnrollment */); } else { - FingerprintErrorDialog.showErrorDialog(this, errMsgId, mCanAssumeUdfps); + FingerprintErrorDialog.showErrorDialog(this, errMsgId); } } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java index 8f604e21605..f0a87a4a889 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java @@ -18,13 +18,10 @@ package com.android.settings.biometrics.fingerprint; import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED; import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT; -import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.KEY_STATE_CANCELED; import android.app.Activity; import android.app.Dialog; import android.app.settings.SettingsEnums; -import android.content.DialogInterface; -import android.content.Intent; import android.hardware.biometrics.BiometricConstants; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; @@ -50,7 +47,6 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment { final CharSequence errorString = getArguments().getCharSequence(KEY_ERROR_MSG); final CharSequence errorTitle = getArguments().getCharSequence(KEY_ERROR_TITLE); final int errMsgId = getArguments().getInt(KEY_ERROR_ID); - final boolean canAssumeUdfps = getArguments().getBoolean(KEY_UDFPS, false); final boolean wasTimeout = errMsgId == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT; builder.setTitle(errorTitle) @@ -58,53 +54,22 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment { .setCancelable(false) .setPositiveButton( R.string.security_settings_fingerprint_enroll_dialog_ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - final Activity activity = getActivity(); - if (wasTimeout && !canAssumeUdfps) { - activity.setResult(RESULT_TIMEOUT); - } else { - activity.setResult(RESULT_FINISHED); - } - activity.finish(); + (dialog, which) -> { + dialog.dismiss(); + final Activity activity = getActivity(); + if (wasTimeout) { + activity.setResult(RESULT_TIMEOUT); + } else { + activity.setResult(RESULT_FINISHED); } + activity.finish(); }); - if (wasTimeout && canAssumeUdfps) { - builder.setPositiveButton( - R.string.security_settings_fingerprint_enroll_dialog_try_again, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - final Activity activity = getActivity(); - final Intent intent = activity.getIntent(); - intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); - intent.putExtra(KEY_STATE_CANCELED, false); - activity.startActivity(intent); - activity.finish(); - } - }) - .setNegativeButton( - R.string.security_settings_fingerprint_enroll_dialog_ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - final Activity activity = getActivity(); - activity.setResult(RESULT_TIMEOUT); - activity.finish(); - } - }); - } final AlertDialog dialog = builder.create(); dialog.setCanceledOnTouchOutside(false); return dialog; } - public static void showErrorDialog(BiometricEnrollBase host, int errMsgId, - boolean canAssumeUdfps) { + public static void showErrorDialog(BiometricEnrollBase host, int errMsgId) { if (host.isFinishing()) { return; } @@ -114,12 +79,7 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment { } CharSequence errMsg = host.getText(getErrorMessage(errMsgId)); final CharSequence errTitle = host.getText(getErrorTitle(errMsgId)); - if (!canAssumeUdfps - && errMsgId == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) { - errMsg = host.getText(getErrorMessage(BiometricConstants.BIOMETRIC_ERROR_CANCELED)); - } - final FingerprintErrorDialog dialog = newInstance(errMsg, errTitle, - errMsgId, canAssumeUdfps); + final FingerprintErrorDialog dialog = newInstance(errMsg, errTitle, errMsgId); dialog.show(fragmentManager, FingerprintErrorDialog.class.getName()); } @@ -154,13 +114,12 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment { } private static FingerprintErrorDialog newInstance(CharSequence msg, CharSequence title, - int msgId, boolean canAssumeUdfps) { + int msgId) { final FingerprintErrorDialog dialog = new FingerprintErrorDialog(); final Bundle args = new Bundle(); args.putCharSequence(KEY_ERROR_MSG, msg); args.putCharSequence(KEY_ERROR_TITLE, title); args.putInt(KEY_ERROR_ID, msgId); - args.putBoolean(KEY_UDFPS, canAssumeUdfps); dialog.setArguments(args); return dialog; } From ea03b3e2a07742d80365ea3c8482e2ffb7a6ded3 Mon Sep 17 00:00:00 2001 From: Becca Hughes Date: Thu, 27 Apr 2023 21:27:52 +0000 Subject: [PATCH 06/12] Add combined provider class that manages new settings UI Example of how to use: https://paste.googleplex.com/6523798525313024 Test: make Bug: 278919696 Change-Id: I934a5f6d02b50f8c97bda6d997902a42ff88e26f --- .../credentials/CombinedProviderInfo.java | 262 ++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 src/com/android/settings/applications/credentials/CombinedProviderInfo.java diff --git a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java new file mode 100644 index 00000000000..ce985975c90 --- /dev/null +++ b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2023 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.applications.credentials; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.ServiceInfo; +import android.credentials.CredentialProviderInfo; +import android.graphics.drawable.Drawable; +import android.service.autofill.AutofillServiceInfo; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Holds combined autofill and credential manager data grouped by package name. Contains backing + * logic for each row in settings. + */ +public final class CombinedProviderInfo { + private final List mCredentialProviderInfos; + private final @Nullable AutofillServiceInfo mAutofillServiceInfo; + private final boolean mIsDefaultAutofillProvider; + private final boolean mIsDefaultCredmanProvider; + + /** Constructs an information instance from both autofill and credential provider. */ + public CombinedProviderInfo( + @Nullable List cpis, + @Nullable AutofillServiceInfo asi, + boolean isDefaultAutofillProvider, + boolean isDefaultCredmanProvider) { + mCredentialProviderInfos = new ArrayList<>(cpis); + mAutofillServiceInfo = asi; + mIsDefaultAutofillProvider = isDefaultAutofillProvider; + mIsDefaultCredmanProvider = isDefaultCredmanProvider; + } + + /** Returns the credential provider info. */ + @Nullable + public List getCredentialProviderInfos() { + return mCredentialProviderInfos; + } + + /** Returns the autofill provider info. */ + @Nullable + public AutofillServiceInfo getAutofillServiceInfo() { + return mAutofillServiceInfo; + } + + /** Returns the application info. */ + public @Nullable ApplicationInfo getApplicationInfo() { + if (!mCredentialProviderInfos.isEmpty()) { + return mCredentialProviderInfos.get(0).getServiceInfo().applicationInfo; + } + return mAutofillServiceInfo.getServiceInfo().applicationInfo; + } + + /** Returns the app icon. */ + @Nullable + public Drawable getAppIcon(@NonNull Context context) { + Drawable icon = null; + ServiceInfo brandingService = getBrandingService(); + if (brandingService != null) { + icon = brandingService.loadIcon(context.getPackageManager()); + } + + // If the branding service gave us a icon then use that. + if (icon != null) { + return icon; + } + + // Otherwise fallback to the app label and then the package name. + return getApplicationInfo().loadIcon(context.getPackageManager()); + } + + /** Returns the app name. */ + @Nullable + public CharSequence getAppName(@NonNull Context context) { + CharSequence name = ""; + ServiceInfo brandingService = getBrandingService(); + if (brandingService != null) { + name = brandingService.loadLabel(context.getPackageManager()); + } + + // If the branding service gave us a name then use that. + if (!TextUtils.isEmpty(name)) { + return name; + } + + // Otherwise fallback to the app label and then the package name. + name = getApplicationInfo().loadLabel(context.getPackageManager()); + if (TextUtils.isEmpty(name)) { + name = getApplicationInfo().packageName; + } + return name; + } + + /** Gets the service to use for branding (name, icons). */ + public @Nullable ServiceInfo getBrandingService() { + // If the app has an autofill service then use that. + if (mAutofillServiceInfo != null) { + return mAutofillServiceInfo.getServiceInfo(); + } + + // If there are no credman providers then stop here. + if (mCredentialProviderInfos.isEmpty()) { + return null; + } + + // Build a list of credential providers and sort them by component names + // alphabetically to ensure we are deterministic when picking the provider. + Map flattenedNamesToServices = new HashMap<>(); + List flattenedNames = new ArrayList<>(); + for (CredentialProviderInfo cpi : mCredentialProviderInfos) { + final String flattenedName = cpi.getComponentName().flattenToString(); + flattenedNamesToServices.put(flattenedName, cpi.getServiceInfo()); + flattenedNames.add(flattenedName); + } + + Collections.sort(flattenedNames); + return flattenedNamesToServices.get(flattenedNames.get(0)); + } + + /** Returns whether the provider is the default autofill provider. */ + public boolean isDefaultAutofillProvider() { + return mIsDefaultAutofillProvider; + } + + /** Returns whether the provider is the default credman provider. */ + public boolean isDefaultCredmanProvider() { + return mIsDefaultCredmanProvider; + } + + /** Returns the settings subtitle. */ + @Nullable + public String getSettingsSubtitle() { + List subtitles = new ArrayList<>(); + for (CredentialProviderInfo cpi : mCredentialProviderInfos) { + // Convert from a CharSequence. + String subtitle = String.valueOf(cpi.getSettingsSubtitle()); + if (subtitle != null && !TextUtils.isEmpty(subtitle) && !subtitle.equals("null")) { + subtitles.add(subtitle); + } + } + + if (subtitles.size() == 0) { + return ""; + } + + return String.join(", ", subtitles); + } + + /** Returns the autofill component name string. */ + @Nullable + public String getAutofillServiceString() { + if (mAutofillServiceInfo != null) { + return mAutofillServiceInfo.getServiceInfo().getComponentName().flattenToString(); + } + return null; + } + + /** Returns the provider that gets the top spot. */ + public static @Nullable CombinedProviderInfo getTopProvider( + List providers) { + // If there is an autofill provider then it should be the + // top app provider. + for (CombinedProviderInfo cpi : providers) { + if (cpi.isDefaultAutofillProvider()) { + return cpi; + } + } + + // TODO(280454916): Add logic here. + return null; + } + + public static List buildMergedList( + List asiList, + List cpiList, + @Nullable String defaultAutofillProvider) { + ComponentName defaultAutofillProviderComponent = + (defaultAutofillProvider == null) + ? null + : ComponentName.unflattenFromString(defaultAutofillProvider); + + // Index the autofill providers by package name. + Set packageNames = new HashSet<>(); + Map> autofillServices = new HashMap<>(); + for (AutofillServiceInfo asi : asiList) { + final String packageName = asi.getServiceInfo().packageName; + if (!autofillServices.containsKey(packageName)) { + autofillServices.put(packageName, new ArrayList<>()); + } + + autofillServices.get(packageName).add(asi); + packageNames.add(packageName); + } + + // Index the credman providers by package name. + Map> credmanServices = new HashMap<>(); + for (CredentialProviderInfo cpi : cpiList) { + String packageName = cpi.getServiceInfo().packageName; + if (!credmanServices.containsKey(packageName)) { + credmanServices.put(packageName, new ArrayList<>()); + } + + credmanServices.get(packageName).add(cpi); + packageNames.add(packageName); + } + + // Now go through and build the joint datasets. + List cmpi = new ArrayList<>(); + for (String packageName : packageNames) { + List asi = autofillServices.get(packageName); + List cpi = credmanServices.get(packageName); + + // If there are multiple autofill services then pick the first one. + AutofillServiceInfo selectedAsi = asi.isEmpty() ? null : asi.get(0); + + // Check if we are the default autofill provider. + boolean isDefaultAutofillProvider = false; + if (defaultAutofillProviderComponent != null + && defaultAutofillProviderComponent.getPackageName().equals(packageName)) { + isDefaultAutofillProvider = true; + } + + // Check if we have any enabled cred man services. + boolean isDefaultCredmanProvider = false; + if (!cpi.isEmpty()) { + isDefaultCredmanProvider = cpi.get(0).isEnabled(); + } + + cmpi.add( + new CombinedProviderInfo( + cpi, selectedAsi, isDefaultAutofillProvider, isDefaultCredmanProvider)); + } + + return cmpi; + } +} From ddf359d0f81e76775ea6b285f8d58b8a64e3049a Mon Sep 17 00:00:00 2001 From: Becca Hughes Date: Mon, 24 Apr 2023 16:08:59 +0000 Subject: [PATCH 07/12] Fork autofill default app selection We are merging the default app selection for autofill with credman so this forks the existing UI (so we can flag it off). Test: ondevice Bug: 278919696 Change-Id: I96bcf1ff86b169a182b9974f7384c45b474c3d5d --- res/values/strings.xml | 16 ++ .../accounts_dashboard_settings_credman.xml | 6 +- ...ts_personal_dashboard_settings_credman.xml | 6 +- ...counts_work_dashboard_settings_credman.xml | 4 +- res/xml/default_credman_picker.xml | 20 ++ .../accounts/AccountDashboardFragment.java | 20 +- .../AccountPersonalDashboardFragment.java | 9 +- .../AccountWorkProfileDashboardFragment.java | 9 +- ...CredentialManagerPreferenceController.java | 90 ++++-- .../credentials/DefaultCombinedPicker.java | 258 ++++++++++++++++++ .../DefaultCombinedPreferenceController.java | 150 ++++++++++ ...faultWorkCombinedPreferenceController.java | 84 ++++++ ...entialManagerPreferenceControllerTest.java | 13 +- 13 files changed, 637 insertions(+), 48 deletions(-) create mode 100644 res/xml/default_credman_picker.xml create mode 100644 src/com/android/settings/applications/credentials/DefaultCombinedPicker.java create mode 100644 src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java create mode 100644 src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 5d4487c42ca..d106737cda9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10265,6 +10265,8 @@ auto, fill, autofill, password data, passkey, password + + auto, fill, autofill, data, passkey, password @@ -10277,6 +10279,20 @@ ]]> + + + Use %1$s\? +
+
+ %1$s uses what\'s on + your screen to determine what can be autofilled. New passwords, passkeys and other info will be saved here from now on. + ]]> +
+ + + Passwords, passkeys and data services + Turn off %1$s\? diff --git a/res/xml/accounts_dashboard_settings_credman.xml b/res/xml/accounts_dashboard_settings_credman.xml index 4f278b659b2..83c16a2df30 100644 --- a/res/xml/accounts_dashboard_settings_credman.xml +++ b/res/xml/accounts_dashboard_settings_credman.xml @@ -27,10 +27,10 @@ android:title="@string/credman_chosen_app_title"> + settings:keywords="@string/credman_autofill_keywords"> diff --git a/res/xml/accounts_personal_dashboard_settings_credman.xml b/res/xml/accounts_personal_dashboard_settings_credman.xml index 891bb7c1d22..b87e866700b 100644 --- a/res/xml/accounts_personal_dashboard_settings_credman.xml +++ b/res/xml/accounts_personal_dashboard_settings_credman.xml @@ -28,10 +28,10 @@ android:title="@string/credman_chosen_app_title"> + settings:keywords="@string/credman_autofill_keywords"> diff --git a/res/xml/accounts_work_dashboard_settings_credman.xml b/res/xml/accounts_work_dashboard_settings_credman.xml index 0d4b742c366..15299208a4e 100644 --- a/res/xml/accounts_work_dashboard_settings_credman.xml +++ b/res/xml/accounts_work_dashboard_settings_credman.xml @@ -28,8 +28,8 @@ android:title="@string/credman_chosen_app_title"> + + + diff --git a/src/com/android/settings/accounts/AccountDashboardFragment.java b/src/com/android/settings/accounts/AccountDashboardFragment.java index f59de46428d..e4c20313a54 100644 --- a/src/com/android/settings/accounts/AccountDashboardFragment.java +++ b/src/com/android/settings/accounts/AccountDashboardFragment.java @@ -30,6 +30,8 @@ import android.provider.SearchIndexableResource; import com.android.settings.R; import com.android.settings.applications.autofill.PasswordsPreferenceController; import com.android.settings.applications.credentials.CredentialManagerPreferenceController; +import com.android.settings.applications.credentials.DefaultCombinedPreferenceController; +import com.android.settings.applications.credentials.DefaultWorkCombinedPreferenceController; import com.android.settings.applications.defaultapps.DefaultAutofillPreferenceController; import com.android.settings.applications.defaultapps.DefaultWorkAutofillPreferenceController; import com.android.settings.dashboard.DashboardFragment; @@ -76,7 +78,14 @@ public class AccountDashboardFragment extends DashboardFragment { CredentialManagerPreferenceController cmpp = use(CredentialManagerPreferenceController.class); CredentialManagerPreferenceController.Delegate delegate = - result -> getActivity().setResult(result); + new CredentialManagerPreferenceController.Delegate() { + public void setActivityResult(int resultCode) { + getActivity().setResult(resultCode); + } + public void forceDelegateRefresh() { + forceUpdatePreferences(); + } + }; cmpp.init(this, getFragmentManager(), getIntent(), delegate); } else { getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class)); @@ -99,8 +108,13 @@ public class AccountDashboardFragment extends DashboardFragment { static void buildAutofillPreferenceControllers( Context context, List controllers) { - controllers.add(new DefaultAutofillPreferenceController(context)); - controllers.add(new DefaultWorkAutofillPreferenceController(context)); + if (CredentialManager.isServiceEnabled(context)) { + controllers.add(new DefaultCombinedPreferenceController(context)); + controllers.add(new DefaultWorkCombinedPreferenceController(context)); + } else { + controllers.add(new DefaultAutofillPreferenceController(context)); + controllers.add(new DefaultWorkAutofillPreferenceController(context)); + } } private static void buildAccountPreferenceControllers( diff --git a/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java b/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java index a87eb7dd774..d330dd49ec6 100644 --- a/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java +++ b/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java @@ -70,7 +70,14 @@ public class AccountPersonalDashboardFragment extends DashboardFragment { CredentialManagerPreferenceController cmpp = use(CredentialManagerPreferenceController.class); CredentialManagerPreferenceController.Delegate delegate = - result -> getActivity().setResult(result); + new CredentialManagerPreferenceController.Delegate() { + public void setActivityResult(int resultCode) { + getActivity().setResult(resultCode); + } + public void forceDelegateRefresh() { + forceUpdatePreferences(); + } + }; cmpp.init(this, getFragmentManager(), getIntent(), delegate); } else { getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class)); diff --git a/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java b/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java index 445aced18d0..39146c75a7c 100644 --- a/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java +++ b/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java @@ -70,7 +70,14 @@ public class AccountWorkProfileDashboardFragment extends DashboardFragment { CredentialManagerPreferenceController cmpp = use(CredentialManagerPreferenceController.class); CredentialManagerPreferenceController.Delegate delegate = - result -> getActivity().setResult(result); + new CredentialManagerPreferenceController.Delegate() { + public void setActivityResult(int resultCode) { + getActivity().setResult(resultCode); + } + public void forceDelegateRefresh() { + forceUpdatePreferences(); + } + }; cmpp.init(this, getFragmentManager(), getIntent(), delegate); } else { getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class)); diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java index f19fb91fccc..944a9fa551b 100644 --- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java +++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java @@ -44,8 +44,6 @@ import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; import android.text.TextUtils; -import com.android.settingslib.utils.ThreadUtils; -import com.android.internal.content.PackageMonitor; import android.util.IconDrawableFactory; import android.util.Log; @@ -127,6 +125,23 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl return null; } + @Override + public int getAvailabilityStatus() { + if (mCredentialManager == null) { + return UNSUPPORTED_ON_DEVICE; + } + + if (!isAutofillPrefSelected()) { + return CONDITIONALLY_UNAVAILABLE; + } + + if (mServices.isEmpty()) { + return CONDITIONALLY_UNAVAILABLE; + } + + return AVAILABLE; + } + @VisibleForTesting public boolean isConnected() { return mCredentialManager != null; @@ -266,12 +281,15 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl if (mPreferenceScreen != null) { displayPreference(mPreferenceScreen); } + + if (mDelegate != null) { + mDelegate.forceDelegateRefresh(); + } } @VisibleForTesting void setAvailableServices( - List availableServices, - String flagOverrideForTest) { + List availableServices, String flagOverrideForTest) { mFlagOverrideForTest = flagOverrideForTest; mServices.clear(); mServices.addAll(availableServices); @@ -291,11 +309,6 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl } } - @Override - public int getAvailabilityStatus() { - return mServices.isEmpty() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE; - } - @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); @@ -305,6 +318,17 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl mPreferenceScreen = screen; PreferenceGroup group = screen.findPreference(getPreferenceKey()); + group.removeAll(); + + // Hide/show based on autofill pref. + boolean isVisible = isAutofillPrefSelected(); + screen.setVisible(isVisible); + group.setVisible(isVisible); + + if (!isVisible) { + return; + } + Context context = screen.getContext(); mPrefs.putAll(buildPreferenceList(context, group)); @@ -586,10 +610,9 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl /** If the provider is also the autofill provider then hide it. */ @VisibleForTesting public boolean isProviderHiddenBecauseOfAutofill(String packageName) { - final String autofillService = Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.AUTOFILL_SERVICE, - getUser()); + final String autofillService = + Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, getUser()); if (autofillService == null || TextUtils.isEmpty(autofillService)) { return false; } @@ -600,6 +623,13 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl return autofillService.startsWith(packageName); } + private boolean isAutofillPrefSelected() { + final String autofillService = + Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, getUser()); + return !TextUtils.isEmpty(autofillService); + } + @VisibleForTesting void completeEnableProviderDialogBox( int whichButton, String packageName, boolean setActivityResult) { @@ -682,27 +712,31 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl /** Called to send messages back to the parent fragment. */ public static interface Delegate { void setActivityResult(int resultCode); + + void forceDelegateRefresh(); } /** - * Monitor coming and going credman services and calls {@link #update()} when necessary + * Monitor coming and going credman services and calls {@link #DefaultCombinedPicker} when + * necessary */ - private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() { - @Override - public void onPackageAdded(String packageName, int uid) { - ThreadUtils.postOnMainThread(() -> update()); - } + private final PackageMonitor mSettingsPackageMonitor = + new PackageMonitor() { + @Override + public void onPackageAdded(String packageName, int uid) { + ThreadUtils.postOnMainThread(() -> updateFromExternal()); + } - @Override - public void onPackageModified(String packageName) { - ThreadUtils.postOnMainThread(() -> update()); - } + @Override + public void onPackageModified(String packageName) { + ThreadUtils.postOnMainThread(() -> updateFromExternal()); + } - @Override - public void onPackageRemoved(String packageName, int uid) { - ThreadUtils.postOnMainThread(() -> update()); - } - }; + @Override + public void onPackageRemoved(String packageName, int uid) { + ThreadUtils.postOnMainThread(() -> updateFromExternal()); + } + }; /** Dialog fragment parent class. */ private abstract static class CredentialManagerDialogFragment extends DialogFragment diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java new file mode 100644 index 00000000000..41eaf9657a4 --- /dev/null +++ b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2023 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.applications.credentials; + +import android.app.Activity; +import android.app.settings.SettingsEnums; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.autofill.AutofillService; +import android.service.autofill.AutofillServiceInfo; +import android.text.Html; +import android.text.TextUtils; + +import androidx.preference.Preference; + +import com.android.internal.content.PackageMonitor; +import com.android.settings.R; +import com.android.settings.applications.defaultapps.DefaultAppPickerFragment; +import com.android.settingslib.applications.DefaultAppInfo; +import com.android.settingslib.utils.ThreadUtils; +import com.android.settingslib.widget.CandidateInfo; + +import java.util.ArrayList; +import java.util.List; + +public class DefaultCombinedPicker extends DefaultAppPickerFragment { + + private static final String TAG = "DefaultCombinedPicker"; + + public static final String SETTING = Settings.Secure.AUTOFILL_SERVICE; + public static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE); + + /** Extra set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. */ + public static final String EXTRA_PACKAGE_NAME = "package_name"; + + /** Set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. */ + private DialogInterface.OnClickListener mCancelListener; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Activity activity = getActivity(); + if (activity != null && activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME) != null) { + mCancelListener = + (d, w) -> { + activity.setResult(Activity.RESULT_CANCELED); + activity.finish(); + }; + // If mCancelListener is not null, fragment is started from + // ACTION_REQUEST_SET_AUTOFILL_SERVICE and we should always use the calling uid. + mUserId = UserHandle.myUserId(); + } + mSettingsPackageMonitor.register(activity, activity.getMainLooper(), false); + update(); + } + + @Override + protected DefaultAppPickerFragment.ConfirmationDialogFragment newConfirmationDialogFragment( + String selectedKey, CharSequence confirmationMessage) { + final AutofillPickerConfirmationDialogFragment fragment = + new AutofillPickerConfirmationDialogFragment(); + fragment.init(this, selectedKey, confirmationMessage); + return fragment; + } + + /** + * Custom dialog fragment that has a cancel listener used to propagate the result back to caller + * (for the cases where the picker is launched by {@code + * android.settings.REQUEST_SET_AUTOFILL_SERVICE}. + */ + public static class AutofillPickerConfirmationDialogFragment + extends DefaultAppPickerFragment.ConfirmationDialogFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + final DefaultCombinedPicker target = (DefaultCombinedPicker) getTargetFragment(); + setCancelListener(target.mCancelListener); + super.onCreate(savedInstanceState); + } + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.default_credman_picker; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.DEFAULT_AUTOFILL_PICKER; + } + + @Override + protected boolean shouldShowItemNone() { + return true; + } + + /** Monitor coming and going auto fill services and calls {@link #update()} when necessary */ + private final PackageMonitor mSettingsPackageMonitor = + new PackageMonitor() { + @Override + public void onPackageAdded(String packageName, int uid) { + ThreadUtils.postOnMainThread(() -> update()); + } + + @Override + public void onPackageModified(String packageName) { + ThreadUtils.postOnMainThread(() -> update()); + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + ThreadUtils.postOnMainThread(() -> update()); + } + }; + + /** Update the data in this UI. */ + private void update() { + updateCandidates(); + addAddServicePreference(); + } + + @Override + public void onDestroy() { + mSettingsPackageMonitor.unregister(); + super.onDestroy(); + } + + /** + * Gets the preference that allows to add a new autofill service. + * + * @return The preference or {@code null} if no service can be added + */ + private Preference newAddServicePreferenceOrNull() { + final String searchUri = + Settings.Secure.getStringForUser( + getActivity().getContentResolver(), + Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI, + mUserId); + if (TextUtils.isEmpty(searchUri)) { + return null; + } + + final Intent addNewServiceIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)); + final Context context = getPrefContext(); + final Preference preference = new Preference(context); + preference.setOnPreferenceClickListener( + p -> { + context.startActivityAsUser(addNewServiceIntent, UserHandle.of(mUserId)); + return true; + }); + preference.setTitle(R.string.print_menu_item_add_service); + preference.setIcon(R.drawable.ic_add_24dp); + preference.setOrder(Integer.MAX_VALUE - 1); + preference.setPersistent(false); + return preference; + } + + /** + * Add a preference that allows the user to add a service if the market link for that is + * configured. + */ + private void addAddServicePreference() { + final Preference addNewServicePreference = newAddServicePreferenceOrNull(); + if (addNewServicePreference != null) { + getPreferenceScreen().addPreference(addNewServicePreference); + } + } + + @Override + protected List getCandidates() { + final List candidates = new ArrayList<>(); + final List services = + AutofillServiceInfo.getAvailableServices(getContext(), mUserId); + for (AutofillServiceInfo asi : services) { + candidates.add( + new DefaultAppInfo( + getContext(), mPm, mUserId, asi.getServiceInfo().getComponentName())); + } + + return candidates; + } + + public static String getDefaultKey(Context context, int userId) { + String setting = + Settings.Secure.getStringForUser(context.getContentResolver(), SETTING, userId); + if (setting != null) { + ComponentName componentName = ComponentName.unflattenFromString(setting); + if (componentName != null) { + return componentName.flattenToString(); + } + } + return null; + } + + @Override + protected String getDefaultKey() { + return getDefaultKey(getContext(), mUserId); + } + + @Override + protected CharSequence getConfirmationMessage(CandidateInfo appInfo) { + if (appInfo == null) { + return null; + } + final CharSequence appName = appInfo.loadLabel(); + final String message = + getContext() + .getString( + R.string.credman_autofill_confirmation_message, + Html.escapeHtml(appName)); + return Html.fromHtml(message); + } + + @Override + protected boolean setDefaultKey(String key) { + Settings.Secure.putStringForUser(getContext().getContentResolver(), SETTING, key, mUserId); + + // Check if activity was launched from Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE + // intent, and set proper result if so... + final Activity activity = getActivity(); + if (activity != null) { + final String packageName = activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME); + if (packageName != null) { + final int result = + key != null && key.startsWith(packageName) + ? Activity.RESULT_OK + : Activity.RESULT_CANCELED; + activity.setResult(result); + activity.finish(); + } + } + + // TODO: Notify the rest + + return true; + } +} diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java new file mode 100644 index 00000000000..00125a02342 --- /dev/null +++ b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2023 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.applications.credentials; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.credentials.CredentialManager; +import android.provider.Settings; +import android.service.autofill.AutofillServiceInfo; +import android.text.TextUtils; +import android.util.Log; +import android.view.autofill.AutofillManager; + +import com.android.settings.applications.defaultapps.DefaultAppPreferenceController; +import com.android.settingslib.applications.DefaultAppInfo; + +import java.util.List; + +public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController { + + private final AutofillManager mAutofillManager; + private final CredentialManager mCredentialManager; + + public DefaultCombinedPreferenceController(Context context) { + super(context); + + mAutofillManager = mContext.getSystemService(AutofillManager.class); + + if (CredentialManager.isServiceEnabled(context)) { + mCredentialManager = mContext.getSystemService(CredentialManager.class); + } else { + mCredentialManager = null; + } + } + + @Override + public boolean isAvailable() { + return mAutofillManager != null + && mCredentialManager != null + && mAutofillManager.hasAutofillFeature() + && mAutofillManager.isAutofillSupported(); + } + + @Override + public String getPreferenceKey() { + return "default_credman_autofill_main"; + } + + @Override + protected Intent getSettingIntent(DefaultAppInfo info) { + if (info == null) { + return null; + } + final AutofillSettingIntentProvider intentProvider = + new AutofillSettingIntentProvider(mContext, mUserId, info.getKey()); + return intentProvider.getIntent(); + } + + @Override + protected DefaultAppInfo getDefaultAppInfo() { + final String flattenComponent = + Settings.Secure.getString( + mContext.getContentResolver(), DefaultCombinedPicker.SETTING); + if (!TextUtils.isEmpty(flattenComponent)) { + DefaultAppInfo appInfo = + new DefaultAppInfo( + mContext, + mPackageManager, + mUserId, + ComponentName.unflattenFromString(flattenComponent)); + return appInfo; + } + return null; + } + + @Override + protected boolean showLabelAsTitle() { + return true; + } + + /** Provides Intent to setting activity for the specified autofill service. */ + static final class AutofillSettingIntentProvider { + + private final String mSelectedKey; + private final Context mContext; + private final int mUserId; + + public AutofillSettingIntentProvider(Context context, int userId, String key) { + mSelectedKey = key; + mContext = context; + mUserId = userId; + } + + public Intent getIntent() { + final List resolveInfos = + mContext.getPackageManager() + .queryIntentServicesAsUser( + DefaultCombinedPicker.AUTOFILL_PROBE, + PackageManager.GET_META_DATA, + mUserId); + + for (ResolveInfo resolveInfo : resolveInfos) { + final ServiceInfo serviceInfo = resolveInfo.serviceInfo; + final String flattenKey = + new ComponentName(serviceInfo.packageName, serviceInfo.name) + .flattenToString(); + if (TextUtils.equals(mSelectedKey, flattenKey)) { + final String settingsActivity; + try { + settingsActivity = + new AutofillServiceInfo(mContext, serviceInfo) + .getSettingsActivity(); + } catch (SecurityException e) { + // Service does not declare the proper permission, ignore it. + Log.w( + "AutofillSettingIntentProvider", + "Error getting info for " + serviceInfo + ": " + e); + return null; + } + if (TextUtils.isEmpty(settingsActivity)) { + return null; + } + return new Intent(Intent.ACTION_MAIN) + .setComponent( + new ComponentName(serviceInfo.packageName, settingsActivity)); + } + } + + return null; + } + } +} diff --git a/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java new file mode 100644 index 00000000000..cc41e971071 --- /dev/null +++ b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 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.applications.credentials; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; + +import com.android.settings.Utils; +import com.android.settingslib.applications.DefaultAppInfo; + +public class DefaultWorkCombinedPreferenceController extends DefaultCombinedPreferenceController { + private final UserHandle mUserHandle; + + public DefaultWorkCombinedPreferenceController(Context context) { + super(context); + mUserHandle = Utils.getManagedProfile(mUserManager); + } + + @Override + public boolean isAvailable() { + if (mUserHandle == null) { + return false; + } + return super.isAvailable(); + } + + @Override + public String getPreferenceKey() { + return "default_autofill_work"; + } + + @Override + protected DefaultAppInfo getDefaultAppInfo() { + final String flattenComponent = + Settings.Secure.getStringForUser( + mContext.getContentResolver(), + DefaultCombinedPicker.SETTING, + mUserHandle.getIdentifier()); + if (!TextUtils.isEmpty(flattenComponent)) { + DefaultAppInfo appInfo = + new DefaultAppInfo( + mContext, + mPackageManager, + mUserHandle.getIdentifier(), + ComponentName.unflattenFromString(flattenComponent)); + return appInfo; + } + return null; + } + + @Override + protected Intent getSettingIntent(DefaultAppInfo info) { + if (info == null) { + return null; + } + final AutofillSettingIntentProvider intentProvider = + new AutofillSettingIntentProvider( + mContext, mUserHandle.getIdentifier(), info.getKey()); + return intentProvider.getIntent(); + } + + @Override + protected void startActivity(Intent intent) { + mContext.startActivityAsUser(intent, mUserHandle); + } +} diff --git a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java index 3145cce1ac6..60c7d45645a 100644 --- a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java @@ -86,13 +86,12 @@ public class CredentialManagerPreferenceControllerTest { mCredentialsPreferenceCategory.setKey("credentials_test"); mScreen.addPreference(mCredentialsPreferenceCategory); mReceivedResultCode = Optional.empty(); - mDelegate = - new CredentialManagerPreferenceController.Delegate() { - @Override - public void setActivityResult(int resultCode) { - mReceivedResultCode = Optional.of(resultCode); - } - }; + mDelegate = new CredentialManagerPreferenceController.Delegate() { + public void setActivityResult(int resultCode) { + mReceivedResultCode = Optional.of(resultCode); + } + public void forceDelegateRefresh() {} + }; } @Test From b21df6c5fc189c1069b497c58a22e0ab027822ce Mon Sep 17 00:00:00 2001 From: Becca Hughes Date: Wed, 26 Apr 2023 22:52:10 +0000 Subject: [PATCH 08/12] Align settings with new mocks Test: ondevice Bug: 278919696 Change-Id: I274126bce3616596c4a81dd3385f0844d8bdbf71 --- .../credentials/CombinedProviderInfo.java | 5 +- ...CredentialManagerPreferenceController.java | 164 +++++++---------- .../credentials/DefaultCombinedPicker.java | 166 +++++++++++++++--- .../DefaultCombinedPreferenceController.java | 66 +++++-- ...faultWorkCombinedPreferenceController.java | 2 +- .../DefaultAppPreferenceController.java | 37 ++-- ...entialManagerPreferenceControllerTest.java | 58 ------ 7 files changed, 287 insertions(+), 211 deletions(-) diff --git a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java index ce985975c90..af06a0144b9 100644 --- a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java +++ b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java @@ -237,7 +237,10 @@ public final class CombinedProviderInfo { List cpi = credmanServices.get(packageName); // If there are multiple autofill services then pick the first one. - AutofillServiceInfo selectedAsi = asi.isEmpty() ? null : asi.get(0); + AutofillServiceInfo selectedAsi = null; + if (asi != null && !asi.isEmpty()) { + selectedAsi = asi.get(0); + } // Check if we are the default autofill provider. boolean isDefaultAutofillProvider = false; diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java index 944a9fa551b..069336e7000 100644 --- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java +++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java @@ -41,8 +41,8 @@ import android.os.Bundle; import android.os.Handler; import android.os.OutcomeReceiver; import android.os.UserHandle; -import android.provider.DeviceConfig; import android.provider.Settings; +import android.service.autofill.AutofillServiceInfo; import android.text.TextUtils; import android.util.IconDrawableFactory; import android.util.Log; @@ -79,6 +79,15 @@ import java.util.concurrent.Executor; public class CredentialManagerPreferenceController extends BasePreferenceController implements LifecycleObserver { public static final String ADD_SERVICE_DEVICE_CONFIG = "credential_manager_service_search_uri"; + + /** + * In the settings logic we should hide the list of additional credman providers if there is no + * provider selected at the top. The current logic relies on checking whether the autofill + * provider is set which won't work for cred-man only providers. Therefore when a CM only + * provider is set we will set the autofill setting to be this placeholder. + */ + public static final String AUTOFILL_CREDMAN_ONLY_PROVIDER_PLACEHOLDER = "credential-provider"; + private static final String TAG = "CredentialManagerPreferenceController"; private static final String ALTERNATE_INTENT = "android.settings.SYNC_SETTINGS"; private static final String PRIMARY_INTENT = "android.settings.CREDENTIAL_PROVIDER"; @@ -99,6 +108,8 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl private @Nullable String mFlagOverrideForTest = null; private @Nullable PreferenceScreen mPreferenceScreen = null; + private boolean mVisibility = false; + public CredentialManagerPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mPm = context.getPackageManager(); @@ -131,7 +142,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl return UNSUPPORTED_ON_DEVICE; } - if (!isAutofillPrefSelected()) { + if (!mVisibility) { return CONDITIONALLY_UNAVAILABLE; } @@ -271,7 +282,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl setAvailableServices( mCredentialManager.getCredentialProviderServices( - getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY), + getUser(), CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS), null); } @@ -287,6 +298,17 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl } } + private void setVisibility(boolean newVisibility) { + if (newVisibility == mVisibility) { + return; + } + + mVisibility = newVisibility; + if (mDelegate != null) { + mDelegate.forceDelegateRefresh(); + } + } + @VisibleForTesting void setAvailableServices( List availableServices, String flagOverrideForTest) { @@ -320,48 +342,8 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl PreferenceGroup group = screen.findPreference(getPreferenceKey()); group.removeAll(); - // Hide/show based on autofill pref. - boolean isVisible = isAutofillPrefSelected(); - screen.setVisible(isVisible); - group.setVisible(isVisible); - - if (!isVisible) { - return; - } - Context context = screen.getContext(); mPrefs.putAll(buildPreferenceList(context, group)); - - // Add the "add service" button only when there are no providers. - if (mPrefs.isEmpty()) { - String searchUri = getAddServiceUri(context); - if (!TextUtils.isEmpty(searchUri)) { - group.addPreference(newAddServicePreference(searchUri, context)); - } - } - } - - /** - * Returns the "add service" URI to show the play store. It will first try and use the - * credential manager specific search URI and if that is null it will fallback to the autofill - * one. - */ - public @NonNull String getAddServiceUri(@NonNull Context context) { - // Check the credential manager gflag for a link. - String searchUri = - DeviceConfig.getString( - DeviceConfig.NAMESPACE_CREDENTIAL, - ADD_SERVICE_DEVICE_CONFIG, - mFlagOverrideForTest); - if (!TextUtils.isEmpty(searchUri)) { - return searchUri; - } - - // If not fall back on autofill. - return Settings.Secure.getStringForUser( - context.getContentResolver(), - Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI, - getUser()); } /** @@ -396,52 +378,59 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl @VisibleForTesting public Map buildPreferenceList( Context context, PreferenceGroup group) { - // Group the services by package name. - Map> groupedInfos = new HashMap<>(); - for (CredentialProviderInfo cpi : mServices) { - String packageName = cpi.getServiceInfo().packageName; - if (isProviderHiddenBecauseOfAutofill(packageName)) { + // Get the selected autofill provider. If it is the placeholder then replace it with an + // empty string. + String selectedAutofillProvider = + DefaultCombinedPicker.getSelectedAutofillProvider(mContext, getUser()); + if (TextUtils.equals( + selectedAutofillProvider, AUTOFILL_CREDMAN_ONLY_PROVIDER_PLACEHOLDER)) { + selectedAutofillProvider = ""; + } + + // Get the list of combined providers. + List providers = + CombinedProviderInfo.buildMergedList( + AutofillServiceInfo.getAvailableServices(context, getUser()), + mServices, + selectedAutofillProvider); + + // Get the provider that is displayed at the top. If there is none then hide + // everything. + CombinedProviderInfo topProvider = CombinedProviderInfo.getTopProvider(providers); + if (topProvider == null) { + setVisibility(false); + return new HashMap<>(); + } + + Map output = new HashMap<>(); + for (CombinedProviderInfo combinedInfo : providers) { + final String packageName = combinedInfo.getApplicationInfo().packageName; + + // If this provider is displayed at the top then we should not show it. + if (topProvider != null + && topProvider.getApplicationInfo().packageName.equals(packageName)) { continue; } - if (!groupedInfos.containsKey(packageName)) { - groupedInfos.put(packageName, new ArrayList<>()); + // If this is an autofill provider then don't show it here. + if (combinedInfo.getCredentialProviderInfos().isEmpty()) { + continue; } - groupedInfos.get(packageName).add(cpi); - } - - // Build the pref list. - Map output = new HashMap<>(); - for (String packageName : groupedInfos.keySet()) { - List infos = groupedInfos.get(packageName); - CredentialProviderInfo firstInfo = infos.get(0); - ServiceInfo firstServiceInfo = firstInfo.getServiceInfo(); - CharSequence title = firstInfo.getLabel(context); - Drawable icon = firstInfo.getServiceIcon(context); - - if (infos.size() > 1) { - // If there is more than one then group them under the package. - ApplicationInfo appInfo = firstServiceInfo.applicationInfo; - if (appInfo.nonLocalizedLabel != null) { - title = appInfo.loadLabel(mPm); - } - icon = mIconFactory.getBadgedIcon(appInfo, getUser()); - } - - // If there is no title then show the package manager. - if (TextUtils.isEmpty(title)) { - title = firstServiceInfo.packageName; - } + Drawable icon = combinedInfo.getAppIcon(context); + CharSequence title = combinedInfo.getAppName(context); // Build the pref and add it to the output & group. SwitchPreference pref = addProviderPreference( - context, title, icon, packageName, firstInfo.getSettingsSubtitle()); + context, title, icon, packageName, combinedInfo.getSettingsSubtitle()); output.put(packageName, pref); group.addPreference(pref); } + // Set the visibility if we have services. + setVisibility(!output.isEmpty()); + return output; } @@ -607,29 +596,6 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl return new NewProviderConfirmationDialogFragment(host, packageName, appName); } - /** If the provider is also the autofill provider then hide it. */ - @VisibleForTesting - public boolean isProviderHiddenBecauseOfAutofill(String packageName) { - final String autofillService = - Settings.Secure.getStringForUser( - mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, getUser()); - if (autofillService == null || TextUtils.isEmpty(autofillService)) { - return false; - } - if (packageName == null || TextUtils.isEmpty(packageName)) { - return false; - } - - return autofillService.startsWith(packageName); - } - - private boolean isAutofillPrefSelected() { - final String autofillService = - Settings.Secure.getStringForUser( - mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, getUser()); - return !TextUtils.isEmpty(autofillService); - } - @VisibleForTesting void completeEnableProviderDialogBox( int whichButton, String packageName, boolean setActivityResult) { diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java index 41eaf9657a4..cfaf7a211db 100644 --- a/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java +++ b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java @@ -16,21 +16,27 @@ package com.android.settings.applications.credentials; +import android.annotation.Nullable; import android.app.Activity; import android.app.settings.SettingsEnums; -import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.ServiceInfo; +import android.credentials.CredentialManager; +import android.credentials.CredentialProviderInfo; +import android.credentials.SetEnabledProvidersException; import android.net.Uri; import android.os.Bundle; +import android.os.OutcomeReceiver; import android.os.UserHandle; import android.provider.Settings; -import android.service.autofill.AutofillService; import android.service.autofill.AutofillServiceInfo; import android.text.Html; import android.text.TextUtils; +import android.util.Log; +import androidx.core.content.ContextCompat; import androidx.preference.Preference; import com.android.internal.content.PackageMonitor; @@ -47,8 +53,8 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment { private static final String TAG = "DefaultCombinedPicker"; - public static final String SETTING = Settings.Secure.AUTOFILL_SERVICE; - public static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE); + public static final String AUTOFILL_SETTING = Settings.Secure.AUTOFILL_SERVICE; + public static final String CREDENTIAL_SETTING = Settings.Secure.CREDENTIAL_SERVICE; /** Extra set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. */ public static final String EXTRA_PACKAGE_NAME = "package_name"; @@ -56,6 +62,8 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment { /** Set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. */ private DialogInterface.OnClickListener mCancelListener; + private CredentialManager mCredentialManager; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -71,6 +79,7 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment { // ACTION_REQUEST_SET_AUTOFILL_SERVICE and we should always use the calling uid. mUserId = UserHandle.myUserId(); } + mSettingsPackageMonitor.register(activity, activity.getMainLooper(), false); update(); } @@ -187,35 +196,76 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment { } } - @Override + /** + * Get the Credential Manager service if we haven't already got it. We need to get the service + * later because if we do it in onCreate it will fail. + */ + private @Nullable CredentialManager getCredentialProviderService() { + if (mCredentialManager == null) { + mCredentialManager = getContext().getSystemService(CredentialManager.class); + } + return mCredentialManager; + } + + private List getAllProviders() { + final Context context = getContext(); + final List autofillProviders = + AutofillServiceInfo.getAvailableServices(context, mUserId); + + final CredentialManager service = getCredentialProviderService(); + final List credManProviders = new ArrayList<>(); + if (service != null) { + credManProviders.addAll( + service.getCredentialProviderServices( + mUserId, CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY)); + } + + final String selectedAutofillProvider = getSelectedAutofillProvider(context, mUserId); + return CombinedProviderInfo.buildMergedList( + autofillProviders, credManProviders, selectedAutofillProvider); + } + + public static String getSelectedAutofillProvider(Context context, int userId) { + return Settings.Secure.getStringForUser( + context.getContentResolver(), AUTOFILL_SETTING, userId); + } + protected List getCandidates() { + final Context context = getContext(); + final List allProviders = getAllProviders(); final List candidates = new ArrayList<>(); - final List services = - AutofillServiceInfo.getAvailableServices(getContext(), mUserId); - for (AutofillServiceInfo asi : services) { - candidates.add( - new DefaultAppInfo( - getContext(), mPm, mUserId, asi.getServiceInfo().getComponentName())); + + for (CombinedProviderInfo cpi : allProviders) { + ServiceInfo brandingService = cpi.getBrandingService(); + if (brandingService == null) { + candidates.add( + new DefaultAppInfo( + context, + mPm, + mUserId, + cpi.getApplicationInfo(), + cpi.getSettingsSubtitle(), + true)); + } else { + candidates.add( + new DefaultAppInfo( + context, + mPm, + mUserId, + brandingService, + cpi.getSettingsSubtitle(), + true)); + } } return candidates; } - public static String getDefaultKey(Context context, int userId) { - String setting = - Settings.Secure.getStringForUser(context.getContentResolver(), SETTING, userId); - if (setting != null) { - ComponentName componentName = ComponentName.unflattenFromString(setting); - if (componentName != null) { - return componentName.flattenToString(); - } - } - return null; - } - @Override protected String getDefaultKey() { - return getDefaultKey(getContext(), mUserId); + final CombinedProviderInfo topProvider = + CombinedProviderInfo.getTopProvider(getAllProviders()); + return topProvider == null ? "" : topProvider.getApplicationInfo().packageName; } @Override @@ -234,7 +284,39 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment { @Override protected boolean setDefaultKey(String key) { - Settings.Secure.putStringForUser(getContext().getContentResolver(), SETTING, key, mUserId); + // Get the list of providers and see if any match the key (package name). + final List allProviders = getAllProviders(); + CombinedProviderInfo matchedProvider = null; + for (CombinedProviderInfo cpi : allProviders) { + if (cpi.getApplicationInfo().packageName.equals(key)) { + matchedProvider = cpi; + break; + } + } + + // If there were none then clear the stored providers. + if (matchedProvider == null) { + setProviders(null, new ArrayList<>()); + return true; + } + + // Get the component names and save them. + final List credManComponents = new ArrayList<>(); + for (CredentialProviderInfo pi : matchedProvider.getCredentialProviderInfos()) { + credManComponents.add(pi.getServiceInfo().getComponentName().flattenToString()); + } + + String autofillValue = null; + if (matchedProvider.getAutofillServiceInfo() != null) { + autofillValue = + matchedProvider + .getAutofillServiceInfo() + .getServiceInfo() + .getComponentName() + .flattenToString(); + } + + setProviders(autofillValue, credManComponents); // Check if activity was launched from Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE // intent, and set proper result if so... @@ -255,4 +337,38 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment { return true; } + + private void setProviders(String autofillProvider, List credManProviders) { + if (TextUtils.isEmpty(autofillProvider)) { + if (credManProviders.size() > 0) { + autofillProvider = + CredentialManagerPreferenceController + .AUTOFILL_CREDMAN_ONLY_PROVIDER_PLACEHOLDER; + } + } + + Settings.Secure.putStringForUser( + getContext().getContentResolver(), AUTOFILL_SETTING, autofillProvider, mUserId); + + CredentialManager service = getCredentialProviderService(); + if (service == null) { + return; + } + + service.setEnabledProviders( + credManProviders, + mUserId, + ContextCompat.getMainExecutor(getContext()), + new OutcomeReceiver() { + @Override + public void onResult(Void result) { + Log.i(TAG, "setEnabledProviders success"); + } + + @Override + public void onError(SetEnabledProvidersException e) { + Log.e(TAG, "setEnabledProviders error: " + e.toString()); + } + }); + } } diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java index 00125a02342..ca049bcdd48 100644 --- a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java +++ b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java @@ -23,7 +23,9 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.credentials.CredentialManager; +import android.credentials.CredentialProviderInfo; import android.provider.Settings; +import android.service.autofill.AutofillService; import android.service.autofill.AutofillServiceInfo; import android.text.TextUtils; import android.util.Log; @@ -36,6 +38,9 @@ import java.util.List; public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController { + private static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE); + private static final String TAG = "DefaultCombinedPreferenceController"; + private final AutofillManager mAutofillManager; private final CredentialManager mCredentialManager; @@ -76,26 +81,57 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon @Override protected DefaultAppInfo getDefaultAppInfo() { - final String flattenComponent = - Settings.Secure.getString( - mContext.getContentResolver(), DefaultCombinedPicker.SETTING); - if (!TextUtils.isEmpty(flattenComponent)) { - DefaultAppInfo appInfo = - new DefaultAppInfo( - mContext, - mPackageManager, - mUserId, - ComponentName.unflattenFromString(flattenComponent)); - return appInfo; + List providers = getAllProviders(mUserId); + CombinedProviderInfo topProvider = CombinedProviderInfo.getTopProvider(providers); + if (topProvider != null) { + ServiceInfo brandingService = topProvider.getBrandingService(); + if (brandingService == null) { + return new DefaultAppInfo( + mContext, + mPackageManager, + mUserId, + topProvider.getApplicationInfo(), + topProvider.getSettingsSubtitle(), + true); + } else { + return new DefaultAppInfo( + mContext, + mPackageManager, + mUserId, + brandingService, + topProvider.getSettingsSubtitle(), + true); + } } return null; } + private List getAllProviders(int userId) { + final List autofillProviders = + AutofillServiceInfo.getAvailableServices(mContext, userId); + final List credManProviders = + mCredentialManager.getCredentialProviderServices( + userId, CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY); + final String selectedAutofillProvider = + Settings.Secure.getStringForUser( + mContext.getContentResolver(), + DefaultCombinedPicker.AUTOFILL_SETTING, + userId); + + return CombinedProviderInfo.buildMergedList( + autofillProviders, credManProviders, selectedAutofillProvider); + } + @Override protected boolean showLabelAsTitle() { return true; } + @Override + protected boolean showAppSummary() { + return true; + } + /** Provides Intent to setting activity for the specified autofill service. */ static final class AutofillSettingIntentProvider { @@ -113,9 +149,7 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon final List resolveInfos = mContext.getPackageManager() .queryIntentServicesAsUser( - DefaultCombinedPicker.AUTOFILL_PROBE, - PackageManager.GET_META_DATA, - mUserId); + AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId); for (ResolveInfo resolveInfo : resolveInfos) { final ServiceInfo serviceInfo = resolveInfo.serviceInfo; @@ -130,9 +164,7 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon .getSettingsActivity(); } catch (SecurityException e) { // Service does not declare the proper permission, ignore it. - Log.w( - "AutofillSettingIntentProvider", - "Error getting info for " + serviceInfo + ": " + e); + Log.w(TAG, "Error getting info for " + serviceInfo + ": " + e); return null; } if (TextUtils.isEmpty(settingsActivity)) { diff --git a/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java index cc41e971071..0990f3b2f9f 100644 --- a/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java +++ b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java @@ -52,7 +52,7 @@ public class DefaultWorkCombinedPreferenceController extends DefaultCombinedPref final String flattenComponent = Settings.Secure.getStringForUser( mContext.getContentResolver(), - DefaultCombinedPicker.SETTING, + DefaultCombinedPicker.AUTOFILL_SETTING, mUserHandle.getIdentifier()); if (!TextUtils.isEmpty(flattenComponent)) { DefaultAppInfo appInfo = diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java index d962692e2df..30c79734025 100644 --- a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java +++ b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.applications.defaultapps; import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM; +import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -65,16 +66,21 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC ((TwoTargetPreference) preference).setIconSize(ICON_SIZE_MEDIUM); } if (!TextUtils.isEmpty(defaultAppLabel)) { - if (showLabelAsTitle()) { + if (showLabelAsTitle() && showAppSummary()) { + preference.setTitle(defaultAppLabel); + preference.setSummary(getDefaultAppSummary()); + } else if (showLabelAsTitle()) { preference.setTitle(defaultAppLabel); } else { preference.setSummary(defaultAppLabel); } + preference.setIcon(Utils.getSafeIcon(getDefaultAppIcon())); } else { Log.d(TAG, "No default app"); if (showLabelAsTitle()) { preference.setTitle(R.string.app_list_preference_none); + preference.setSummary(null); } else { preference.setSummary(R.string.app_list_preference_none); } @@ -89,8 +95,7 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC } final Intent settingIntent = getSettingIntent(app); if (settingIntent != null) { - ((GearPreference) preference).setOnGearClickListener( - p -> startActivity(settingIntent)); + ((GearPreference) preference).setOnGearClickListener(p -> startActivity(settingIntent)); } else { ((GearPreference) preference).setOnGearClickListener(null); } @@ -102,21 +107,22 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC protected abstract DefaultAppInfo getDefaultAppInfo(); - /** - * Returns an optional intent that will be launched when clicking "gear" icon. - */ + /** Returns an optional intent that will be launched when clicking "gear" icon. */ protected Intent getSettingIntent(DefaultAppInfo info) { - //By default return null. It's up to subclasses to provide logic. + // By default return null. It's up to subclasses to provide logic. return null; } - /** - * Whether to show the default app label as the title, instead of as the summary. - */ + /** Whether to show the default app label as the title, instead of as the summary. */ protected boolean showLabelAsTitle() { return false; } + /** Whether to show the app summary. */ + protected boolean showAppSummary() { + return false; + } + public Drawable getDefaultAppIcon() { if (!isAvailable()) { return null; @@ -138,4 +144,15 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC } return null; } + + private @Nullable CharSequence getDefaultAppSummary() { + if (!isAvailable()) { + return null; + } + final DefaultAppInfo app = getDefaultAppInfo(); + if (app != null) { + return app.getSummary(); + } + return null; + } } diff --git a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java index 60c7d45645a..e369f527d85 100644 --- a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java @@ -121,64 +121,6 @@ public class CredentialManagerPreferenceControllerTest { assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE); } - @Test - public void verifyHiddenIfAutofillSelectedProvider() { - CredentialManagerPreferenceController controller = - createControllerWithServices(Collections.emptyList()); - - // Set the autofill provider. - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.AUTOFILL_SERVICE, "com.example.test/AutofillClass", - UserHandle.myUserId()); - - // Verify the error cases - assertThat(controller.isProviderHiddenBecauseOfAutofill(null)).isFalse(); - assertThat(controller.isProviderHiddenBecauseOfAutofill("")).isFalse(); - assertThat(controller.isProviderHiddenBecauseOfAutofill("test")).isFalse(); - - // Verify the example. - assertThat(controller.isProviderHiddenBecauseOfAutofill("com.example.test")).isTrue(); - } - - @Test - public void displayPreference_noServices_noPreferencesAdded_useAutofillUri() { - Settings.Secure.putStringForUser( - mContext.getContentResolver(), - Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI, - "test", - UserHandle.myUserId()); - - CredentialManagerPreferenceController controller = - createControllerWithServices(Collections.emptyList()); - controller.displayPreference(mScreen); - assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(1); - - Preference pref = mCredentialsPreferenceCategory.getPreference(0); - assertThat(pref.getTitle()).isEqualTo("Add service"); - - assertThat(controller.getAddServiceUri(mContext)).isEqualTo("test"); - } - - @Test - public void displayPreference_noServices_noPreferencesAdded_useCredManUri() { - Settings.Secure.putStringForUser( - mContext.getContentResolver(), - Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI, - "test", - UserHandle.myUserId()); - - CredentialManagerPreferenceController controller = - createControllerWithServicesAndAddServiceOverride( - Collections.emptyList(), "credman"); - controller.displayPreference(mScreen); - assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(1); - - Preference pref = mCredentialsPreferenceCategory.getPreference(0); - assertThat(pref.getTitle()).isEqualTo("Add service"); - - assertThat(controller.getAddServiceUri(mContext)).isEqualTo("credman"); - } - @Test public void displayPreference_withServices_preferencesAdded() { CredentialManagerPreferenceController controller = From 2aca9d40f870c464b956eb3c8efc322cfe9b604e Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Wed, 3 May 2023 11:59:43 +0800 Subject: [PATCH 09/12] Use correct App info screen's trash can icon Fix: 280120259 Test: Visual Change-Id: I954c344c3f5fdd256de7519f1534c982c084a521 --- .../spa/app/appinfo/AppUninstallButton.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt b/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt index c1a308f2dce..6b3535be353 100644 --- a/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt +++ b/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt @@ -17,18 +17,17 @@ package com.android.settings.spa.app.appinfo import android.app.settings.SettingsEnums -import android.content.Intent; +import android.content.Intent import android.content.om.OverlayManager import android.content.pm.ApplicationInfo import android.os.UserHandle import android.os.UserManager -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Delete - -import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminAdd +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource import com.android.settings.R -import com.android.settings.spa.SpaActivity import com.android.settings.Utils +import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminAdd import com.android.settingslib.spa.widget.button.ActionButton import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager import com.android.settingslib.spaprivileged.model.app.hasFlag @@ -41,6 +40,7 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter) private val overlayManager = context.getSystemService(OverlayManager::class.java)!! private val userManager = context.getSystemService(UserManager::class.java)!! + @Composable fun getActionButton(app: ApplicationInfo): ActionButton? { if (app.isSystemApp || app.isInstantApp) return null return uninstallButton(app = app, enabled = isUninstallButtonEnabled(app)) @@ -89,10 +89,11 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter) isResourceOverlay && overlayManager.getOverlayInfo(packageName, userHandle)?.isEnabled == true + @Composable private fun uninstallButton(app: ApplicationInfo, enabled: Boolean) = ActionButton( text = if (isCloneApp(app)) context.getString(R.string.delete) else context.getString(R.string.uninstall_text), - imageVector = Icons.Outlined.Delete, + imageVector = ImageVector.vectorResource(R.drawable.ic_settings_delete), enabled = enabled, ) { onUninstallClicked(app) } @@ -100,7 +101,7 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter) if (appButtonRepository.isUninstallBlockedByAdmin(app)) { return } else if (app.isActiveAdmin(context)) { - var uninstallDaIntent = Intent(context, DeviceAdminAdd::class.java) + val uninstallDaIntent = Intent(context, DeviceAdminAdd::class.java) uninstallDaIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME, app.packageName) packageInfoPresenter.logAction( From b663f17d313b5e914e23fcca963f5ad7ffa55b3c Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Wed, 3 May 2023 13:06:28 +0800 Subject: [PATCH 10/12] Add new page enum for Account detail Bug: 260054568 Test: rebuild Change-Id: I332bebf4f4ce725759860f937f50c8a6f21d8648 --- .../settings/accounts/AccountDetailDashboardFragment.java | 2 +- .../settings/accounts/AccountPersonalDashboardFragment.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/accounts/AccountDetailDashboardFragment.java b/src/com/android/settings/accounts/AccountDetailDashboardFragment.java index 0668c62b758..8e1d627d922 100644 --- a/src/com/android/settings/accounts/AccountDetailDashboardFragment.java +++ b/src/com/android/settings/accounts/AccountDetailDashboardFragment.java @@ -120,7 +120,7 @@ public class AccountDetailDashboardFragment extends DashboardFragment { @Override public int getMetricsCategory() { - return SettingsEnums.ACCOUNT; + return SettingsEnums.ACCOUNT_DETAIL; } @Override diff --git a/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java b/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java index a87eb7dd774..05afaa229bb 100644 --- a/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java +++ b/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java @@ -42,7 +42,7 @@ public class AccountPersonalDashboardFragment extends DashboardFragment { @Override public int getMetricsCategory() { - return SettingsEnums.ACCOUNT; + return SettingsEnums.ACCOUNT_PERSONAL; } @Override From fae664c79cc7f0df989e03df037654189fe224ed Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Tue, 11 Apr 2023 16:01:02 +0800 Subject: [PATCH 11/12] Reducing slice requesting logging Bug: 253684322 Test: rebuild Change-Id: If0e884875e09bd43b88f6b5f205ad450de488552 --- .../slices/SettingsSliceProvider.java | 8 +++++ .../settings/slices/SliceBuilderUtils.java | 8 ----- .../slices/SliceBuilderUtilsTest.java | 34 ++----------------- 3 files changed, 10 insertions(+), 40 deletions(-) diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index 2672906d25e..12272a7523a 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.READ_SEARCH_INDEXABLES; import static android.app.slice.Slice.HINT_PARTIAL; import android.app.PendingIntent; +import android.app.settings.SettingsEnums; import android.app.slice.SliceManager; import android.content.ContentResolver; import android.content.Context; @@ -162,6 +163,13 @@ public class SettingsSliceProvider extends SliceProvider { Log.d(TAG, "onSlicePinned: " + sliceUri); mFirstSlicePinned = true; } + FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider() + .action(SettingsEnums.PAGE_UNKNOWN, + SettingsEnums.ACTION_SETTINGS_SLICE_REQUESTED, + SettingsEnums.PAGE_UNKNOWN, + sliceUri.getLastPathSegment(), + 0); + if (CustomSliceRegistry.isValidUri(sliceUri)) { final Context context = getContext(); final CustomSliceable sliceable = FeatureFactory.getFactory(context) diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java index 8d5a0b4d8c7..6b69540ace1 100644 --- a/src/com/android/settings/slices/SliceBuilderUtils.java +++ b/src/com/android/settings/slices/SliceBuilderUtils.java @@ -24,7 +24,6 @@ import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY; import android.annotation.ColorInt; import android.app.PendingIntent; -import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -51,7 +50,6 @@ import com.android.settings.core.BasePreferenceController; import com.android.settings.core.SliderPreferenceController; import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.TogglePreferenceController; -import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; import java.util.Arrays; @@ -78,12 +76,6 @@ public class SliceBuilderUtils { public static Slice buildSlice(Context context, SliceData sliceData) { Log.d(TAG, "Creating slice for: " + sliceData.getPreferenceController()); final BasePreferenceController controller = getPreferenceController(context, sliceData); - FeatureFactory.getFactory(context).getMetricsFeatureProvider() - .action(SettingsEnums.PAGE_UNKNOWN, - SettingsEnums.ACTION_SETTINGS_SLICE_REQUESTED, - SettingsEnums.PAGE_UNKNOWN, - sliceData.getKey(), - 0); if (!controller.isAvailable()) { // Cannot guarantee setting page is accessible, let the presenter handle error case. diff --git a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java index 2941ca0acd5..45fea570970 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java @@ -20,9 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -37,10 +35,8 @@ import androidx.slice.SliceProvider; import androidx.slice.core.SliceAction; import androidx.slice.widget.SliceLiveData; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; -import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeInvalidSliderController; import com.android.settings.testutils.FakeSliderController; import com.android.settings.testutils.FakeToggleController; @@ -70,12 +66,10 @@ public class SliceBuilderUtilsTest { private final Class CONTEXT_CONTROLLER = FakeContextOnlyPreferenceController.class; private Context mContext; - private FakeFeatureFactory mFeatureFactory; @Before public void setUp() { mContext = RuntimeEnvironment.application; - mFeatureFactory = FakeFeatureFactory.setupForTest(); // Set-up specs for SliceMetadata. SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); } @@ -93,12 +87,6 @@ public class SliceBuilderUtilsTest { final SliceData mockData = getMockData(TOGGLE_CONTROLLER, SliceData.SliceType.SWITCH); final Slice slice = SliceBuilderUtils.buildSlice(mContext, mockData); - verify(mFeatureFactory.metricsFeatureProvider) - .action(SettingsEnums.PAGE_UNKNOWN, - MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED, - SettingsEnums.PAGE_UNKNOWN, - mockData.getKey(), - 0); SliceTester.testSettingsToggleSlice(mContext, slice, mockData); } @@ -107,12 +95,6 @@ public class SliceBuilderUtilsTest { final SliceData data = getMockData(SLIDER_CONTROLLER, SliceData.SliceType.SLIDER); final Slice slice = SliceBuilderUtils.buildSlice(mContext, data); - verify(mFeatureFactory.metricsFeatureProvider) - .action(SettingsEnums.PAGE_UNKNOWN, - MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED, - SettingsEnums.PAGE_UNKNOWN, - data.getKey(), - 0); SliceTester.testSettingsSliderSlice(mContext, slice, data); } @@ -298,18 +280,11 @@ public class SliceBuilderUtilsTest { final Slice slice = SliceBuilderUtils.buildSlice(mContext, data); - verify(mFeatureFactory.metricsFeatureProvider) - .action(SettingsEnums.PAGE_UNKNOWN, - MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED, - SettingsEnums.PAGE_UNKNOWN, - data.getKey(), - 0); - SliceTester.testSettingsUnavailableSlice(mContext, slice, data); } @Test - public void testConditionallyUnavailableSlice_validTitleSummary() { + public void testConditionallyUnavailableSlice_sliceShouldBeNull() { final SliceData data = getMockData(FakeUnavailablePreferenceController.class, SliceData.SliceType.SWITCH); Settings.Global.putInt(mContext.getContentResolver(), @@ -318,12 +293,7 @@ public class SliceBuilderUtilsTest { final Slice slice = SliceBuilderUtils.buildSlice(mContext, data); - verify(mFeatureFactory.metricsFeatureProvider) - .action(SettingsEnums.PAGE_UNKNOWN, - MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED, - SettingsEnums.PAGE_UNKNOWN, - data.getKey(), - 0); + assertThat(slice).isNull(); } @Test From 77f76267d296561a7a0f5c2977ab829da91461c5 Mon Sep 17 00:00:00 2001 From: Priyanka Advani Date: Wed, 3 May 2023 16:05:25 +0000 Subject: [PATCH 12/12] Revert "Disable Settings large-screen optimization" Revert submission 22926596-disable_large_screen_opt Reason for revert: b/280606740 Reverted changes: /q/submissionid:22926596-disable_large_screen_opt Change-Id: Ib918705206dc6b8bcbce21f83494d1108c6b8393 --- .../android/settings/SettingsApplication.java | 5 ++- .../android/settings/SettingsInitialize.java | 4 +- .../ActivityEmbeddingUtils.java | 41 ++----------------- .../homepage/SettingsHomepageActivity.java | 3 +- 4 files changed, 11 insertions(+), 42 deletions(-) diff --git a/src/com/android/settings/SettingsApplication.java b/src/com/android/settings/SettingsApplication.java index 7d8055d6337..49cb85e2392 100644 --- a/src/com/android/settings/SettingsApplication.java +++ b/src/com/android/settings/SettingsApplication.java @@ -22,8 +22,9 @@ import android.net.Uri; import android.provider.Settings; import android.util.FeatureFlagUtils; +import androidx.window.embedding.SplitController; + import com.android.settings.activityembedding.ActivityEmbeddingRulesController; -import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.core.instrumentation.ElapsedTimeUtils; import com.android.settings.homepage.SettingsHomepageActivity; import com.android.settings.spa.SettingsSpaEnvironment; @@ -52,7 +53,7 @@ public class SettingsApplication extends Application { setSpaEnvironment(); if (FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN) - && ActivityEmbeddingUtils.isSettingsSplitEnabled(this)) { + && SplitController.getInstance(this).isSplitSupported()) { if (WizardManagerHelper.isUserSetupComplete(this)) { new ActivityEmbeddingRulesController(this).initRules(); } else { diff --git a/src/com/android/settings/SettingsInitialize.java b/src/com/android/settings/SettingsInitialize.java index 37a564b7c6a..1d235233f0e 100644 --- a/src/com/android/settings/SettingsInitialize.java +++ b/src/com/android/settings/SettingsInitialize.java @@ -37,9 +37,9 @@ import android.os.UserManager; import android.util.Log; import androidx.annotation.VisibleForTesting; +import androidx.window.embedding.SplitController; import com.android.settings.Settings.CreateShortcutActivity; -import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.homepage.DeepLinkHomepageActivity; import com.android.settings.search.SearchStateReceiver; import com.android.settingslib.utils.ThreadUtils; @@ -166,7 +166,7 @@ public class SettingsInitialize extends BroadcastReceiver { DeepLinkHomepageActivity.class); final ComponentName searchStateReceiver = new ComponentName(context, SearchStateReceiver.class); - final int enableState = ActivityEmbeddingUtils.isSettingsSplitEnabled(context) + final int enableState = SplitController.getInstance(context).isSplitSupported() ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; pm.setComponentEnabledSetting(deepLinkHome, enableState, PackageManager.DONT_KILL_APP); diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java b/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java index ae890f87632..a0dac5a9ec5 100644 --- a/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java +++ b/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java @@ -18,7 +18,6 @@ package com.android.settings.activityembedding; import android.app.Activity; import android.content.Context; -import android.os.SystemProperties; import android.util.DisplayMetrics; import android.util.FeatureFlagUtils; import android.util.Log; @@ -40,21 +39,6 @@ public class ActivityEmbeddingUtils { private static final int MIN_SMALLEST_SCREEN_SPLIT_WIDTH_DP = 600; // The minimum width of the activity to show the regular homepage layout. private static final float MIN_REGULAR_HOMEPAGE_LAYOUT_WIDTH_DP = 380f; - - /** - * Indicates whether to enable large screen optimization if the device supports - * the Activity Embedding split feature. - *

- * Note that the large screen optimization won't be enabled if the device doesn't support the - * Activity Embedding feature regardless of this property value. - * - * @see androidx.window.embedding.SplitController#getSplitSupportStatus - * @see androidx.window.embedding.SplitController.SplitSupportStatus#SPLIT_AVAILABLE - * @see androidx.window.embedding.SplitController.SplitSupportStatus#SPLIT_UNAVAILABLE - */ - private static final boolean SHOULD_ENABLE_LARGE_SCREEN_OPTIMIZATION = - SystemProperties.getBoolean("persist.settings.large_screen_opt.enabled", true); - private static final String TAG = "ActivityEmbeddingUtils"; /** Get the smallest width dp of the window when the split should be used. */ @@ -78,35 +62,18 @@ public class ActivityEmbeddingUtils { return context.getResources().getFloat(R.dimen.config_activity_embed_split_ratio); } - /** - * Returns {@code true} to indicate that Settings app support the Activity Embedding feature on - * this device. Returns {@code false}, otherwise. - */ - public static boolean isSettingsSplitEnabled(Context context) { - return SHOULD_ENABLE_LARGE_SCREEN_OPTIMIZATION - && SplitController.getInstance(context).getSplitSupportStatus() - == SplitController.SplitSupportStatus.SPLIT_AVAILABLE; - } - - /** - * Checks whether to support embedding activity feature with following conditions: - *

    - *
  • Whether {@link #isSettingsSplitEnabled(Context)}
  • - *
  • Whether {@link FeatureFlagUtils#SETTINGS_SUPPORT_LARGE_SCREEN} is enabled
  • - *
  • Whether User setup is completed
  • - *
- */ + /** Whether to support embedding activity feature. */ public static boolean isEmbeddingActivityEnabled(Context context) { boolean isFlagEnabled = FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN); - boolean isSettingsSplitSupported = isSettingsSplitEnabled(context); + boolean isSplitSupported = SplitController.getInstance(context).isSplitSupported(); boolean isUserSetupComplete = WizardManagerHelper.isUserSetupComplete(context); Log.d(TAG, "isFlagEnabled = " + isFlagEnabled); - Log.d(TAG, "isSettingsSplitSupported = " + isSettingsSplitSupported); + Log.d(TAG, "isSplitSupported = " + isSplitSupported); Log.d(TAG, "isUserSetupComplete = " + isUserSetupComplete); - return isFlagEnabled && isSettingsSplitSupported && isUserSetupComplete; + return isFlagEnabled && isSplitSupported && isUserSetupComplete; } /** Whether to show the regular or simplified homepage layout. */ diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 03bc1b32fb2..7713e270425 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -58,6 +58,7 @@ import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import androidx.window.embedding.ActivityEmbeddingController; +import androidx.window.embedding.SplitController; import androidx.window.embedding.SplitRule; import com.android.settings.R; @@ -428,7 +429,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements private boolean shouldLaunchDeepLinkIntentToRight() { if (!FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN) - || !ActivityEmbeddingUtils.isSettingsSplitEnabled(this)) { + || !SplitController.getInstance(this).isSplitSupported()) { return false; }