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