Hide "Send feedback" button if no target exists.

This change hides the "Send feedback" button  when a target for
the intent is not found. This will ensure the button is not
visible in cases when clicking it would have done nothing.

Bug: 283239837
Flag: com.android.settings.flags.datetime_feedback
Test: Test: atest TimeFeedbackPreferenceControllerTest.java
Change-Id: I8bb18c313925a7dc7ac07a1fb4c2f9e2d98352db
This commit is contained in:
Kanyinsola Fapohunda
2025-02-08 03:23:41 +00:00
parent 4141fac1db
commit 8c427e654e
2 changed files with 110 additions and 10 deletions

View File

@@ -19,9 +19,12 @@ package com.android.settings.datetime;
import static android.content.Intent.URI_INTENT_SCHEME; import static android.content.Intent.URI_INTENT_SCHEME;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import androidx.preference.Preference; import androidx.preference.Preference;
@@ -40,17 +43,22 @@ public class TimeFeedbackPreferenceController
extends BasePreferenceController extends BasePreferenceController
implements PreferenceControllerMixin { implements PreferenceControllerMixin {
private static final String TAG = "TimeFeedbackController";
private final PackageManager mPackageManager;
private final String mIntentUri; private final String mIntentUri;
private final int mAvailabilityStatus; private final int mAvailabilityStatus;
public TimeFeedbackPreferenceController(Context context, String preferenceKey) { 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)); R.string.config_time_feedback_intent_uri));
} }
@VisibleForTesting @VisibleForTesting
TimeFeedbackPreferenceController(Context context, String preferenceKey, String intentUri) { TimeFeedbackPreferenceController(Context context, PackageManager packageManager,
String preferenceKey, String intentUri) {
super(context, preferenceKey); super(context, preferenceKey);
mPackageManager = packageManager;
mIntentUri = intentUri; mIntentUri = intentUri;
mAvailabilityStatus = TextUtils.isEmpty(mIntentUri) ? UNSUPPORTED_ON_DEVICE : AVAILABLE; mAvailabilityStatus = TextUtils.isEmpty(mIntentUri) ? UNSUPPORTED_ON_DEVICE : AVAILABLE;
} }
@@ -70,6 +78,9 @@ public class TimeFeedbackPreferenceController
if (!DateTimeLaunchUtils.isFeedbackFeatureSupported()) { if (!DateTimeLaunchUtils.isFeedbackFeatureSupported()) {
return UNSUPPORTED_ON_DEVICE; return UNSUPPORTED_ON_DEVICE;
} }
if (!isTimeFeedbackTargetAvailable()) {
return CONDITIONALLY_UNAVAILABLE;
}
return mAvailabilityStatus; return mAvailabilityStatus;
} }
@@ -89,7 +100,25 @@ public class TimeFeedbackPreferenceController
mContext.startActivity(intent); mContext.startActivity(intent);
return true; return true;
} catch (URISyntaxException e) { } 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;
}
} }

View File

@@ -16,31 +16,64 @@
package com.android.settings.datetime; 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.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; 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 androidx.preference.Preference;
import com.android.settings.flags.Flags;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; 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.Robolectric;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class TimeFeedbackPreferenceControllerTest { 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; private Context mContext;
@Before @Before
@@ -52,21 +85,47 @@ public class TimeFeedbackPreferenceControllerTest {
public void emptyIntentUri_controllerNotAvailable() { public void emptyIntentUri_controllerNotAvailable() {
String emptyIntentUri = ""; String emptyIntentUri = "";
TimeFeedbackPreferenceController controller = TimeFeedbackPreferenceController controller =
new TimeFeedbackPreferenceController(mContext, "test_key", emptyIntentUri); new TimeFeedbackPreferenceController(mContext, mContext.getPackageManager(),
"test_key", emptyIntentUri);
assertThat(controller.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); 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 @Test
public void clickPreference() { public void clickPreference() {
Preference preference = new Preference(mContext); 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 = 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. // Click a preference that's not controlled by this controller.
preference.setKey("fake_key"); preference.setKey("fake_key");
@@ -87,4 +146,16 @@ public class TimeFeedbackPreferenceControllerTest {
"com.android.settings.test.LAUNCH_USER_FEEDBACK"); "com.android.settings.test.LAUNCH_USER_FEEDBACK");
assertThat(actualIntent.getPackage()).isEqualTo("com.android.settings.test.target"); 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;
}
} }