[go: nahoru, domu]

blob: 88b0eb6d73fb8da01c932e39f62ddf298a97961d [file] [log] [blame]
/*
* Copyright 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 androidx.slice.widget;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Instrumentation;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.slice.Slice;
import androidx.slice.SliceItem;
import androidx.slice.SliceUtils;
import androidx.slice.SliceViewManager;
import androidx.slice.SliceViewManager.SliceCallback;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.concurrent.CountDownLatch;
@RunWith(AndroidJUnit4.class)
@MediumTest
@SdkSuppress(minSdkVersion = 19)
public class SliceLiveDataTest {
private static final Uri URI = Uri.parse("content://test/something");
private static final Intent INTENT_ONE = new Intent("intent1");
private static final Intent INTENT_TWO = new Intent("intent2");
private static final Intent INTENT_THREE = new Intent("intent3");
private final Context mContext = ApplicationProvider.getApplicationContext();
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
private SliceViewManager mManager = mock(SliceViewManager.class);
private SliceLiveData.OnErrorListener mErrorListener =
mock(SliceLiveData.OnErrorListener.class);
private Observer<Slice> mObserver = mock(Observer.class);
private final SliceItem.ActionHandler mActionHandler = mock(SliceItem.ActionHandler.class);
private Slice mBaseSlice = new Slice.Builder(URI)
.addAction(mActionHandler,
new Slice.Builder(Uri.parse("content://test/something/other")).build(),
null)
.build();
private SliceLiveData.CachedSliceLiveData mLiveData;
private ArgumentCaptor<Slice> mSlice;
@Before
public void setUp() throws InterruptedException {
InputStream input = createInput(mBaseSlice);
mLiveData = SliceLiveData.fromStream(mContext, mManager, input, mErrorListener);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
mLiveData.observeForever(mObserver);
}
});
waitForAsync();
// The second one executes the loading initial slice code.
waitForAsync();
mInstrumentation.waitForIdleSync();
}
@After
public void tearDown() {
if (mLiveData != null) {
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
mLiveData.removeObserver(mObserver);
}
});
}
}
@Test
public void testOnlyCache() throws InterruptedException {
verify(mManager, never()).bindSlice(any(Uri.class));
verify(mManager, never()).registerSliceCallback(any(Uri.class),
any(SliceCallback.class));
verify(mObserver, times(1)).onChanged(any(Slice.class));
verify(mErrorListener, never()).onSliceError(anyInt(), any(Throwable.class));
}
@Test
public void testClickGoesLive() throws PendingIntent.CanceledException, InterruptedException {
when(mManager.bindSlice(URI)).thenReturn(mBaseSlice);
ArgumentCaptor<Slice> s = ArgumentCaptor.forClass(Slice.class);
verify(mObserver, times(1)).onChanged(s.capture());
clearInvocations(mObserver);
s.getValue().getItems().get(0).fireAction(null, null);
waitForAsync();
mInstrumentation.waitForIdleSync();
verify(mManager).bindSlice(any(Uri.class));
verify(mManager).registerSliceCallback(any(Uri.class),
any(SliceCallback.class));
verify(mObserver, times(1)).onChanged(any(Slice.class));
verify(mErrorListener, never()).onSliceError(anyInt(), any(Throwable.class));
verify(mActionHandler).onAction(any(SliceItem.class), (Context) eq(null),
(Intent) eq(null));
}
@Test
public void testMultipleClickGoesLive() throws InterruptedException {
when(mManager.bindSlice(URI)).thenReturn(mBaseSlice);
mSlice = ArgumentCaptor.forClass(Slice.class);
verify(mObserver, times(1)).onChanged(mSlice.capture());
clearInvocations(mObserver);
android.os.AsyncTask.execute(new Runnable() {
@Override
public void run() {
try {
SliceItem item = mSlice.getValue().getItems().get(0);
item.fireAction(null, INTENT_ONE);
item.fireAction(null, INTENT_TWO);
item.fireAction(null, INTENT_THREE);
} catch (PendingIntent.CanceledException e) {
}
}
});
// Wait for the completion of the first async to fire action three times.
waitForAsync();
// Wait for the completion of the second async to update slice.
waitForAsync();
mInstrumentation.waitForIdleSync();
verify(mManager, times(1)).bindSlice(any(Uri.class));
verify(mManager, times(1)).registerSliceCallback(any(Uri.class),
any(SliceCallback.class));
verify(mObserver, times(1)).onChanged(any(Slice.class));
// Make sure error listener is not triggered.
verify(mErrorListener, never()).onSliceError(anyInt(), any(Throwable.class));
// Make sure all three intent actions are fired.
verify(mActionHandler, times(1)).onAction(any(SliceItem.class), (Context) eq(null),
eq(INTENT_ONE));
verify(mActionHandler, times(1)).onAction(any(SliceItem.class), (Context) eq(null),
eq(INTENT_TWO));
verify(mActionHandler, times(1)).onAction(any(SliceItem.class), (Context) eq(null),
eq(INTENT_THREE));
}
@Test
public void testWaitsForLoad() throws PendingIntent.CanceledException, InterruptedException {
Slice loadingSlice = new Slice.Builder(URI)
.addAction(mActionHandler,
new Slice.Builder(Uri.parse("content://test/something/other"))
.addHints(android.app.slice.Slice.HINT_PARTIAL)
.build(),
null)
.build();
when(mManager.bindSlice(URI)).thenReturn(loadingSlice);
ArgumentCaptor<Slice> s = ArgumentCaptor.forClass(Slice.class);
verify(mObserver, times(1)).onChanged(s.capture());
clearInvocations(mObserver);
s.getValue().getItems().get(0).fireAction(null, null);
waitForAsync();
mInstrumentation.waitForIdleSync();
// Loading slice returned, shouldn't have triggered.
verify(mActionHandler, never()).onAction(any(SliceItem.class), (Context) eq(null),
(Intent) eq(null));
// Pass it the loaded slice now.
verify(mManager).registerSliceCallback(any(Uri.class),
argThat(new ArgumentMatcher<SliceCallback>() {
@Override
public boolean matches(SliceCallback argument) {
argument.onSliceUpdated(mBaseSlice);
return true;
}
}));
waitForAsync();
mInstrumentation.waitForIdleSync();
verify(mActionHandler).onAction(any(SliceItem.class), (Context) eq(null),
(Intent) eq(null));
}
@Test
public void testStructureChange() throws PendingIntent.CanceledException, InterruptedException {
when(mManager.bindSlice(URI)).thenReturn(new Slice.Builder(URI).build());
ArgumentCaptor<Slice> s = ArgumentCaptor.forClass(Slice.class);
verify(mObserver, times(1)).onChanged(s.capture());
clearInvocations(mObserver);
s.getValue().getItems().get(0).fireAction(null, null);
waitForAsync();
mInstrumentation.waitForIdleSync();
verify(mErrorListener).onSliceError(
eq(SliceLiveData.OnErrorListener.ERROR_STRUCTURE_CHANGED), (Throwable) eq(null));
}
@Test
public void testSliceMissing() throws PendingIntent.CanceledException, InterruptedException {
ArgumentCaptor<Slice> s = ArgumentCaptor.forClass(Slice.class);
verify(mObserver, times(1)).onChanged(s.capture());
clearInvocations(mObserver);
s.getValue().getItems().get(0).fireAction(null, null);
waitForAsync();
mInstrumentation.waitForIdleSync();
verify(mErrorListener).onSliceError(
eq(SliceLiveData.OnErrorListener.ERROR_SLICE_NO_LONGER_PRESENT),
(Throwable) eq(null));
}
@Test
public void testInvalidInput() throws PendingIntent.CanceledException, InterruptedException {
mLiveData = SliceLiveData.fromStream(mContext, mManager,
new ByteArrayInputStream(new byte[0]), mErrorListener);
mLiveData.parseStream();
waitForAsync();
mInstrumentation.waitForIdleSync();
verify(mErrorListener).onSliceError(
eq(SliceLiveData.OnErrorListener.ERROR_INVALID_INPUT),
any(Throwable.class));
}
@Test
@UiThreadTest
public void testInvalidUri() {
final SliceView sliceView = new SliceView(mContext);
LiveData<Slice> sliceLiveData = SliceLiveData.fromUri(mContext,
Uri.parse("content://doesnotexist"));
sliceLiveData.observeForever(sliceView);
assertNull(sliceView.getSlice());
}
private InputStream createInput(Slice s) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
SliceUtils.serializeSlice(s, mContext, output, new SliceUtils.SerializeOptions()
.setActionMode(SliceUtils.SerializeOptions.MODE_CONVERT));
return new ByteArrayInputStream(output.toByteArray());
}
private void waitForAsync() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
android.os.AsyncTask.execute(new Runnable() {
@Override
public void run() {
latch.countDown();
}
});
latch.await();
}
}