/* * 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.textclassifier; import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_OTHERS; import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_SELF; import static com.google.common.truth.Truth.assertThat; import android.app.PendingIntent; import android.app.Person; import android.app.RemoteAction; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.view.textclassifier.ConversationAction; import android.view.textclassifier.ConversationActions; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.textclassifier.common.intent.LabeledIntent; import com.android.textclassifier.common.intent.LabeledIntent.TitleChooser; import com.android.textclassifier.common.intent.TemplateIntentFactory; import com.google.android.textclassifier.ActionsSuggestionsModel; import com.google.android.textclassifier.RemoteActionTemplate; import com.google.common.collect.ImmutableList; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.function.Function; import org.junit.Test; import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) public class ActionsSuggestionsHelperTest { private static final String LOCALE_TAG = Locale.US.toLanguageTag(); private static final Function> LANGUAGE_DETECTOR = charSequence -> Collections.singletonList(LOCALE_TAG); @Test public void testToNativeMessages_emptyInput() { ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages(ImmutableList.of(), LANGUAGE_DETECTOR); assertThat(conversationMessages).isEmpty(); } @Test public void testToNativeMessages_noTextMessages() { ConversationActions.Message messageWithoutText = new ConversationActions.Message.Builder(PERSON_USER_OTHERS).build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( ImmutableList.of(messageWithoutText), LANGUAGE_DETECTOR); assertThat(conversationMessages).isEmpty(); } @Test public void testToNativeMessages_userIdEncoding() { Person.Builder userA = new Person.Builder().setName("userA").setKey("A"); Person.Builder userB = new Person.Builder().setName("userB").setKey("B"); ConversationActions.Message firstMessage = new ConversationActions.Message.Builder(userB.build()).setText("first").build(); ConversationActions.Message secondMessage = new ConversationActions.Message.Builder(userA.build()).setText("second").build(); ConversationActions.Message thirdMessage = new ConversationActions.Message.Builder(PERSON_USER_SELF).setText("third").build(); ConversationActions.Message fourthMessage = new ConversationActions.Message.Builder(userA.build()).setText("fourth").build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage), LANGUAGE_DETECTOR); assertThat(conversationMessages).hasLength(4); assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2, 0); assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0, 0); assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1, 0); } @Test public void testToNativeMessages_referenceTime() { ConversationActions.Message firstMessage = new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("first") .setReferenceTime(createZonedDateTimeFromMsUtc(1000)) .build(); ConversationActions.Message secondMessage = new ConversationActions.Message.Builder(PERSON_USER_OTHERS).setText("second").build(); ConversationActions.Message thirdMessage = new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("third") .setReferenceTime(createZonedDateTimeFromMsUtc(2000)) .build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( Arrays.asList(firstMessage, secondMessage, thirdMessage), LANGUAGE_DETECTOR); assertThat(conversationMessages).hasLength(3); assertNativeMessage(conversationMessages[0], firstMessage.getText(), 1, 1000); assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 1, 2000); } @Test public void testDeduplicateActions() { Bundle phoneExtras = new Bundle(); Intent phoneIntent = new Intent(); phoneIntent.setComponent(new ComponentName("phone", "intent")); ExtrasUtils.putActionIntent(phoneExtras, phoneIntent); Bundle anotherPhoneExtras = new Bundle(); Intent anotherPhoneIntent = new Intent(); anotherPhoneIntent.setComponent(new ComponentName("phone", "another.intent")); ExtrasUtils.putActionIntent(anotherPhoneExtras, anotherPhoneIntent); Bundle urlExtras = new Bundle(); Intent urlIntent = new Intent(); urlIntent.setComponent(new ComponentName("url", "intent")); ExtrasUtils.putActionIntent(urlExtras, urlIntent); PendingIntent pendingIntent = PendingIntent.getActivity(ApplicationProvider.getApplicationContext(), 0, phoneIntent, 0); Icon icon = Icon.createWithData(new byte[0], 0, 0); ConversationAction action = new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) .setAction(new RemoteAction(icon, "label", "1", pendingIntent)) .setExtras(phoneExtras) .build(); ConversationAction actionWithSameLabel = new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) .setAction(new RemoteAction(icon, "label", "2", pendingIntent)) .setExtras(phoneExtras) .build(); ConversationAction actionWithSamePackageButDifferentClass = new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) .setAction(new RemoteAction(icon, "label", "3", pendingIntent)) .setExtras(anotherPhoneExtras) .build(); ConversationAction actionWithDifferentLabel = new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) .setAction(new RemoteAction(icon, "another_label", "4", pendingIntent)) .setExtras(phoneExtras) .build(); ConversationAction actionWithDifferentPackage = new ConversationAction.Builder(ConversationAction.TYPE_OPEN_URL) .setAction(new RemoteAction(icon, "label", "5", pendingIntent)) .setExtras(urlExtras) .build(); ConversationAction actionWithoutRemoteAction = new ConversationAction.Builder(ConversationAction.TYPE_CREATE_REMINDER).build(); List conversationActions = ActionsSuggestionsHelper.removeActionsWithDuplicates( Arrays.asList( action, actionWithSameLabel, actionWithSamePackageButDifferentClass, actionWithDifferentLabel, actionWithDifferentPackage, actionWithoutRemoteAction)); assertThat(conversationActions).hasSize(3); assertThat(conversationActions.get(0).getAction().getContentDescription().toString()) .isEqualTo("4"); assertThat(conversationActions.get(1).getAction().getContentDescription().toString()) .isEqualTo("5"); assertThat(conversationActions.get(2).getAction()).isNull(); } @Test public void testDeduplicateActions_nullComponent() { Bundle phoneExtras = new Bundle(); Intent phoneIntent = new Intent(Intent.ACTION_DIAL); ExtrasUtils.putActionIntent(phoneExtras, phoneIntent); PendingIntent pendingIntent = PendingIntent.getActivity(ApplicationProvider.getApplicationContext(), 0, phoneIntent, 0); Icon icon = Icon.createWithData(new byte[0], 0, 0); ConversationAction action = new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) .setAction(new RemoteAction(icon, "label", "1", pendingIntent)) .setExtras(phoneExtras) .build(); ConversationAction actionWithSameLabel = new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) .setAction(new RemoteAction(icon, "label", "2", pendingIntent)) .setExtras(phoneExtras) .build(); List conversationActions = ActionsSuggestionsHelper.removeActionsWithDuplicates( Arrays.asList(action, actionWithSameLabel)); assertThat(conversationActions).isEmpty(); } @Test public void createLabeledIntentResult_null() { ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = new ActionsSuggestionsModel.ActionSuggestion( "text", ConversationAction.TYPE_OPEN_URL, 1.0f, null, null, null, null); LabeledIntent.Result labeledIntentResult = ActionsSuggestionsHelper.createLabeledIntentResult( ApplicationProvider.getApplicationContext(), new TemplateIntentFactory(), nativeSuggestion); assertThat(labeledIntentResult).isNull(); } @Test public void createLabeledIntentResult_emptyList() { ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = new ActionsSuggestionsModel.ActionSuggestion( "text", ConversationAction.TYPE_OPEN_URL, 1.0f, null, null, new RemoteActionTemplate[0], null); LabeledIntent.Result labeledIntentResult = ActionsSuggestionsHelper.createLabeledIntentResult( ApplicationProvider.getApplicationContext(), new TemplateIntentFactory(), nativeSuggestion); assertThat(labeledIntentResult).isNull(); } @Test public void createLabeledIntentResult() { ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = new ActionsSuggestionsModel.ActionSuggestion( "text", ConversationAction.TYPE_OPEN_URL, 1.0f, null, null, new RemoteActionTemplate[] { new RemoteActionTemplate( "title", null, "description", null, Intent.ACTION_VIEW, Uri.parse("http://www.android.com").toString(), null, 0, null, null, null, 0) }, null); LabeledIntent.Result labeledIntentResult = ActionsSuggestionsHelper.createLabeledIntentResult( ApplicationProvider.getApplicationContext(), new TemplateIntentFactory(), nativeSuggestion); assertThat(labeledIntentResult.remoteAction.getTitle().toString()).isEqualTo("title"); assertThat(labeledIntentResult.resolvedIntent.getAction()).isEqualTo(Intent.ACTION_VIEW); } @Test public void createTitleChooser_notOpenUrl() { assertThat(ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_CALL_PHONE)) .isNull(); } @Test public void createTitleChooser_openUrl_resolveInfoIsNull() { TitleChooser titleChooser = ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_OPEN_URL); LabeledIntent labeledIntent = createWebLabeledIntent(); assertThat(titleChooser.chooseTitle(labeledIntent, /* resolveInfo= */ null).toString()) .isEqualTo("titleWithEntity"); } @Test public void createTitleChooser_openUrl_packageIsNotAndroidAndHandleAllWebDataUriTrue() { TitleChooser titleChooser = ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_OPEN_URL); LabeledIntent labeledIntent = createWebLabeledIntent(); assertThat( titleChooser .chooseTitle( labeledIntent, createResolveInfo("com.android.chrome", /* handleAllWebDataURI= */ true)) .toString()) .isEqualTo("titleWithEntity"); } @Test public void createTitleChooser_openUrl_packageIsNotAndroidAndHandleAllWebDataUriFalse() { TitleChooser titleChooser = ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_OPEN_URL); LabeledIntent labeledIntent = createWebLabeledIntent(); assertThat( titleChooser .chooseTitle( labeledIntent, createResolveInfo("com.youtube", /* handleAllWebDataURI= */ false)) .toString()) .isEqualTo("titleWithoutEntity"); } @Test public void createTitleChooser_openUrl_packageIsAndroidAndHandleAllWebDataUriFalse() { TitleChooser titleChooser = ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_OPEN_URL); LabeledIntent labeledIntent = createWebLabeledIntent(); assertThat( titleChooser .chooseTitle( labeledIntent, createResolveInfo("android", /* handleAllWebDataURI= */ false)) .toString()) .isEqualTo("titleWithEntity"); } @Test public void createTitleChooser_openUrl_packageIsAndroidAndHandleAllWebDataUriTrue() { TitleChooser titleChooser = ActionsSuggestionsHelper.createTitleChooser(ConversationAction.TYPE_OPEN_URL); LabeledIntent labeledIntent = createWebLabeledIntent(); assertThat( titleChooser .chooseTitle( labeledIntent, createResolveInfo("android", /* handleAllWebDataURI= */ true)) .toString()) .isEqualTo("titleWithEntity"); } private LabeledIntent createWebLabeledIntent() { Intent webIntent = new Intent(Intent.ACTION_VIEW); webIntent.setData(Uri.parse("http://www.android.com")); return new LabeledIntent( "titleWithoutEntity", "titleWithEntity", "description", "descriptionWithAppName", webIntent, /* requestCode= */ 0); } private static ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) { return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneId.of("UTC")); } private static void assertNativeMessage( ActionsSuggestionsModel.ConversationMessage nativeMessage, CharSequence text, int userId, long referenceTimeInMsUtc) { assertThat(nativeMessage.getText()).isEqualTo(text.toString()); assertThat(nativeMessage.getUserId()).isEqualTo(userId); assertThat(nativeMessage.getDetectedTextLanguageTags()).isEqualTo(LOCALE_TAG); assertThat(nativeMessage.getReferenceTimeMsUtc()).isEqualTo(referenceTimeInMsUtc); } private static ResolveInfo createResolveInfo(String packageName, boolean handleAllWebDataURI) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = new ActivityInfo(); resolveInfo.activityInfo.packageName = packageName; resolveInfo.handleAllWebDataURI = handleAllWebDataURI; return resolveInfo; } }