1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.textclassifier; 18 19 import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_OTHERS; 20 import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_SELF; 21 import static com.google.common.truth.Truth.assertThat; 22 23 import android.app.PendingIntent; 24 import android.app.Person; 25 import android.app.RemoteAction; 26 import android.content.ComponentName; 27 import android.content.Intent; 28 import android.graphics.drawable.Icon; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.view.textclassifier.ConversationAction; 32 import android.view.textclassifier.ConversationActions; 33 import androidx.test.core.app.ApplicationProvider; 34 import androidx.test.ext.junit.runners.AndroidJUnit4; 35 import androidx.test.filters.SmallTest; 36 import com.android.textclassifier.common.intent.LabeledIntent; 37 import com.android.textclassifier.common.intent.TemplateIntentFactory; 38 import com.google.android.textclassifier.ActionsSuggestionsModel; 39 import com.google.android.textclassifier.RemoteActionTemplate; 40 import com.google.common.collect.ImmutableList; 41 import java.time.Instant; 42 import java.time.ZoneId; 43 import java.time.ZonedDateTime; 44 import java.util.Arrays; 45 import java.util.Collections; 46 import java.util.List; 47 import java.util.Locale; 48 import java.util.function.Function; 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 52 @SmallTest 53 @RunWith(AndroidJUnit4.class) 54 public class ActionsSuggestionsHelperTest { 55 private static final String LOCALE_TAG = Locale.US.toLanguageTag(); 56 private static final Function<CharSequence, List<String>> LANGUAGE_DETECTOR = 57 charSequence -> Collections.singletonList(LOCALE_TAG); 58 59 @Test testToNativeMessages_emptyInput()60 public void testToNativeMessages_emptyInput() { 61 ActionsSuggestionsModel.ConversationMessage[] conversationMessages = 62 ActionsSuggestionsHelper.toNativeMessages(ImmutableList.of(), LANGUAGE_DETECTOR); 63 64 assertThat(conversationMessages).isEmpty(); 65 } 66 67 @Test testToNativeMessages_noTextMessages()68 public void testToNativeMessages_noTextMessages() { 69 ConversationActions.Message messageWithoutText = 70 new ConversationActions.Message.Builder(PERSON_USER_OTHERS).build(); 71 72 ActionsSuggestionsModel.ConversationMessage[] conversationMessages = 73 ActionsSuggestionsHelper.toNativeMessages( 74 ImmutableList.of(messageWithoutText), LANGUAGE_DETECTOR); 75 76 assertThat(conversationMessages).isEmpty(); 77 } 78 79 @Test testToNativeMessages_userIdEncoding()80 public void testToNativeMessages_userIdEncoding() { 81 Person.Builder userA = new Person.Builder().setName("userA").setKey("A"); 82 Person.Builder userB = new Person.Builder().setName("userB").setKey("B"); 83 84 ConversationActions.Message firstMessage = 85 new ConversationActions.Message.Builder(userB.build()).setText("first").build(); 86 ConversationActions.Message secondMessage = 87 new ConversationActions.Message.Builder(userA.build()).setText("second").build(); 88 ConversationActions.Message thirdMessage = 89 new ConversationActions.Message.Builder(PERSON_USER_SELF).setText("third").build(); 90 ConversationActions.Message fourthMessage = 91 new ConversationActions.Message.Builder(userA.build()).setText("fourth").build(); 92 93 ActionsSuggestionsModel.ConversationMessage[] conversationMessages = 94 ActionsSuggestionsHelper.toNativeMessages( 95 Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage), 96 LANGUAGE_DETECTOR); 97 98 assertThat(conversationMessages).hasLength(4); 99 assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2, 0); 100 assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); 101 assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0, 0); 102 assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1, 0); 103 } 104 105 @Test testToNativeMessages_referenceTime()106 public void testToNativeMessages_referenceTime() { 107 ConversationActions.Message firstMessage = 108 new ConversationActions.Message.Builder(PERSON_USER_OTHERS) 109 .setText("first") 110 .setReferenceTime(createZonedDateTimeFromMsUtc(1000)) 111 .build(); 112 ConversationActions.Message secondMessage = 113 new ConversationActions.Message.Builder(PERSON_USER_OTHERS).setText("second").build(); 114 ConversationActions.Message thirdMessage = 115 new ConversationActions.Message.Builder(PERSON_USER_OTHERS) 116 .setText("third") 117 .setReferenceTime(createZonedDateTimeFromMsUtc(2000)) 118 .build(); 119 120 ActionsSuggestionsModel.ConversationMessage[] conversationMessages = 121 ActionsSuggestionsHelper.toNativeMessages( 122 Arrays.asList(firstMessage, secondMessage, thirdMessage), LANGUAGE_DETECTOR); 123 124 assertThat(conversationMessages).hasLength(3); 125 assertNativeMessage(conversationMessages[0], firstMessage.getText(), 1, 1000); 126 assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); 127 assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 1, 2000); 128 } 129 130 @Test testDeduplicateActions()131 public void testDeduplicateActions() { 132 Bundle phoneExtras = new Bundle(); 133 Intent phoneIntent = new Intent(); 134 phoneIntent.setComponent(new ComponentName("phone", "intent")); 135 ExtrasUtils.putActionIntent(phoneExtras, phoneIntent); 136 137 Bundle anotherPhoneExtras = new Bundle(); 138 Intent anotherPhoneIntent = new Intent(); 139 anotherPhoneIntent.setComponent(new ComponentName("phone", "another.intent")); 140 ExtrasUtils.putActionIntent(anotherPhoneExtras, anotherPhoneIntent); 141 142 Bundle urlExtras = new Bundle(); 143 Intent urlIntent = new Intent(); 144 urlIntent.setComponent(new ComponentName("url", "intent")); 145 ExtrasUtils.putActionIntent(urlExtras, urlIntent); 146 147 PendingIntent pendingIntent = 148 PendingIntent.getActivity(ApplicationProvider.getApplicationContext(), 0, phoneIntent, 0); 149 Icon icon = Icon.createWithData(new byte[0], 0, 0); 150 ConversationAction action = 151 new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) 152 .setAction(new RemoteAction(icon, "label", "1", pendingIntent)) 153 .setExtras(phoneExtras) 154 .build(); 155 ConversationAction actionWithSameLabel = 156 new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) 157 .setAction(new RemoteAction(icon, "label", "2", pendingIntent)) 158 .setExtras(phoneExtras) 159 .build(); 160 ConversationAction actionWithSamePackageButDifferentClass = 161 new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) 162 .setAction(new RemoteAction(icon, "label", "3", pendingIntent)) 163 .setExtras(anotherPhoneExtras) 164 .build(); 165 ConversationAction actionWithDifferentLabel = 166 new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) 167 .setAction(new RemoteAction(icon, "another_label", "4", pendingIntent)) 168 .setExtras(phoneExtras) 169 .build(); 170 ConversationAction actionWithDifferentPackage = 171 new ConversationAction.Builder(ConversationAction.TYPE_OPEN_URL) 172 .setAction(new RemoteAction(icon, "label", "5", pendingIntent)) 173 .setExtras(urlExtras) 174 .build(); 175 ConversationAction actionWithoutRemoteAction = 176 new ConversationAction.Builder(ConversationAction.TYPE_CREATE_REMINDER).build(); 177 178 List<ConversationAction> conversationActions = 179 ActionsSuggestionsHelper.removeActionsWithDuplicates( 180 Arrays.asList( 181 action, 182 actionWithSameLabel, 183 actionWithSamePackageButDifferentClass, 184 actionWithDifferentLabel, 185 actionWithDifferentPackage, 186 actionWithoutRemoteAction)); 187 188 assertThat(conversationActions).hasSize(3); 189 assertThat(conversationActions.get(0).getAction().getContentDescription().toString()) 190 .isEqualTo("4"); 191 assertThat(conversationActions.get(1).getAction().getContentDescription().toString()) 192 .isEqualTo("5"); 193 assertThat(conversationActions.get(2).getAction()).isNull(); 194 } 195 196 @Test testDeduplicateActions_nullComponent()197 public void testDeduplicateActions_nullComponent() { 198 Bundle phoneExtras = new Bundle(); 199 Intent phoneIntent = new Intent(Intent.ACTION_DIAL); 200 ExtrasUtils.putActionIntent(phoneExtras, phoneIntent); 201 PendingIntent pendingIntent = 202 PendingIntent.getActivity(ApplicationProvider.getApplicationContext(), 0, phoneIntent, 0); 203 Icon icon = Icon.createWithData(new byte[0], 0, 0); 204 ConversationAction action = 205 new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) 206 .setAction(new RemoteAction(icon, "label", "1", pendingIntent)) 207 .setExtras(phoneExtras) 208 .build(); 209 ConversationAction actionWithSameLabel = 210 new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) 211 .setAction(new RemoteAction(icon, "label", "2", pendingIntent)) 212 .setExtras(phoneExtras) 213 .build(); 214 215 List<ConversationAction> conversationActions = 216 ActionsSuggestionsHelper.removeActionsWithDuplicates( 217 Arrays.asList(action, actionWithSameLabel)); 218 219 assertThat(conversationActions).isEmpty(); 220 } 221 222 @Test createLabeledIntentResult_null()223 public void createLabeledIntentResult_null() { 224 ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = 225 new ActionsSuggestionsModel.ActionSuggestion( 226 "text", ConversationAction.TYPE_OPEN_URL, 1.0f, null, null, null); 227 228 LabeledIntent.Result labeledIntentResult = 229 ActionsSuggestionsHelper.createLabeledIntentResult( 230 ApplicationProvider.getApplicationContext(), 231 new TemplateIntentFactory(), 232 nativeSuggestion); 233 234 assertThat(labeledIntentResult).isNull(); 235 } 236 237 @Test createLabeledIntentResult_emptyList()238 public void createLabeledIntentResult_emptyList() { 239 ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = 240 new ActionsSuggestionsModel.ActionSuggestion( 241 "text", 242 ConversationAction.TYPE_OPEN_URL, 243 1.0f, 244 null, 245 null, 246 new RemoteActionTemplate[0]); 247 248 LabeledIntent.Result labeledIntentResult = 249 ActionsSuggestionsHelper.createLabeledIntentResult( 250 ApplicationProvider.getApplicationContext(), 251 new TemplateIntentFactory(), 252 nativeSuggestion); 253 254 assertThat(labeledIntentResult).isNull(); 255 } 256 257 @Test createLabeledIntentResult()258 public void createLabeledIntentResult() { 259 ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = 260 new ActionsSuggestionsModel.ActionSuggestion( 261 "text", 262 ConversationAction.TYPE_OPEN_URL, 263 1.0f, 264 null, 265 null, 266 new RemoteActionTemplate[] { 267 new RemoteActionTemplate( 268 "title", 269 null, 270 "description", 271 null, 272 Intent.ACTION_VIEW, 273 Uri.parse("http://www.android.com").toString(), 274 null, 275 0, 276 null, 277 null, 278 null, 279 0) 280 }); 281 282 LabeledIntent.Result labeledIntentResult = 283 ActionsSuggestionsHelper.createLabeledIntentResult( 284 ApplicationProvider.getApplicationContext(), 285 new TemplateIntentFactory(), 286 nativeSuggestion); 287 288 assertThat(labeledIntentResult.remoteAction.getTitle().toString()).isEqualTo("title"); 289 assertThat(labeledIntentResult.resolvedIntent.getAction()).isEqualTo(Intent.ACTION_VIEW); 290 } 291 createZonedDateTimeFromMsUtc(long msUtc)292 private static ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) { 293 return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneId.of("UTC")); 294 } 295 assertNativeMessage( ActionsSuggestionsModel.ConversationMessage nativeMessage, CharSequence text, int userId, long referenceTimeInMsUtc)296 private static void assertNativeMessage( 297 ActionsSuggestionsModel.ConversationMessage nativeMessage, 298 CharSequence text, 299 int userId, 300 long referenceTimeInMsUtc) { 301 assertThat(nativeMessage.getText()).isEqualTo(text.toString()); 302 assertThat(nativeMessage.getUserId()).isEqualTo(userId); 303 assertThat(nativeMessage.getDetectedTextLanguageTags()).isEqualTo(LOCALE_TAG); 304 assertThat(nativeMessage.getReferenceTimeMsUtc()).isEqualTo(referenceTimeInMsUtc); 305 } 306 } 307