1 /*
2  * Copyright (C) 2020 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 com.android.tests.atomicinstall;
18 
19 import static com.android.compatibility.common.util.MatcherUtils.assertThrows;
20 import static com.android.compatibility.common.util.MatcherUtils.hasMessageThat;
21 import static com.android.compatibility.common.util.MatcherUtils.instanceOf;
22 import static com.android.cts.install.lib.InstallUtils.openPackageInstallerSession;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.hamcrest.CoreMatchers.containsString;
27 
28 import android.Manifest;
29 import android.content.pm.PackageInstaller;
30 import android.os.Handler;
31 import android.os.HandlerThread;
32 
33 import androidx.annotation.NonNull;
34 import androidx.core.util.Preconditions;
35 import androidx.test.ext.junit.runners.AndroidJUnit4;
36 import androidx.test.platform.app.InstrumentationRegistry;
37 
38 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
39 import com.android.compatibility.common.util.PollingCheck;
40 import com.android.cts.install.lib.Install;
41 import com.android.cts.install.lib.InstallUtils;
42 import com.android.cts.install.lib.TestApp;
43 import com.android.cts.install.lib.Uninstall;
44 
45 import org.junit.After;
46 import org.junit.Before;
47 import org.junit.Rule;
48 import org.junit.Test;
49 import org.junit.rules.TestName;
50 import org.junit.runner.RunWith;
51 
52 import java.io.Closeable;
53 import java.io.IOException;
54 import java.io.OutputStream;
55 import java.util.ArrayList;
56 import java.util.List;
57 import java.util.concurrent.CountDownLatch;
58 import java.util.concurrent.TimeUnit;
59 
60 
61 /**
62  * There are the following factors need to combine for testing the abandon behavior.
63  * <ul>
64  *     <li>staged vs. noStaged</li>
65  *     <li>Single Package vs. MultiPackage</li>
66  *     <li>Receive callback, Session Info abandoned, getNames, openWrite, open abandoned session
67  *     etc.</li>
68  * </ul>*
69  */
70 @RunWith(AndroidJUnit4.class)
71 public class SessionAbandonBehaviorTest {
72     /**
73      * Please don't change too small to ensure the test run normally.
74      */
75     public static final int CALLBACK_TIMEOUT_SECONDS = 10;
76 
77     /**
78      * To wait 1 second prevents the race condition from the framework services.
79      * The child session is cleaned up asynchronously after abandoning the parent session. Even
80      * if receiving the callback to tell the session is finished, it may be the race condition
81      * between executing {@link PackageInstaller#getSessionInfo(int)} and cleaning up the
82      * {@link android.content.pm.PackageInstaller.Session}.
83      */
84     public static final long PREVENT_RACE_CONDITION_TIMEOUT_SECONDS = TimeUnit.SECONDS.toMillis(1);
85     private static final byte[] PLACE_HOLDER_STRING_BYTES = "Place Holder".getBytes();
86 
87     /**
88      * This is a wrapper class to let the test easier to focus on the "onFinish". It implements
89      * all of abstract methods with nothing in {@link PackageInstaller.SessionCallback} except for
90      * onFinish function.
91      */
92     private static class AbandonSessionCallBack extends PackageInstaller.SessionCallback {
93         private final CountDownLatch mCountDownLatch;
94         private final List<Integer> mSessionIds;
95 
AbandonSessionCallBack(CountDownLatch countDownLatch, int[] sessionIds)96         AbandonSessionCallBack(CountDownLatch countDownLatch, int[] sessionIds) {
97             mCountDownLatch = countDownLatch;
98             mSessionIds = new ArrayList<>();
99             for (int sessionId : sessionIds) {
100                 mSessionIds.add(sessionId);
101             }
102         }
103 
AbandonSessionCallBack(CountDownLatch countDownLatch, int sessionId)104         AbandonSessionCallBack(CountDownLatch countDownLatch, int sessionId) {
105             this(countDownLatch, new int[]{sessionId});
106         }
107 
108         @Override
onCreated(int sessionId)109         public void onCreated(int sessionId) {
110             /* Do nothing to make sub class no need to implement it*/
111         }
112 
113         @Override
onBadgingChanged(int sessionId)114         public void onBadgingChanged(int sessionId) {
115             /* Do nothing to make sub class no need to implement it*/
116         }
117 
118         @Override
onActiveChanged(int sessionId, boolean active)119         public void onActiveChanged(int sessionId, boolean active) {
120             /* Do nothing to make sub class no need to implement it*/
121         }
122 
123         @Override
onProgressChanged(int sessionId, float progress)124         public void onProgressChanged(int sessionId, float progress) {
125             /* Do nothing to make sub class no need to implement it*/
126         }
127 
128         @Override
onFinished(int sessionId, boolean success)129         public void onFinished(int sessionId, boolean success) {
130             if (!success) {
131                 if (mSessionIds.contains(sessionId)) {
132                     mCountDownLatch.countDown();
133                 }
134             }
135         }
136     }
137 
138     @Rule
139     public final AdoptShellPermissionsRule mAdoptShellPermissionsRule =
140             new AdoptShellPermissionsRule(
141                     InstrumentationRegistry.getInstrumentation().getUiAutomation(),
142                     Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES);
143 
144     @Rule
145     public final TestName mTestName = new TestName();
146 
147     private final List<PackageInstaller.SessionCallback> mSessionCallbacks = new ArrayList<>();
148 
149     private Handler mHandler;
150     private HandlerThread mHandlerThread;
151     private List<Closeable> mCloseableList = new ArrayList<>();
152 
153     @After
tearDown()154     public void tearDown() {
155         for (Closeable closeable : mCloseableList) {
156             try {
157                 closeable.close();
158             } catch (IOException e) {
159                 /* ensure close the resources and do no nothing */
160             }
161         }
162 
163         for (PackageInstaller.SessionCallback sessionCallback : mSessionCallbacks) {
164             InstallUtils.getPackageInstaller().unregisterSessionCallback(sessionCallback);
165         }
166         mSessionCallbacks.clear();
167 
168         if (mHandlerThread != null) {
169             mHandlerThread.quit();
170         }
171     }
172 
173 
174     /**
175      * To help the test to register the {@link PackageInstaller.SessionCallback} easier and the
176      * parameter {@link PackageInstaller.SessionCallback} will unregister after the end of the
177      * test.
178      *
179      * @param sessionCallback registers by the {@link PackageInstaller}
180      */
registerSessionCallbacks( @onNull PackageInstaller.SessionCallback sessionCallback)181     private void registerSessionCallbacks(
182             @NonNull PackageInstaller.SessionCallback sessionCallback) {
183         Preconditions.checkNotNull(sessionCallback);
184         Preconditions.checkArgument(!mSessionCallbacks.contains(sessionCallback),
185                 "The callback has registered.");
186 
187         if (mHandler == null) {
188             mHandlerThread = new HandlerThread(mTestName.getMethodName());
189             mHandlerThread.start();
190             mHandler = new Handler(mHandlerThread.getLooper());
191         }
192 
193         InstallUtils.getPackageInstaller().registerSessionCallback(sessionCallback, mHandler);
194         mSessionCallbacks.add(sessionCallback);
195     }
196 
197     /**
198      * To get all of child session IDs.
199      *
200      * @param parentSessionId the parent session id
201      * @return the array of child session IDs
202      * @throws IOException caused by opening parent session fail.
203      */
getChildSessionIds(int parentSessionId)204     private int[] getChildSessionIds(int parentSessionId) throws IOException {
205         try (PackageInstaller.Session parentSession =
206                      openPackageInstallerSession(parentSessionId)) {
207             return parentSession.getChildSessionIds();
208         }
209     }
210 
getAllChildSessions(int[] sessionIds)211     private static List<PackageInstaller.SessionInfo> getAllChildSessions(int[] sessionIds) {
212         List<PackageInstaller.SessionInfo> result = new ArrayList<>();
213         for (int sessionId : sessionIds) {
214             final PackageInstaller.SessionInfo session =
215                     InstallUtils.getPackageInstaller().getSessionInfo(sessionId);
216             if (session != null) {
217                 result.add(session);
218             }
219         }
220         return result;
221     }
222 
223     /**
224      * To open the specified session.
225      * <p>
226      * The opened resources will be closed in {@link #tearDown()} automatically.
227      * </p>
228      *
229      * @param sessionId the session want to open
230      * @return the opened {@link PackageInstaller.Session} instance
231      * @throws IOException caused by opening {@link PackageInstaller.Session} fail.
232      */
openSession(int sessionId)233     private PackageInstaller.Session openSession(int sessionId) throws IOException {
234         PackageInstaller.Session session = openPackageInstallerSession(sessionId);
235         mCloseableList.add(session);
236 
237         return session;
238     }
239 
240     /**
241      * To open and write the file for the specified session.
242      * <p>
243      * The opened resources will be closed in {@link #tearDown()} automatically.
244      * </p>
245      *
246      * @param sessionId the session want to open
247      * @param fileName  the expected file name
248      * @return the opened {@link OutputStream} instance
249      * @throws IOException caused by opening file fail.
250      */
openSessionForWrite(int sessionId, String fileName)251     private OutputStream openSessionForWrite(int sessionId, String fileName) throws IOException {
252         PackageInstaller.Session session = openSession(sessionId);
253         OutputStream os = session.openWrite(fileName, 0, -1);
254         mCloseableList.add(os);
255 
256         return os;
257     }
258 
259     @Before
setUp()260     public void setUp() throws Exception {
261         Uninstall.packages(TestApp.A, TestApp.B);
262     }
263 
264     @Test
abandon_stagedSession_shouldReceiveAbandonCallBack()265     public void abandon_stagedSession_shouldReceiveAbandonCallBack()
266             throws Exception {
267         final int sessionId = Install.single(TestApp.A1).setStaged().createSession();
268         final CountDownLatch countDownLatch = new CountDownLatch(1);
269         registerSessionCallbacks(
270                 new AbandonSessionCallBack(countDownLatch, sessionId));
271 
272         InstallUtils.getPackageInstaller().abandonSession(sessionId);
273 
274         assertThat(
275                 countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
276     }
277 
278     @Test
abandon_nonStagedSession_shouldReceiveAbandonCallBack()279     public void abandon_nonStagedSession_shouldReceiveAbandonCallBack()
280             throws Exception {
281         final int sessionId = Install.single(TestApp.A1).createSession();
282         final CountDownLatch countDownLatch = new CountDownLatch(1);
283         registerSessionCallbacks(
284                 new AbandonSessionCallBack(countDownLatch, sessionId));
285 
286         InstallUtils.getPackageInstaller().abandonSession(sessionId);
287 
288         assertThat(
289                 countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
290     }
291 
292 
293     @Test
abandon_stagedSession_openedSession_canNotGetNames()294     public void abandon_stagedSession_openedSession_canNotGetNames()
295             throws Exception {
296         final int sessionId = Install.single(TestApp.A1).setStaged().createSession();
297         final CountDownLatch countDownLatch = new CountDownLatch(1);
298         final PackageInstaller.Session session = openSession(sessionId);
299         registerSessionCallbacks(
300                 new AbandonSessionCallBack(countDownLatch, sessionId));
301 
302         InstallUtils.getPackageInstaller().abandonSession(sessionId);
303         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
304 
305         assertThrows(instanceOf(SecurityException.class,
306                 hasMessageThat(containsString("getNames not allowed"))),
307                 () -> session.getNames());
308     }
309 
310     @Test
abandon_nonStagedSession_openedSession_canNotGetNames()311     public void abandon_nonStagedSession_openedSession_canNotGetNames()
312             throws Exception {
313         final int sessionId = Install.single(TestApp.A1).createSession();
314         final CountDownLatch countDownLatch = new CountDownLatch(1);
315         final PackageInstaller.Session session = openSession(sessionId);
316         registerSessionCallbacks(
317                 new AbandonSessionCallBack(countDownLatch, sessionId));
318 
319         InstallUtils.getPackageInstaller().abandonSession(sessionId);
320         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
321 
322         assertThrows(instanceOf(SecurityException.class,
323                 hasMessageThat(containsString("getNames not allowed"))),
324                 () -> session.getNames());
325     }
326 
327     @Test
abandon_stagedSession_openForWriting_shouldFail()328     public void abandon_stagedSession_openForWriting_shouldFail()
329             throws Exception {
330         final int sessionId = Install.single(TestApp.A1).setStaged().createSession();
331         final CountDownLatch countDownLatch = new CountDownLatch(1);
332         registerSessionCallbacks(
333                 new AbandonSessionCallBack(countDownLatch, sessionId));
334         final OutputStream outputStream = openSessionForWrite(sessionId,
335                 mTestName.getMethodName());
336 
337         InstallUtils.getPackageInstaller().abandonSession(sessionId);
338         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
339 
340         assertThrows(instanceOf(IOException.class,
341                 hasMessageThat(containsString("write failed"))),
342                 () -> outputStream.write(PLACE_HOLDER_STRING_BYTES));
343     }
344 
345     @Test
abandon_nonStagedSession_openForWriting_shouldFail()346     public void abandon_nonStagedSession_openForWriting_shouldFail()
347             throws Exception {
348         final int sessiondId = Install.single(TestApp.A1).createSession();
349         final CountDownLatch countDownLatch = new CountDownLatch(1);
350         registerSessionCallbacks(
351                 new AbandonSessionCallBack(countDownLatch, sessiondId));
352         final OutputStream outputStream = openSessionForWrite(sessiondId,
353                 mTestName.getMethodName());
354 
355         InstallUtils.getPackageInstaller().abandonSession(sessiondId);
356         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
357 
358         assertThrows(instanceOf(IOException.class,
359                 hasMessageThat(containsString("write failed"))),
360                 () -> outputStream.write(PLACE_HOLDER_STRING_BYTES));
361     }
362 
363     @Test
abandon_stagedSession_canNotOpenAgain()364     public void abandon_stagedSession_canNotOpenAgain()
365             throws Exception {
366         final int sessionId = Install.single(TestApp.A1).setStaged().createSession();
367         final CountDownLatch countDownLatch = new CountDownLatch(1);
368         registerSessionCallbacks(
369                 new AbandonSessionCallBack(countDownLatch, sessionId));
370 
371         InstallUtils.getPackageInstaller().abandonSession(sessionId);
372         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
373 
374         assertThrows(instanceOf(SecurityException.class,
375                 hasMessageThat(containsString(String.valueOf(sessionId)))),
376                 () -> InstallUtils.getPackageInstaller().openSession(sessionId));
377     }
378 
379     @Test
abandon_nonStagedSession_canNotOpenAgain()380     public void abandon_nonStagedSession_canNotOpenAgain()
381             throws Exception {
382         final int sessionId = Install.single(TestApp.A1).createSession();
383         final CountDownLatch countDownLatch = new CountDownLatch(1);
384         registerSessionCallbacks(
385                 new AbandonSessionCallBack(countDownLatch, sessionId));
386 
387         InstallUtils.getPackageInstaller().abandonSession(sessionId);
388         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
389 
390         assertThrows(instanceOf(SecurityException.class,
391                 hasMessageThat(containsString(String.valueOf(sessionId)))),
392                 () -> InstallUtils.getPackageInstaller().openSession(sessionId));
393     }
394 
395     @Test
abandon_stagedParentSession_shouldReceiveAllChildrenAbandonCallBack()396     public void abandon_stagedParentSession_shouldReceiveAllChildrenAbandonCallBack()
397             throws Exception {
398         final int parentSessionId = Install.multi(TestApp.A1,
399                 TestApp.B1).setStaged().createSession();
400         final int[] childSessionIds = getChildSessionIds(parentSessionId);
401         final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
402         registerSessionCallbacks(
403                 new AbandonSessionCallBack(countDownLatch, childSessionIds));
404 
405         InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
406 
407         assertThat(
408                 countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
409     }
410 
411     @Test
abandon_nonStagedParentSession_shouldReceiveAllChildrenAbandonCallBack()412     public void abandon_nonStagedParentSession_shouldReceiveAllChildrenAbandonCallBack()
413             throws Exception {
414         final int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).createSession();
415         final int[] childSessionIds = getChildSessionIds(parentSessionId);
416         final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
417         registerSessionCallbacks(
418                 new AbandonSessionCallBack(countDownLatch, childSessionIds));
419 
420         InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
421 
422         assertThat(
423                 countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
424     }
425 
426     @Test
abandon_stagedParentSession_shouldAbandonAllChildrenSessions()427     public void abandon_stagedParentSession_shouldAbandonAllChildrenSessions()
428             throws Exception {
429         final int parentSessionId = Install.multi(TestApp.A1, TestApp.B1)
430                 .setStaged().createSession();
431         final int[] childSessionIds = getChildSessionIds(parentSessionId);
432         final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
433         registerSessionCallbacks(
434                 new AbandonSessionCallBack(countDownLatch, childSessionIds));
435 
436         InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
437         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
438 
439         // The child session is cleaned up asynchronously after abandoning the parent session.
440         PollingCheck.check("The result should be an empty list.",
441                 PREVENT_RACE_CONDITION_TIMEOUT_SECONDS,
442                 () -> getAllChildSessions(childSessionIds).isEmpty());
443     }
444 
445     @Test
abandon_nonStagedParentSession_shouldAbandonAllChildrenSessions()446     public void abandon_nonStagedParentSession_shouldAbandonAllChildrenSessions()
447             throws Exception {
448         final int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).createSession();
449         final int[] childSessionIds = getChildSessionIds(parentSessionId);
450         final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
451         registerSessionCallbacks(
452                 new AbandonSessionCallBack(countDownLatch, childSessionIds));
453 
454         InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
455         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
456 
457         // The child session is cleaned up asynchronously after abandoning the parent session.
458         PollingCheck.check("The result should be empty list",
459                 PREVENT_RACE_CONDITION_TIMEOUT_SECONDS,
460                 () -> getAllChildSessions(childSessionIds).isEmpty());
461     }
462 
463     @Test
abandon_stagedParentSession_openedChildSession_getNamesShouldReturnEmptyList()464     public void abandon_stagedParentSession_openedChildSession_getNamesShouldReturnEmptyList()
465             throws Exception {
466         final int parentSessionId = Install.multi(TestApp.A1).setStaged().createSession();
467         final int[] childSessionIds = getChildSessionIds(parentSessionId);
468         final int firstChildSession = childSessionIds[0];
469         final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
470         final PackageInstaller.Session childSession = openSession(firstChildSession);
471         registerSessionCallbacks(
472                 new AbandonSessionCallBack(countDownLatch, childSessionIds));
473 
474         InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
475         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
476 
477         // TODO(b/171774482): the inconsistent behavior between staged and non-staged child session
478         // The child session is cleaned up asynchronously after abandoning the parent session.
479         PollingCheck.check("The result should be empty list",
480                 PREVENT_RACE_CONDITION_TIMEOUT_SECONDS, () -> {
481                     final String[] names;
482                     try {
483                         names = childSession.getNames();
484                     } catch (IOException e) {
485                         return false;
486                     }
487                     return names != null && names.length == 0;
488                 });
489     }
490 
491     @Test
abandon_nonStagedParentSession_openedChildSession_canNotGetNames()492     public void abandon_nonStagedParentSession_openedChildSession_canNotGetNames()
493             throws Exception {
494         final int parentSessionId = Install.multi(TestApp.A1).createSession();
495         final int[] childSessionIds = getChildSessionIds(parentSessionId);
496         final int firstChildSession = childSessionIds[0];
497         final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
498         final PackageInstaller.Session childSession = openSession(firstChildSession);
499         registerSessionCallbacks(
500                 new AbandonSessionCallBack(countDownLatch, childSessionIds));
501 
502         InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
503         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
504 
505         // The child session is cleaned up asynchronously after abandoning the parent session.
506         PollingCheck.check("getNames should get the security exception",
507                 PREVENT_RACE_CONDITION_TIMEOUT_SECONDS, () -> {
508                     try {
509                         childSession.getNames();
510                     } catch (SecurityException e) {
511                         if (e.getMessage().contains("getNames")) {
512                             return true;
513                         }
514                     } catch (IOException e) {
515                         return false;
516                     }
517                     return false;
518                 });
519     }
520 
521     @Test
abandon_stagedParentSession_openChildSessionForWriting_shouldFail()522     public void abandon_stagedParentSession_openChildSessionForWriting_shouldFail()
523             throws Exception {
524         final int parentSessionId = Install.multi(TestApp.A1).setStaged().createSession();
525         final int[] childSessionIds = getChildSessionIds(parentSessionId);
526         final int firstChildSession = childSessionIds[0];
527         final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
528         registerSessionCallbacks(
529                 new AbandonSessionCallBack(countDownLatch, childSessionIds));
530         final OutputStream outputStream = openSessionForWrite(firstChildSession,
531                 mTestName.getMethodName());
532 
533         InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
534         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
535 
536         assertThrows(instanceOf(IOException.class,
537                 hasMessageThat(containsString("write failed"))),
538                 () -> outputStream.write(PLACE_HOLDER_STRING_BYTES));
539     }
540 
541     @Test
abandon_nonStagedParentSession_openChildSessionForWriting_shouldFail()542     public void abandon_nonStagedParentSession_openChildSessionForWriting_shouldFail()
543             throws Exception {
544         final int parentSessionId = Install.multi(TestApp.A1).createSession();
545         final int[] childSessionIds = getChildSessionIds(parentSessionId);
546         final int firstChildSession = childSessionIds[0];
547         final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
548         registerSessionCallbacks(
549                 new AbandonSessionCallBack(countDownLatch, childSessionIds));
550         final OutputStream outputStream =
551                 openSessionForWrite(firstChildSession, mTestName.getMethodName());
552 
553         InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
554         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
555 
556         assertThrows(instanceOf(IOException.class,
557                 hasMessageThat(containsString("write failed"))),
558                 () -> outputStream.write(PLACE_HOLDER_STRING_BYTES));
559     }
560 
561     @Test
abandon_stagedParentSession_childSession_canNotOpenAgain()562     public void abandon_stagedParentSession_childSession_canNotOpenAgain()
563             throws Exception {
564         final int parentSessionId = Install.multi(TestApp.A1).setStaged().createSession();
565         final int[] childSessionIds = getChildSessionIds(parentSessionId);
566         final int firstChildSession = childSessionIds[0];
567         final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
568         registerSessionCallbacks(
569                 new AbandonSessionCallBack(countDownLatch, childSessionIds));
570 
571         InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
572         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
573 
574         assertThrows(instanceOf(SecurityException.class,
575                 hasMessageThat(containsString(String.valueOf(firstChildSession)))),
576                 () -> InstallUtils.getPackageInstaller().openSession(firstChildSession));
577     }
578 
579     @Test
abandon_nonStagedParentSession_childSession_canNotOpenAgain()580     public void abandon_nonStagedParentSession_childSession_canNotOpenAgain()
581             throws Exception {
582         final int parentSessionId = Install.multi(TestApp.A1).createSession();
583         final int[] childSessionIds = getChildSessionIds(parentSessionId);
584         final int firstChildSession = childSessionIds[0];
585         final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
586         registerSessionCallbacks(
587                 new AbandonSessionCallBack(countDownLatch, childSessionIds));
588 
589         InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
590         countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
591 
592         assertThrows(instanceOf(SecurityException.class,
593                 hasMessageThat(containsString(String.valueOf(firstChildSession)))),
594                 () -> InstallUtils.getPackageInstaller().openSession(firstChildSession));
595     }
596 }
597