diff --git a/src/com/android/settings/datetime/TimeFeedbackPreferenceController.java b/src/com/android/settings/datetime/TimeFeedbackPreferenceController.java index 907c202e09f..ff3cb4ba49c 100644 --- a/src/com/android/settings/datetime/TimeFeedbackPreferenceController.java +++ b/src/com/android/settings/datetime/TimeFeedbackPreferenceController.java @@ -19,9 +19,12 @@ package com.android.settings.datetime; import static android.content.Intent.URI_INTENT_SCHEME; import android.app.ActivityManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.text.TextUtils; +import android.util.Log; import androidx.preference.Preference; @@ -40,17 +43,22 @@ public class TimeFeedbackPreferenceController extends BasePreferenceController implements PreferenceControllerMixin { + private static final String TAG = "TimeFeedbackController"; + + private final PackageManager mPackageManager; private final String mIntentUri; private final int mAvailabilityStatus; public TimeFeedbackPreferenceController(Context context, String preferenceKey) { - this(context, preferenceKey, context.getResources().getString( + this(context, context.getPackageManager(), preferenceKey, context.getResources().getString( R.string.config_time_feedback_intent_uri)); } @VisibleForTesting - TimeFeedbackPreferenceController(Context context, String preferenceKey, String intentUri) { + TimeFeedbackPreferenceController(Context context, PackageManager packageManager, + String preferenceKey, String intentUri) { super(context, preferenceKey); + mPackageManager = packageManager; mIntentUri = intentUri; mAvailabilityStatus = TextUtils.isEmpty(mIntentUri) ? UNSUPPORTED_ON_DEVICE : AVAILABLE; } @@ -70,6 +78,9 @@ public class TimeFeedbackPreferenceController if (!DateTimeLaunchUtils.isFeedbackFeatureSupported()) { return UNSUPPORTED_ON_DEVICE; } + if (!isTimeFeedbackTargetAvailable()) { + return CONDITIONALLY_UNAVAILABLE; + } return mAvailabilityStatus; } @@ -89,7 +100,25 @@ public class TimeFeedbackPreferenceController mContext.startActivity(intent); return true; } catch (URISyntaxException e) { - throw new IllegalArgumentException("Bad intent configuration: " + mIntentUri, e); + Log.e(TAG, "Bad intent configuration: " + mIntentUri, e); + return false; } } + + private boolean isTimeFeedbackTargetAvailable() { + Intent intent; + try { + intent = Intent.parseUri(mIntentUri, URI_INTENT_SCHEME); + } catch (URISyntaxException e) { + Log.e(TAG, "Bad intent configuration: " + mIntentUri, e); + return false; + } + ComponentName resolvedActivity = intent.resolveActivity(mPackageManager); + + if (resolvedActivity == null) { + Log.w(TAG, "No valid target for the time feedback intent: " + intent); + return false; + } + return true; + } } diff --git a/tests/robotests/src/com/android/settings/datetime/TimeFeedbackPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/TimeFeedbackPreferenceControllerTest.java index f60e8319cb7..196aa36d8de 100644 --- a/tests/robotests/src/com/android/settings/datetime/TimeFeedbackPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/datetime/TimeFeedbackPreferenceControllerTest.java @@ -16,31 +16,64 @@ package com.android.settings.datetime; +import static android.provider.DeviceConfig.NAMESPACE_SETTINGS_UI; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.DeviceConfig; import androidx.preference.Preference; +import com.android.settings.flags.Flags; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class TimeFeedbackPreferenceControllerTest { + private static final String PACKAGE = "com.android.settings.test"; + private static final String TEST_INTENT_URI = + "intent:#Intent;" + + "action=com.android.settings.test.LAUNCH_USER_FEEDBACK;" + + "package=com.android.settings.test.target;" + + "end"; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock + private PackageManager mMockPackageManager; private Context mContext; @Before @@ -52,21 +85,47 @@ public class TimeFeedbackPreferenceControllerTest { public void emptyIntentUri_controllerNotAvailable() { String emptyIntentUri = ""; TimeFeedbackPreferenceController controller = - new TimeFeedbackPreferenceController(mContext, "test_key", emptyIntentUri); + new TimeFeedbackPreferenceController(mContext, mContext.getPackageManager(), + "test_key", emptyIntentUri); assertThat(controller.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); } + @Test + @EnableFlags({Flags.FLAG_DATETIME_FEEDBACK}) + public void validIntentUri_targetHandlerNotFound_returnsConditionallyUnavailable() { + DeviceConfig.setProperty(NAMESPACE_SETTINGS_UI, + DateTimeLaunchUtils.KEY_HELP_AND_FEEDBACK_FEATURE_SUPPORTED, "true", true); + when(mMockPackageManager.resolveActivity(any(), anyInt())).thenReturn(null); + + TimeFeedbackPreferenceController controller = + new TimeFeedbackPreferenceController(mContext, mMockPackageManager, "test_key", + TEST_INTENT_URI); + + assertThat(controller.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Test + @EnableFlags({Flags.FLAG_DATETIME_FEEDBACK}) + public void validIntentUri_targetHandlerAvailable_returnsAvailable() { + DeviceConfig.setProperty(NAMESPACE_SETTINGS_UI, + DateTimeLaunchUtils.KEY_HELP_AND_FEEDBACK_FEATURE_SUPPORTED, "true", true); + when(mMockPackageManager.resolveActivity(any(), anyInt())).thenReturn( + createDummyResolveInfo()); + + TimeFeedbackPreferenceController controller = + new TimeFeedbackPreferenceController(mContext, mMockPackageManager, "test_key", + TEST_INTENT_URI); + + assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + @Test public void clickPreference() { Preference preference = new Preference(mContext); - String intentUri = - "intent:#Intent;" - + "action=com.android.settings.test.LAUNCH_USER_FEEDBACK;" - + "package=com.android.settings.test.target;" - + "end"; TimeFeedbackPreferenceController controller = - new TimeFeedbackPreferenceController(mContext, "test_key", intentUri); + new TimeFeedbackPreferenceController(mContext, mContext.getPackageManager(), + "test_key", TEST_INTENT_URI); // Click a preference that's not controlled by this controller. preference.setKey("fake_key"); @@ -87,4 +146,16 @@ public class TimeFeedbackPreferenceControllerTest { "com.android.settings.test.LAUNCH_USER_FEEDBACK"); assertThat(actualIntent.getPackage()).isEqualTo("com.android.settings.test.target"); } + + private static ResolveInfo createDummyResolveInfo() { + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = PACKAGE; + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.applicationInfo = applicationInfo; + activityInfo.name = "TestActivity"; + + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = activityInfo; + return resolveInfo; + } }