diff --git a/src/com/android/settings/accessibility/FeedbackManager.java b/src/com/android/settings/accessibility/FeedbackManager.java new file mode 100644 index 00000000000..d765797e1db --- /dev/null +++ b/src/com/android/settings/accessibility/FeedbackManager.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2025 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.accessibility; + +import android.app.Activity; +import android.content.Intent; +import android.text.TextUtils; + +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.android.server.accessibility.Flags; +import com.android.settingslib.DeviceInfoUtils; + +import java.lang.ref.WeakReference; + +/** + * Manages the feedback flow. This class is responsible for checking feedback availability and + * sending feedback. Uses a WeakReference to the Activity to prevent memory leaks. + */ +public class FeedbackManager { + + // TODO(b/393980229): Add a feature provider for Pixel overlay to expose the feedback bucket ID + static final String ACCESSIBILITY_FEEDBACK_REQUEST_BUCKET_ID = + "com.google.android.settings.intelligence.ACCESSIBILITY_FEEDBACK_REQUEST"; + static final String CATEGORY_TAG = "category_tag"; + private static final int FEEDBACK_INTENT_RESULT_CODE = 0; + + private final WeakReference mActivityWeakReference; + @Nullable private final String mFeedbackReporterPackage; + @Nullable private final String mCategoryTag; + + /** + * Constructs a new FeedbackManager. + * + * @param activity The activity context. A WeakReference is used to prevent memory leaks. + */ + public FeedbackManager(@Nullable Activity activity) { + this(activity, DeviceInfoUtils.getFeedbackReporterPackage(activity)); + } + + @VisibleForTesting + public FeedbackManager(@Nullable Activity activity, @Nullable String feedbackReporterPackage) { + this.mActivityWeakReference = new WeakReference<>(activity); + this.mFeedbackReporterPackage = feedbackReporterPackage; + this.mCategoryTag = ACCESSIBILITY_FEEDBACK_REQUEST_BUCKET_ID; + } + + /** + * Checks if feedback is available on the device. + * + * @return {@code true} if feedback is available, {@code false} otherwise. + */ + public boolean isAvailable() { + if (!Flags.enableLowVisionGenericFeedback()) { + return false; + } + + return !TextUtils.isEmpty(mFeedbackReporterPackage) && mActivityWeakReference.get() != null; + } + + /** + * Sends feedback using the available feedback reporter. This will start the feedback + * activity. It is the responsibility of the calling activity to handle the result + * code {@link #FEEDBACK_INTENT_RESULT_CODE} if necessary. + * + * @return {@code true} if the feedback intent was successfully started, {@code false} + * otherwise. + */ + public boolean sendFeedback() { + Activity activity = mActivityWeakReference.get(); + if (!isAvailable() || activity == null) { + return false; + } + + final Intent intent = new Intent(Intent.ACTION_BUG_REPORT); + intent.setPackage(mFeedbackReporterPackage); + intent.putExtra(CATEGORY_TAG, mCategoryTag); + activity.startActivityForResult(intent, FEEDBACK_INTENT_RESULT_CODE); + return true; + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/FeedbackManagerTest.java b/tests/robotests/src/com/android/settings/accessibility/FeedbackManagerTest.java new file mode 100644 index 00000000000..aca144e2465 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FeedbackManagerTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2025 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.accessibility; + +import static com.android.settings.accessibility.FeedbackManager.ACCESSIBILITY_FEEDBACK_REQUEST_BUCKET_ID; +import static com.android.settings.accessibility.FeedbackManager.CATEGORY_TAG; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.server.accessibility.Flags; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.Shadows; + +/** Tests for {@link FeedbackManager}. */ +@RunWith(RobolectricTestRunner.class) +public class FeedbackManagerTest { + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final String FEEDBACK_PACKAGE = "test.feedback.package"; + + private Activity mActivity; + + @Before + public void setUp() { + mActivity = Robolectric.buildActivity(Activity.class).create().get(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK) + public void isAvailable_enableLowVisionGenericFeedbackWithPackageAndActivity_returnsTrue() { + FeedbackManager feedbackManager = new FeedbackManager(mActivity, FEEDBACK_PACKAGE); + + assertThat(feedbackManager.isAvailable()).isTrue(); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK) + public void isAvailable_disableLowVisionGenericFeedback_returnsFalse() { + FeedbackManager feedbackManager = new FeedbackManager(mActivity, FEEDBACK_PACKAGE); + + assertThat(feedbackManager.isAvailable()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK) + public void isAvailable_withNullPackage_returnsFalse() { + FeedbackManager feedbackManager = new FeedbackManager(mActivity, null); + + assertThat(feedbackManager.isAvailable()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK) + public void isAvailable_withNullActivity_returnsFalse() { + FeedbackManager feedbackManager = new FeedbackManager(null, FEEDBACK_PACKAGE); + + assertThat(feedbackManager.isAvailable()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK) + public void sendFeedback_enableLowVisionGenericFeedbackWithPackageAndActivity_success() { + FeedbackManager feedbackManager = new FeedbackManager(mActivity, FEEDBACK_PACKAGE); + + assertThat(feedbackManager.sendFeedback()).isTrue(); + + Intent startedIntent = Shadows.shadowOf(mActivity).getNextStartedActivity(); + assertThat(startedIntent).isNotNull(); + assertThat(startedIntent.getAction()).isEqualTo(Intent.ACTION_BUG_REPORT); + assertThat(startedIntent.getPackage()).isEqualTo(FEEDBACK_PACKAGE); + Bundle extras = startedIntent.getExtras(); + assertThat(extras).isNotNull(); + assertThat(extras.getString(CATEGORY_TAG)).isEqualTo( + ACCESSIBILITY_FEEDBACK_REQUEST_BUCKET_ID); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK) + public void sendFeedback_disableLowVisionGenericFeedback_returnsFalse() { + FeedbackManager feedbackManager = new FeedbackManager(mActivity, FEEDBACK_PACKAGE); + + assertThat(feedbackManager.sendFeedback()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK) + public void sendFeedback_withNullPackage_returnsFalse() { + FeedbackManager feedbackManager = new FeedbackManager(mActivity, null); + + assertThat(feedbackManager.sendFeedback()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK) + public void sendFeedback_withNullActivity_returnsFalse() { + FeedbackManager feedbackManager = new FeedbackManager(null, FEEDBACK_PACKAGE); + + assertThat(feedbackManager.sendFeedback()).isFalse(); + } +}