1 /* 2 * Copyright 2017 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 android.app.servertransaction; 18 19 import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE; 20 import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY; 21 import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; 22 import static android.app.servertransaction.ActivityLifecycleItem.ON_RESTART; 23 import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; 24 import static android.app.servertransaction.ActivityLifecycleItem.ON_START; 25 import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; 26 import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE; 27 import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; 28 29 import static junit.framework.Assert.assertEquals; 30 31 import static org.junit.Assert.assertArrayEquals; 32 import static org.mockito.ArgumentMatchers.any; 33 import static org.mockito.ArgumentMatchers.eq; 34 import static org.mockito.Mockito.inOrder; 35 import static org.mockito.Mockito.mock; 36 import static org.mockito.Mockito.spy; 37 import static org.mockito.Mockito.times; 38 import static org.mockito.Mockito.verify; 39 import static org.mockito.Mockito.when; 40 41 import android.app.ActivityThread.ActivityClientRecord; 42 import android.app.ClientTransactionHandler; 43 import android.app.servertransaction.ActivityLifecycleItem.LifecycleState; 44 import android.os.IBinder; 45 import android.os.Parcel; 46 import android.os.Parcelable; 47 import android.platform.test.annotations.Presubmit; 48 import android.support.test.filters.SmallTest; 49 import android.support.test.runner.AndroidJUnit4; 50 51 import org.junit.Before; 52 import org.junit.Test; 53 import org.junit.runner.RunWith; 54 import org.mockito.InOrder; 55 56 import java.util.Arrays; 57 import java.util.Collections; 58 import java.util.List; 59 import java.util.stream.Collectors; 60 61 /** Test {@link TransactionExecutor} logic. */ 62 @RunWith(AndroidJUnit4.class) 63 @SmallTest 64 @Presubmit 65 public class TransactionExecutorTests { 66 67 private TransactionExecutor mExecutor; 68 private TransactionExecutorHelper mExecutorHelper; 69 private ClientTransactionHandler mTransactionHandler; 70 private ActivityClientRecord mClientRecord; 71 72 @Before 73 public void setUp() throws Exception { 74 mTransactionHandler = mock(ClientTransactionHandler.class); 75 76 mClientRecord = new ActivityClientRecord(); 77 when(mTransactionHandler.getActivityClient(any())).thenReturn(mClientRecord); 78 79 mExecutor = spy(new TransactionExecutor(mTransactionHandler)); 80 mExecutorHelper = new TransactionExecutorHelper(); 81 } 82 83 @Test 84 public void testLifecycleFromPreOnCreate() { 85 mClientRecord.setState(PRE_ON_CREATE); 86 assertArrayEquals(new int[] {}, path(PRE_ON_CREATE)); 87 assertArrayEquals(new int[] {ON_CREATE}, path(ON_CREATE)); 88 assertArrayEquals(new int[] {ON_CREATE, ON_START}, path(ON_START)); 89 assertArrayEquals(new int[] {ON_CREATE, ON_START, ON_RESUME}, path(ON_RESUME)); 90 assertArrayEquals(new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE}, path(ON_PAUSE)); 91 assertArrayEquals(new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP}, 92 path(ON_STOP)); 93 assertArrayEquals(new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}, 94 path(ON_DESTROY)); 95 } 96 97 @Test 98 public void testLifecycleFromOnCreate() { 99 mClientRecord.setState(ON_CREATE); 100 assertArrayEquals(new int[] {}, path(ON_CREATE)); 101 assertArrayEquals(new int[] {ON_START}, path(ON_START)); 102 assertArrayEquals(new int[] {ON_START, ON_RESUME}, path(ON_RESUME)); 103 assertArrayEquals(new int[] {ON_START, ON_RESUME, ON_PAUSE}, path(ON_PAUSE)); 104 assertArrayEquals(new int[] {ON_START, ON_RESUME, ON_PAUSE, ON_STOP}, path(ON_STOP)); 105 assertArrayEquals(new int[] {ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}, 106 path(ON_DESTROY)); 107 } 108 109 @Test 110 public void testLifecycleFromOnStart() { 111 mClientRecord.setState(ON_START); 112 assertArrayEquals(new int[] {ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY, ON_CREATE}, 113 path(ON_CREATE)); 114 assertArrayEquals(new int[] {}, path(ON_START)); 115 assertArrayEquals(new int[] {ON_RESUME}, path(ON_RESUME)); 116 assertArrayEquals(new int[] {ON_RESUME, ON_PAUSE}, path(ON_PAUSE)); 117 assertArrayEquals(new int[] {ON_RESUME, ON_PAUSE, ON_STOP}, path(ON_STOP)); 118 assertArrayEquals(new int[] {ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}, path(ON_DESTROY)); 119 } 120 121 @Test 122 public void testLifecycleFromOnResume() { 123 mClientRecord.setState(ON_RESUME); 124 assertArrayEquals(new int[] {ON_PAUSE, ON_STOP, ON_DESTROY, ON_CREATE}, path(ON_CREATE)); 125 assertArrayEquals(new int[] {ON_PAUSE, ON_STOP, ON_RESTART, ON_START}, path(ON_START)); 126 assertArrayEquals(new int[] {}, path(ON_RESUME)); 127 assertArrayEquals(new int[] {ON_PAUSE}, path(ON_PAUSE)); 128 assertArrayEquals(new int[] {ON_PAUSE, ON_STOP}, path(ON_STOP)); 129 assertArrayEquals(new int[] {ON_PAUSE, ON_STOP, ON_DESTROY}, path(ON_DESTROY)); 130 } 131 132 @Test 133 public void testLifecycleFromOnPause() { 134 mClientRecord.setState(ON_PAUSE); 135 assertArrayEquals(new int[] {ON_STOP, ON_DESTROY, ON_CREATE}, path(ON_CREATE)); 136 assertArrayEquals(new int[] {ON_STOP, ON_RESTART, ON_START}, path(ON_START)); 137 assertArrayEquals(new int[] {ON_RESUME}, path(ON_RESUME)); 138 assertArrayEquals(new int[] {}, path(ON_PAUSE)); 139 assertArrayEquals(new int[] {ON_STOP}, path(ON_STOP)); 140 assertArrayEquals(new int[] {ON_STOP, ON_DESTROY}, path(ON_DESTROY)); 141 } 142 143 @Test 144 public void testLifecycleFromOnStop() { 145 mClientRecord.setState(ON_STOP); 146 assertArrayEquals(new int[] {ON_DESTROY, ON_CREATE}, path(ON_CREATE)); 147 assertArrayEquals(new int[] {ON_RESTART, ON_START}, path(ON_START)); 148 assertArrayEquals(new int[] {ON_RESTART, ON_START, ON_RESUME}, path(ON_RESUME)); 149 assertArrayEquals(new int[] {ON_RESTART, ON_START, ON_RESUME, ON_PAUSE}, path(ON_PAUSE)); 150 assertArrayEquals(new int[] {}, path(ON_STOP)); 151 assertArrayEquals(new int[] {ON_DESTROY}, path(ON_DESTROY)); 152 } 153 154 @Test 155 public void testLifecycleFromOnDestroy() { 156 mClientRecord.setState(ON_DESTROY); 157 assertArrayEquals(new int[] {ON_CREATE}, path(ON_CREATE)); 158 assertArrayEquals(new int[] {ON_CREATE, ON_START}, path(ON_START)); 159 assertArrayEquals(new int[] {ON_CREATE, ON_START, ON_RESUME}, path(ON_RESUME)); 160 assertArrayEquals(new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE}, path(ON_PAUSE)); 161 assertArrayEquals(new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP}, 162 path(ON_STOP)); 163 assertArrayEquals(new int[] {}, path(ON_DESTROY)); 164 } 165 166 @Test 167 public void testLifecycleExcludeLastItem() { 168 mClientRecord.setState(PRE_ON_CREATE); 169 assertArrayEquals(new int[] {}, pathExcludeLast(PRE_ON_CREATE)); 170 assertArrayEquals(new int[] {}, pathExcludeLast(ON_CREATE)); 171 assertArrayEquals(new int[] {ON_CREATE}, pathExcludeLast(ON_START)); 172 assertArrayEquals(new int[] {ON_CREATE, ON_START}, pathExcludeLast(ON_RESUME)); 173 assertArrayEquals(new int[] {ON_CREATE, ON_START, ON_RESUME}, pathExcludeLast(ON_PAUSE)); 174 assertArrayEquals(new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE}, 175 pathExcludeLast(ON_STOP)); 176 assertArrayEquals(new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP}, 177 pathExcludeLast(ON_DESTROY)); 178 } 179 180 @Test(expected = IllegalArgumentException.class) 181 public void testLifecycleUndefinedStartState() { 182 mClientRecord.setState(UNDEFINED); 183 path(ON_CREATE); 184 } 185 186 @Test(expected = IllegalArgumentException.class) 187 public void testLifecycleUndefinedFinishState() { 188 mClientRecord.setState(PRE_ON_CREATE); 189 path(UNDEFINED); 190 } 191 192 @Test(expected = IllegalArgumentException.class) 193 public void testLifecycleInvalidPreOnCreateFinishState() { 194 mClientRecord.setState(ON_CREATE); 195 path(PRE_ON_CREATE); 196 } 197 198 @Test(expected = IllegalArgumentException.class) 199 public void testLifecycleInvalidOnRestartStartState() { 200 mClientRecord.setState(ON_RESTART); 201 path(ON_RESUME); 202 } 203 204 @Test(expected = IllegalArgumentException.class) 205 public void testLifecycleInvalidOnRestartFinishState() { 206 mClientRecord.setState(ON_CREATE); 207 path(ON_RESTART); 208 } 209 210 @Test 211 public void testTransactionResolution() { 212 ClientTransactionItem callback1 = mock(ClientTransactionItem.class); 213 when(callback1.getPostExecutionState()).thenReturn(UNDEFINED); 214 ClientTransactionItem callback2 = mock(ClientTransactionItem.class); 215 when(callback2.getPostExecutionState()).thenReturn(UNDEFINED); 216 ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class); 217 IBinder token = mock(IBinder.class); 218 219 ClientTransaction transaction = ClientTransaction.obtain(null /* client */, 220 token /* activityToken */); 221 transaction.addCallback(callback1); 222 transaction.addCallback(callback2); 223 transaction.setLifecycleStateRequest(stateRequest); 224 225 transaction.preExecute(mTransactionHandler); 226 mExecutor.execute(transaction); 227 228 InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest); 229 inOrder.verify(callback1, times(1)).execute(eq(mTransactionHandler), eq(token), any()); 230 inOrder.verify(callback2, times(1)).execute(eq(mTransactionHandler), eq(token), any()); 231 inOrder.verify(stateRequest, times(1)).execute(eq(mTransactionHandler), eq(token), any()); 232 } 233 234 @Test 235 public void testActivityResultRequiredStateResolution() { 236 PostExecItem postExecItem = new PostExecItem(ON_RESUME); 237 238 IBinder token = mock(IBinder.class); 239 ClientTransaction transaction = ClientTransaction.obtain(null /* client */, 240 token /* activityToken */); 241 transaction.addCallback(postExecItem); 242 243 // Verify resolution that should get to onPause 244 mClientRecord.setState(ON_RESUME); 245 mExecutor.executeCallbacks(transaction); 246 verify(mExecutor, times(1)).cycleToPath(eq(mClientRecord), eq(ON_PAUSE)); 247 248 // Verify resolution that should get to onStart 249 mClientRecord.setState(ON_STOP); 250 mExecutor.executeCallbacks(transaction); 251 verify(mExecutor, times(1)).cycleToPath(eq(mClientRecord), eq(ON_START)); 252 } 253 254 @Test 255 public void testClosestStateResolutionForSameState() { 256 final int[] allStates = new int[] { 257 ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}; 258 259 mClientRecord.setState(ON_CREATE); 260 assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, 261 shuffledArray(allStates))); 262 263 mClientRecord.setState(ON_START); 264 assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, 265 shuffledArray(allStates))); 266 267 mClientRecord.setState(ON_RESUME); 268 assertEquals(ON_RESUME, mExecutorHelper.getClosestOfStates(mClientRecord, 269 shuffledArray(allStates))); 270 271 mClientRecord.setState(ON_PAUSE); 272 assertEquals(ON_PAUSE, mExecutorHelper.getClosestOfStates(mClientRecord, 273 shuffledArray(allStates))); 274 275 mClientRecord.setState(ON_STOP); 276 assertEquals(ON_STOP, mExecutorHelper.getClosestOfStates(mClientRecord, 277 shuffledArray(allStates))); 278 279 mClientRecord.setState(ON_DESTROY); 280 assertEquals(ON_DESTROY, mExecutorHelper.getClosestOfStates(mClientRecord, 281 shuffledArray(allStates))); 282 283 mClientRecord.setState(PRE_ON_CREATE); 284 assertEquals(PRE_ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, 285 new int[] {PRE_ON_CREATE})); 286 } 287 288 @Test 289 public void testClosestStateResolution() { 290 mClientRecord.setState(PRE_ON_CREATE); 291 assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 292 new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}))); 293 assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 294 new int[] {ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}))); 295 assertEquals(ON_RESUME, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 296 new int[] {ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}))); 297 assertEquals(ON_PAUSE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 298 new int[] {ON_PAUSE, ON_STOP, ON_DESTROY}))); 299 assertEquals(ON_STOP, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 300 new int[] {ON_STOP, ON_DESTROY}))); 301 assertEquals(ON_DESTROY, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 302 new int[] {ON_DESTROY}))); 303 } 304 305 @Test 306 public void testClosestStateResolutionFromOnCreate() { 307 mClientRecord.setState(ON_CREATE); 308 assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 309 new int[] {ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}))); 310 } 311 312 @Test 313 public void testClosestStateResolutionFromOnStart() { 314 mClientRecord.setState(ON_START); 315 assertEquals(ON_RESUME, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 316 new int[] {ON_CREATE, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}))); 317 assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 318 new int[] {ON_CREATE}))); 319 } 320 321 @Test 322 public void testClosestStateResolutionFromOnResume() { 323 mClientRecord.setState(ON_RESUME); 324 assertEquals(ON_PAUSE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 325 new int[] {ON_CREATE, ON_START, ON_PAUSE, ON_STOP, ON_DESTROY}))); 326 assertEquals(ON_DESTROY, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 327 new int[] {ON_CREATE, ON_DESTROY}))); 328 assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 329 new int[] {ON_CREATE, ON_START}))); 330 assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 331 new int[] {ON_CREATE}))); 332 } 333 334 @Test 335 public void testClosestStateResolutionFromOnPause() { 336 mClientRecord.setState(ON_PAUSE); 337 assertEquals(ON_RESUME, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 338 new int[] {ON_CREATE, ON_START, ON_RESUME, ON_DESTROY}))); 339 assertEquals(ON_STOP, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 340 new int[] {ON_CREATE, ON_START, ON_STOP, ON_DESTROY}))); 341 assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 342 new int[] {ON_CREATE, ON_START}))); 343 assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 344 new int[] {ON_CREATE}))); 345 } 346 347 @Test 348 public void testClosestStateResolutionFromOnStop() { 349 mClientRecord.setState(ON_STOP); 350 assertEquals(ON_RESUME, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 351 new int[] {ON_CREATE, ON_RESUME, ON_PAUSE, ON_DESTROY}))); 352 assertEquals(ON_DESTROY, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 353 new int[] {ON_CREATE, ON_DESTROY}))); 354 assertEquals(ON_START, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 355 new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE}))); 356 } 357 358 @Test 359 public void testClosestStateResolutionFromOnDestroy() { 360 mClientRecord.setState(ON_DESTROY); 361 assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray( 362 new int[] {ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP}))); 363 } 364 365 @Test 366 public void testClosestStateResolutionToUndefined() { 367 mClientRecord.setState(ON_CREATE); 368 assertEquals(UNDEFINED, 369 mExecutorHelper.getClosestPreExecutionState(mClientRecord, UNDEFINED)); 370 } 371 372 @Test 373 public void testClosestStateResolutionToOnResume() { 374 mClientRecord.setState(ON_DESTROY); 375 assertEquals(ON_START, 376 mExecutorHelper.getClosestPreExecutionState(mClientRecord, ON_RESUME)); 377 mClientRecord.setState(ON_START); 378 assertEquals(ON_START, 379 mExecutorHelper.getClosestPreExecutionState(mClientRecord, ON_RESUME)); 380 mClientRecord.setState(ON_PAUSE); 381 assertEquals(ON_PAUSE, 382 mExecutorHelper.getClosestPreExecutionState(mClientRecord, ON_RESUME)); 383 } 384 385 private static int[] shuffledArray(int[] inputArray) { 386 final List<Integer> list = Arrays.stream(inputArray).boxed().collect(Collectors.toList()); 387 Collections.shuffle(list); 388 return list.stream().mapToInt(Integer::intValue).toArray(); 389 } 390 391 private int[] path(int finish) { 392 return mExecutorHelper.getLifecyclePath(mClientRecord.getLifecycleState(), finish, 393 false /* excludeLastState */).toArray(); 394 } 395 396 private int[] pathExcludeLast(int finish) { 397 return mExecutorHelper.getLifecyclePath(mClientRecord.getLifecycleState(), finish, 398 true /* excludeLastState */).toArray(); 399 } 400 401 /** A transaction item that requires some specific post-execution state. */ 402 private static class PostExecItem extends StubItem { 403 404 @LifecycleState 405 private int mPostExecutionState; 406 407 PostExecItem(@LifecycleState int state) { 408 mPostExecutionState = state; 409 } 410 411 @Override 412 public int getPostExecutionState() { 413 return mPostExecutionState; 414 } 415 } 416 417 /** Stub implementation of a transaction item that works as a base class for items in tests. */ 418 private static class StubItem extends ClientTransactionItem { 419 420 private StubItem() { 421 } 422 423 private StubItem(Parcel in) { 424 } 425 426 @Override 427 public void execute(ClientTransactionHandler client, IBinder token, 428 PendingTransactionActions pendingActions) { 429 } 430 431 @Override 432 public void recycle() { 433 } 434 435 @Override 436 public void writeToParcel(Parcel dest, int flags) { 437 } 438 439 public static final Parcelable.Creator<StubItem> CREATOR = 440 new Parcelable.Creator<StubItem>() { 441 public StubItem createFromParcel(Parcel in) { 442 return new StubItem(in); 443 } 444 445 public StubItem[] newArray(int size) { 446 return new StubItem[size]; 447 } 448 }; 449 } 450 } 451