blob: 40dc1805220a8724135484311b37a440e1571d56 [file] [log] [blame]
/*
* Copyright 2021 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 androidx.core.google.shortcuts;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.Intent;
import androidx.appsearch.app.ShortcutAdapter;
import androidx.appsearch.builtintypes.Timer;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.google.shortcuts.builders.CapabilityBuilder;
import androidx.core.google.shortcuts.builders.ParameterBuilder;
import androidx.core.google.shortcuts.builders.ShortcutBuilder;
import androidx.core.google.shortcuts.utils.ShortcutUtils;
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import com.google.common.collect.ImmutableList;
import com.google.firebase.appindexing.Action;
import com.google.firebase.appindexing.FirebaseAppIndex;
import com.google.firebase.appindexing.FirebaseUserActions;
import com.google.firebase.appindexing.Indexable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 21) // This module should only be called for version 21+.
public class ShortcutInfoChangeListenerImplTest {
private FirebaseAppIndex mFirebaseAppIndex;
private FirebaseUserActions mFirebaseUserActions;
private Context mContext;
private ShortcutInfoChangeListenerImpl mShortcutInfoChangeListener;
@Before
public void setUp() {
mFirebaseAppIndex = mock(FirebaseAppIndex.class);
mFirebaseUserActions = mock(FirebaseUserActions.class);
mContext = ApplicationProvider.getApplicationContext();
mShortcutInfoChangeListener = new ShortcutInfoChangeListenerImpl(
mContext, mFirebaseAppIndex, mFirebaseUserActions, null);
}
@Test
@SmallTest
public void onShortcutUpdated_publicIntent_savesToAppIndex() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
Intent intent = Intent.parseUri("app://shortcut", 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
.setShortLabel("short label")
.setLongLabel("long label")
.setIntent(intent)
.build();
mShortcutInfoChangeListener.onShortcutUpdated(Collections.singletonList(shortcut));
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
Indexable expected = new ShortcutBuilder()
.setId("publicIntent")
.setShortcutLabel("short label")
.setShortcutDescription("long label")
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "publicIntent"))
.setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
.build();
assertThat(allValues).containsExactly(expected);
}
@Test
@SmallTest
public void onShortcutUpdated_withCapabilityBinding_savesToAppIndex() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
Intent intent = Intent.parseUri("app://shortcut", 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
.setShortLabel("short label")
.setLongLabel("long label")
.setIntent(intent)
.addCapabilityBinding("actions.intent.START_EXERCISE", "exercise.name",
ImmutableList.of("start running", "start jogging"))
.addCapabilityBinding("actions.intent.STOP_EXERCISE", "exercise.name",
ImmutableList.of("stop running", "stop jogging"))
.build();
mShortcutInfoChangeListener.onShortcutUpdated(Collections.singletonList(shortcut));
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
ShortcutBuilder expectedBuilder = new ShortcutBuilder()
.setId("publicIntent")
.setShortcutLabel("short label")
.setShortcutDescription("long label")
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "publicIntent"))
.setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null));
// The order of capability field matters during comparison. However since the order is not
// deterministic because the data is stored in maps and sets in ShortcutInfoCompat, we
// check for all possible orderings to make the test more reliable.
Indexable expected1 = expectedBuilder
.setCapability(
new CapabilityBuilder()
.setName("actions.intent.STOP_EXERCISE")
.setParameter(new ParameterBuilder()
.setName("exercise.name")
.setValue("stop running", "stop jogging")),
new CapabilityBuilder()
.setName("actions.intent.START_EXERCISE")
.setParameter(new ParameterBuilder()
.setName("exercise.name")
.setValue("start running", "start jogging")))
.build();
Indexable expected2 = expectedBuilder
.setCapability(
new CapabilityBuilder()
.setName("actions.intent.START_EXERCISE")
.setParameter(new ParameterBuilder()
.setName("exercise.name")
.setValue("start running", "start jogging")),
new CapabilityBuilder()
.setName("actions.intent.STOP_EXERCISE")
.setParameter(new ParameterBuilder()
.setName("exercise.name")
.setValue("stop running", "stop jogging")))
.build();
assertThat(allValues).hasSize(1);
assertThat(allValues).containsAnyOf(expected1, expected2);
}
@Test
@SmallTest
public void onShortcutUpdated_withCapabilityBindingNoParams_savesToAppIndex() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
Intent intent = Intent.parseUri("app://shortcut", 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
.setShortLabel("short label")
.setLongLabel("long label")
.setIntent(intent)
.addCapabilityBinding("actions.intent.TWEET")
.build();
mShortcutInfoChangeListener.onShortcutUpdated(Collections.singletonList(shortcut));
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
Indexable expected = new ShortcutBuilder()
.setId("publicIntent")
.setShortcutLabel("short label")
.setShortcutDescription("long label")
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "publicIntent"))
.setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
.setCapability(new CapabilityBuilder().setName("actions.intent.TWEET"))
.build();
assertThat(allValues).containsExactly(expected);
}
@Test
@SmallTest
public void onShortcutUpdated_privateIntent_savesToAppIndex() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
String privateIntentUri = "#Intent;component=androidx.core.google.shortcuts.test/androidx"
+ ".core.google.shortcuts.TrampolineActivity;end";
Intent intent = Intent.parseUri(privateIntentUri, 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "privateIntent")
.setShortLabel("short label")
.setIntent(intent)
.build();
mShortcutInfoChangeListener.onShortcutUpdated(Collections.singletonList(shortcut));
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
Indexable expected = new ShortcutBuilder()
.setName("short label")
.setId("privateIntent")
.setShortcutLabel("short label")
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "privateIntent"))
.setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
.build();
assertThat(allValues).containsExactly(expected);
}
@Test
@SmallTest
public void onShortcutUpdated_entityShortcut_savesToAppIndex() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
Timer timer = new Timer.Builder(ShortcutAdapter.DEFAULT_NAMESPACE, "timer_id")
.setCreationTimestampMillis(1000)
.build();
ShortcutInfoCompat timerShortcut =
ShortcutAdapter.createShortcutBuilderFromDocument(mContext, timer)
.build();
mShortcutInfoChangeListener.onShortcutUpdated(Collections.singletonList(timerShortcut));
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
Indexable expected = new Indexable.Builder("Timer")
.setMetadata(new Indexable.Metadata.Builder().setScore(0))
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "timer_id"))
.put("_namespace", ShortcutAdapter.DEFAULT_NAMESPACE)
.put("_ttlMillis", 0)
.put("_creationTimestampMillis", 1000)
.put("id", "timer_id")
.put("length", 0)
.put("name", ShortcutAdapter.DEFAULT_NAMESPACE)
.put("remainingTime", 0)
.put("timerStatus", "Unknown")
.put("vibrate", false)
.put("bootCount", 0)
.put("originalDurationMillis", 0)
.build();
assertThat(allValues).containsExactly(expected);
}
@Test
@SmallTest
public void onShortcutAdded_savesToAppIndex() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
Intent intent = Intent.parseUri("app://shortcut", 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "intent")
.setShortLabel("short label")
.setLongLabel("long label")
.setIntent(intent)
.build();
mShortcutInfoChangeListener.onShortcutAdded(Collections.singletonList(shortcut));
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
Indexable expected = new ShortcutBuilder()
.setName("short label")
.setId("intent")
.setShortcutLabel("short label")
.setDescription("long label")
.setShortcutDescription("long label")
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "intent"))
.setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
.build();
assertThat(allValues).containsExactly(expected);
}
@Test
@SmallTest
public void onShortcutRemoved_removeFromAppIndex() {
ArgumentCaptor<String> urlsCaptor = ArgumentCaptor.forClass(String.class);
mShortcutInfoChangeListener.onShortcutRemoved(Arrays.asList("id1", "id2"));
verify(mFirebaseAppIndex, only()).remove(urlsCaptor.capture());
List<String> urls = urlsCaptor.getAllValues();
assertThat(urls).containsExactly(
ShortcutUtils.getIndexableUrl(mContext, "id1"),
ShortcutUtils.getIndexableUrl(mContext, "id2"));
}
@Test
@SmallTest
public void onAllShortcutRemoved_removeFromAppIndex() {
mShortcutInfoChangeListener.onAllShortcutsRemoved();
verify(mFirebaseAppIndex, only()).removeAll();
}
@Test
@SmallTest
public void onShortcutUsageReported_savesToUserActions() {
ArgumentCaptor<Action> actionCaptor = ArgumentCaptor.forClass(Action.class);
mShortcutInfoChangeListener.onShortcutUsageReported(Arrays.asList("id1", "id2"));
verify(mFirebaseUserActions, times(2)).end(actionCaptor.capture());
Action expectedAction1 = new Action.Builder(Action.Builder.VIEW_ACTION)
.setObject("", ShortcutUtils.getIndexableUrl(mContext, "id1"))
.setMetadata(new Action.Metadata.Builder().setUpload(false))
.build();
Action expectedAction2 = new Action.Builder(Action.Builder.VIEW_ACTION)
.setObject("", ShortcutUtils.getIndexableUrl(mContext, "id2"))
.setMetadata(new Action.Metadata.Builder().setUpload(false))
.build();
// Action has no equals comparator, so instead we compare their string forms.
assertThat(convertActionsToString(actionCaptor.getAllValues())).containsExactly(
expectedAction1.toString(),
expectedAction2.toString());
}
@Test
@SmallTest
public void onShortcutUpdated_withUriIcon_savesToAppIndexWithUri() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
Intent intent = Intent.parseUri("app://shortcut", 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
.setShortLabel("short label")
.setIntent(intent)
.setIcon(IconCompat.createWithContentUri(
"file:///data/user/0/com.example.myapp/files/ic_myicon.jpg"))
.build();
mShortcutInfoChangeListener.onShortcutUpdated(Collections.singletonList(shortcut));
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
Indexable expected = new ShortcutBuilder()
.setId("publicIntent")
.setShortcutLabel("short label")
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "publicIntent"))
.setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
.setImage("file:///data/user/0/com.example.myapp/files/ic_myicon.jpg")
.build();
assertThat(allValues).containsExactly(expected);
}
@Test
@SmallTest
public void onShortcutUpdated_withAdaptiveUriIcon_savesToAppIndexWithUri() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
Intent intent = Intent.parseUri("app://shortcut", 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
.setShortLabel("short label")
.setIntent(intent)
.setIcon(IconCompat.createWithAdaptiveBitmapContentUri(
"file:///data/user/0/com.example.myapp/files/ic_myicon.jpg"))
.build();
mShortcutInfoChangeListener.onShortcutUpdated(Collections.singletonList(shortcut));
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
Indexable expected = new ShortcutBuilder()
.setId("publicIntent")
.setShortcutLabel("short label")
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "publicIntent"))
.setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
.setImage("file:///data/user/0/com.example.myapp/files/ic_myicon.jpg")
.build();
assertThat(allValues).containsExactly(expected);
}
@Test
@SmallTest
public void onShortcutAdded_withCapabilityBindingEntity_reportEntityUsage() throws Exception {
ArgumentCaptor<Action> actionCaptor = ArgumentCaptor.forClass(Action.class);
Intent intent = Intent.parseUri("app://shortcut", 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
.setShortLabel("short label")
.setIntent(intent)
.addCapabilityBinding(
"actions.intent.ORDER_MENU_ITEM",
"menuItem.name",
ImmutableList.of(
createEntityUri("item1"),
createEntityUri("item2"),
"item3"))
.addCapabilityBinding(
"actions.intent.ORDER_MENU_ITEM",
"menuItem.inMenuSection.inMenu.forRestaurant.name",
ImmutableList.of(
createEntityUri("restaurant1"),
createEntityUri("restaurant2"),
"restaurant3"))
.build();
mShortcutInfoChangeListener.onShortcutAdded(ImmutableList.of(shortcut));
verify(mFirebaseUserActions, times(4)).end(actionCaptor.capture());
Action expectedAction1 = new Action.Builder(Action.Builder.VIEW_ACTION)
.setObject("", ShortcutUtils.getIndexableUrl(mContext, "item1"))
.setMetadata(new Action.Metadata.Builder().setUpload(false))
.build();
Action expectedAction2 = new Action.Builder(Action.Builder.VIEW_ACTION)
.setObject("", ShortcutUtils.getIndexableUrl(mContext, "item2"))
.setMetadata(new Action.Metadata.Builder().setUpload(false))
.build();
Action expectedAction3 = new Action.Builder(Action.Builder.VIEW_ACTION)
.setObject("", ShortcutUtils.getIndexableUrl(mContext, "restaurant1"))
.setMetadata(new Action.Metadata.Builder().setUpload(false))
.build();
Action expectedAction4 = new Action.Builder(Action.Builder.VIEW_ACTION)
.setObject("", ShortcutUtils.getIndexableUrl(mContext, "restaurant2"))
.setMetadata(new Action.Metadata.Builder().setUpload(false))
.build();
// Action has no equals comparator, so instead we compare their string forms.
assertThat(convertActionsToString(actionCaptor.getAllValues())).containsExactly(
expectedAction1.toString(),
expectedAction2.toString(),
expectedAction3.toString(),
expectedAction4.toString());
}
private List<String> convertActionsToString(List<Action> actions) {
List<String> actionStrings = new ArrayList<>();
for (Action action : actions) {
actionStrings.add(action.toString());
}
return actionStrings;
}
private String createEntityUri(String id) {
return "appsearch://__shortcut_adapter_db__/__shortcut_adapter_ns__/" + id;
}
}