diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 1b4304b6bef..cc7f909680e 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3044,6 +3044,9 @@ + + diff --git a/res/values/config.xml b/res/values/config.xml index 917f14d4d6a..6b0a10e29fc 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -160,4 +160,6 @@ android.settings.EDIT_EMERGENCY_INFO + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 876be51f6b5..a95eb3d4748 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10249,4 +10249,9 @@ Storage is low. %1$s used - %2$s free + + + Send feedback + + Would you like to give us feedback on this suggestion? \ No newline at end of file diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardFeedbackDialog.java b/src/com/android/settings/homepage/contextualcards/ContextualCardFeedbackDialog.java new file mode 100644 index 00000000000..0d5b2758613 --- /dev/null +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardFeedbackDialog.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 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.homepage.contextualcards; + +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; +import com.android.settings.R; + +public class ContextualCardFeedbackDialog extends AlertActivity implements + DialogInterface.OnClickListener { + + public static final String EXTRA_CARD_NAME = "card_name"; + public static final String EXTRA_FEEDBACK_EMAIL = "feedback_email"; + + private static final String TAG = "CardFeedbackDialog"; + private static final String SUBJECT = "Settings Contextual Card Feedback - "; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final AlertController.AlertParams alertParams = mAlertParams; + alertParams.mMessage = getText(R.string.contextual_card_feedback_confirm_message); + alertParams.mPositiveButtonText = getText(R.string.contextual_card_feedback_send); + alertParams.mPositiveButtonListener = this; + alertParams.mNegativeButtonText = getText(R.string.skip_label); + + setupAlert(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final String cardName = getIntent().getStringExtra(EXTRA_CARD_NAME); + final String email = getIntent().getStringExtra(EXTRA_FEEDBACK_EMAIL); + final Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + email)); + intent.putExtra(Intent.EXTRA_SUBJECT, SUBJECT + cardName); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + try { + startActivity(intent); + } catch (Exception e) { + Log.e(TAG, "Send feedback failed.", e); + } + finish(); + } +} diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java index f1fbc9cb83a..3368580e002 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java @@ -17,11 +17,16 @@ package com.android.settings.homepage.contextualcards.slices; import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; -import com.android.settings.homepage.contextualcards.CardContentProvider; +import androidx.annotation.VisibleForTesting; + +import com.android.settings.R; import com.android.settings.homepage.contextualcards.CardDatabaseHelper; import com.android.settings.homepage.contextualcards.ContextualCard; import com.android.settings.homepage.contextualcards.ContextualCardController; +import com.android.settings.homepage.contextualcards.ContextualCardFeedbackDialog; import com.android.settings.homepage.contextualcards.ContextualCardUpdateListener; import com.android.settingslib.utils.ThreadUtils; @@ -32,7 +37,8 @@ public class SliceContextualCardController implements ContextualCardController { private static final String TAG = "SliceCardController"; - private Context mContext; + private final Context mContext; + private ContextualCardUpdateListener mCardUpdateListener; public SliceContextualCardController(Context context) { @@ -51,7 +57,7 @@ public class SliceContextualCardController implements ContextualCardController { @Override public void onActionClick(ContextualCard card) { - //TODO(b/113783548): Implement feedback mechanism + } @Override @@ -60,10 +66,30 @@ public class SliceContextualCardController implements ContextualCardController { final CardDatabaseHelper dbHelper = CardDatabaseHelper.getInstance(mContext); dbHelper.markContextualCardAsDismissed(mContext, card.getName()); }); + showFeedbackDialog(card); } @Override public void setCardUpdateListener(ContextualCardUpdateListener listener) { mCardUpdateListener = listener; } + + @VisibleForTesting + void showFeedbackDialog(ContextualCard card) { + final String email = mContext.getString(R.string.config_contextual_card_feedback_email); + if (TextUtils.isEmpty(email)) { + return; + } + final Intent feedbackIntent = new Intent(mContext, ContextualCardFeedbackDialog.class); + feedbackIntent.putExtra(ContextualCardFeedbackDialog.EXTRA_CARD_NAME, + getSimpleCardName(card)); + feedbackIntent.putExtra(ContextualCardFeedbackDialog.EXTRA_FEEDBACK_EMAIL, email); + feedbackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(feedbackIntent); + } + + private String getSimpleCardName(ContextualCard card) { + final String[] split = card.getName().split("/"); + return split[split.length - 1]; + } } diff --git a/tests/robotests/res/values-mcc999/config.xml b/tests/robotests/res/values-mcc999/config.xml index 73d22647091..9bba3c331d9 100644 --- a/tests/robotests/res/values-mcc999/config.xml +++ b/tests/robotests/res/values-mcc999/config.xml @@ -77,4 +77,7 @@ com.android.settings.slice_whitelist_package + + + test@test.test diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java index 28015a770c8..64a7c9cfcd6 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java @@ -18,9 +18,6 @@ package com.android.settings.homepage.contextualcards; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.Mockito.doNothing; - import android.content.Context; import android.net.Uri; import android.util.ArrayMap; diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardControllerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardControllerTest.java index 362e1f541ac..29e309d9620 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardControllerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardControllerTest.java @@ -18,6 +18,10 @@ package com.android.settings.homepage.contextualcards.slices; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; + import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -27,14 +31,21 @@ import android.net.Uri; import com.android.settings.homepage.contextualcards.CardContentProvider; import com.android.settings.homepage.contextualcards.CardDatabaseHelper; import com.android.settings.homepage.contextualcards.ContextualCard; +import com.android.settings.homepage.contextualcards.ContextualCardFeedbackDialog; +import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowActivity; import org.robolectric.shadows.ShadowContentResolver; +import org.robolectric.shadows.androidx.fragment.FragmentController; @RunWith(SettingsRobolectricTestRunner.class) public class SliceContextualCardControllerTest { @@ -49,25 +60,22 @@ public class SliceContextualCardControllerTest { @Before public void setUp() { + MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mProvider = Robolectric.setupContentProvider(CardContentProvider.class); ShadowContentResolver.registerProviderInternal(CardContentProvider.CARD_AUTHORITY, mProvider); mResolver = mContext.getContentResolver(); - mController = new SliceContextualCardController(mContext); + mController = spy(new SliceContextualCardController(mContext)); } @Test public void onDismissed_cardShouldBeMarkedAsDismissed() { final Uri providerUri = CardContentProvider.URI; - final ContextualCard card = new ContextualCard.Builder() - .setName(TEST_CARD_NAME) - .setCardType(ContextualCard.CardType.SLICE) - .setSliceUri(Uri.parse(TEST_SLICE_URI)) - .build(); mResolver.insert(providerUri, generateOneRow()); + doNothing().when(mController).showFeedbackDialog(any(ContextualCard.class)); - mController.onDismissed(card); + mController.onDismissed(getTestSliceCard()); final String[] columns = {CardDatabaseHelper.CardColumns.CARD_DISMISSED}; final String selection = CardDatabaseHelper.CardColumns.NAME + "=?"; @@ -80,6 +88,32 @@ public class SliceContextualCardControllerTest { assertThat(qryDismissed).isEqualTo(1); } + @Test + public void onDismissed_noFeedbackEmail_shouldNotShowFeedbackDialog() { + mResolver.insert(CardContentProvider.URI, generateOneRow()); + final ContextualCardsFragment fragment = + FragmentController.of(new ContextualCardsFragment()).create().get(); + final ShadowActivity shadowActivity = Shadows.shadowOf(fragment.getActivity()); + + mController.onDismissed(getTestSliceCard()); + + assertThat(shadowActivity.getNextStartedActivity()).isNull(); + } + + @Test + @Config(qualifiers = "mcc999") + public void onDismissed_hasFeedbackEmail_shouldShowFeedbackDialog() { + mResolver.insert(CardContentProvider.URI, generateOneRow()); + final ContextualCardsFragment fragment = + FragmentController.of(new ContextualCardsFragment()).create().get(); + final ShadowActivity shadowActivity = Shadows.shadowOf(fragment.getActivity()); + + mController.onDismissed(getTestSliceCard()); + + assertThat(shadowActivity.getNextStartedActivity().getComponent().getClassName()) + .isEqualTo(ContextualCardFeedbackDialog.class.getName()); + } + private ContentValues generateOneRow() { final ContentValues values = new ContentValues(); values.put(CardDatabaseHelper.CardColumns.NAME, TEST_CARD_NAME); @@ -93,4 +127,12 @@ public class SliceContextualCardControllerTest { return values; } + + private ContextualCard getTestSliceCard() { + return new ContextualCard.Builder() + .setName(TEST_CARD_NAME) + .setCardType(ContextualCard.CardType.SLICE) + .setSliceUri(Uri.parse(TEST_SLICE_URI)) + .build(); + } }