1 package com.android.server.slice; 2 3 import static org.junit.Assert.assertArrayEquals; 4 import static org.junit.Assert.assertEquals; 5 import static org.junit.Assert.assertFalse; 6 import static org.junit.Assert.assertNull; 7 import static org.junit.Assert.assertTrue; 8 import static org.mockito.ArgumentMatchers.any; 9 import static org.mockito.ArgumentMatchers.anyInt; 10 import static org.mockito.ArgumentMatchers.anyString; 11 import static org.mockito.ArgumentMatchers.argThat; 12 import static org.mockito.ArgumentMatchers.eq; 13 import static org.mockito.Mockito.doReturn; 14 import static org.mockito.Mockito.mock; 15 import static org.mockito.Mockito.verify; 16 import static org.mockito.Mockito.when; 17 18 import android.app.slice.ISliceListener; 19 import android.app.slice.SliceProvider; 20 import android.app.slice.SliceSpec; 21 import android.content.ContentProvider; 22 import android.content.IContentProvider; 23 import android.net.Uri; 24 import android.os.Binder; 25 import android.os.Handler; 26 import android.os.IBinder; 27 import android.os.IBinder.DeathRecipient; 28 import android.os.RemoteException; 29 import android.support.test.filters.SmallTest; 30 import android.testing.AndroidTestingRunner; 31 import android.testing.TestableLooper; 32 import android.testing.TestableLooper.RunWithLooper; 33 34 import com.android.server.UiServiceTestCase; 35 36 import org.junit.Before; 37 import org.junit.Test; 38 import org.junit.runner.RunWith; 39 import org.mockito.ArgumentCaptor; 40 41 @SmallTest 42 @RunWith(AndroidTestingRunner.class) 43 @RunWithLooper 44 public class PinnedSliceStateTest extends UiServiceTestCase { 45 46 private static final String AUTH = "my.authority"; 47 private static final Uri TEST_URI = Uri.parse("content://" + AUTH + "/path"); 48 49 private static final SliceSpec[] FIRST_SPECS = new SliceSpec[]{ 50 new SliceSpec("spec1", 3), 51 new SliceSpec("spec2", 3), 52 new SliceSpec("spec3", 2), 53 new SliceSpec("spec4", 1), 54 }; 55 56 private static final SliceSpec[] SECOND_SPECS = new SliceSpec[]{ 57 new SliceSpec("spec2", 1), 58 new SliceSpec("spec3", 2), 59 new SliceSpec("spec4", 3), 60 new SliceSpec("spec5", 4), 61 }; 62 63 private SliceManagerService mSliceService; 64 private PinnedSliceState mPinnedSliceManager; 65 private IContentProvider mIContentProvider; 66 private ContentProvider mContentProvider; 67 private IBinder mToken = new Binder(); 68 69 @Before 70 public void setup() { 71 mSliceService = mock(SliceManagerService.class); 72 when(mSliceService.getContext()).thenReturn(mContext); 73 when(mSliceService.getLock()).thenReturn(new Object()); 74 when(mSliceService.getHandler()).thenReturn(new Handler(TestableLooper.get(this).getLooper())); 75 mContentProvider = mock(ContentProvider.class); 76 mIContentProvider = mock(IContentProvider.class); 77 when(mContentProvider.getIContentProvider()).thenReturn(mIContentProvider); 78 mContext.getContentResolver().addProvider(AUTH, mContentProvider); 79 mPinnedSliceManager = new PinnedSliceState(mSliceService, TEST_URI, "pkg"); 80 } 81 82 @Test 83 public void testMergeSpecs() { 84 // No annotations to start. 85 assertNull(mPinnedSliceManager.getSpecs()); 86 87 mPinnedSliceManager.mergeSpecs(FIRST_SPECS); 88 assertArrayEquals(FIRST_SPECS, mPinnedSliceManager.getSpecs()); 89 90 mPinnedSliceManager.mergeSpecs(SECOND_SPECS); 91 assertArrayEquals(new SliceSpec[]{ 92 // spec1 is gone because it's not in the second set. 93 new SliceSpec("spec2", 1), // spec2 is 1 because it's smaller in the second set. 94 new SliceSpec("spec3", 2), // spec3 is the same in both sets 95 new SliceSpec("spec4", 1), // spec4 is 1 because it's smaller in the first set. 96 // spec5 is gone because it's not in the first set. 97 }, mPinnedSliceManager.getSpecs()); 98 } 99 100 @Test 101 public void testSendPinnedOnPin() throws RemoteException { 102 TestableLooper.get(this).processAllMessages(); 103 104 // When pinned for the first time, a pinned message should be sent. 105 mPinnedSliceManager.pin("pkg", FIRST_SPECS, mToken); 106 TestableLooper.get(this).processAllMessages(); 107 108 verify(mIContentProvider).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), 109 argThat(b -> { 110 assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI)); 111 return true; 112 })); 113 } 114 115 @Test 116 public void testPkgPin() { 117 assertFalse(mPinnedSliceManager.hasPinOrListener()); 118 119 mPinnedSliceManager.pin("pkg", FIRST_SPECS, mToken); 120 assertTrue(mPinnedSliceManager.hasPinOrListener()); 121 122 assertTrue(mPinnedSliceManager.unpin("pkg", mToken)); 123 assertFalse(mPinnedSliceManager.hasPinOrListener()); 124 } 125 126 @Test 127 public void testMultiPkgPin() { 128 IBinder t2 = new Binder(); 129 assertFalse(mPinnedSliceManager.hasPinOrListener()); 130 131 mPinnedSliceManager.pin("pkg", FIRST_SPECS, mToken); 132 assertTrue(mPinnedSliceManager.hasPinOrListener()); 133 mPinnedSliceManager.pin("pkg2", FIRST_SPECS, t2); 134 135 assertFalse(mPinnedSliceManager.unpin("pkg", mToken)); 136 assertTrue(mPinnedSliceManager.unpin("pkg2", t2)); 137 assertFalse(mPinnedSliceManager.hasPinOrListener()); 138 } 139 140 @Test 141 public void testListenerDeath() throws RemoteException { 142 ISliceListener listener = mock(ISliceListener.class); 143 IBinder binder = mock(IBinder.class); 144 when(binder.isBinderAlive()).thenReturn(true); 145 when(listener.asBinder()).thenReturn(binder); 146 assertFalse(mPinnedSliceManager.hasPinOrListener()); 147 148 mPinnedSliceManager.pin(mContext.getPackageName(), FIRST_SPECS, binder); 149 assertTrue(mPinnedSliceManager.hasPinOrListener()); 150 151 ArgumentCaptor<DeathRecipient> arg = ArgumentCaptor.forClass(DeathRecipient.class); 152 verify(binder).linkToDeath(arg.capture(), anyInt()); 153 154 when(binder.isBinderAlive()).thenReturn(false); 155 arg.getValue().binderDied(); 156 157 verify(mSliceService).removePinnedSlice(eq(TEST_URI)); 158 assertFalse(mPinnedSliceManager.hasPinOrListener()); 159 } 160 } 161