1 /*
2  * Copyright (C) 2019 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.server.rollback;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNull;
22 import static org.junit.Assert.assertTrue;
23 import static org.mockito.ArgumentMatchers.eq;
24 import static org.mockito.Matchers.anyInt;
25 import static org.mockito.Matchers.anyString;
26 import static org.mockito.Mockito.doReturn;
27 import static org.mockito.Mockito.mock;
28 import static org.mockito.Mockito.spy;
29 import static org.mockito.Mockito.when;
30 import static org.mockito.MockitoAnnotations.initMocks;
31 
32 import android.content.pm.VersionedPackage;
33 import android.content.rollback.PackageRollbackInfo;
34 import android.content.rollback.PackageRollbackInfo.RestoreInfo;
35 import android.util.SparseIntArray;
36 
37 import com.android.server.pm.ApexManager;
38 import com.android.server.pm.Installer;
39 
40 import org.junit.Before;
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 import org.junit.runners.JUnit4;
44 import org.mockito.InOrder;
45 import org.mockito.Mock;
46 import org.mockito.Mockito;
47 
48 import java.io.File;
49 import java.util.ArrayList;
50 import java.util.List;
51 
52 @RunWith(JUnit4.class)
53 public class AppDataRollbackHelperTest {
54 
55     @Mock private ApexManager mApexManager;
56 
57     @Before
setUp()58     public void setUp() {
59         initMocks(this);
60     }
61 
62     @Test
testSnapshotAppData()63     public void testSnapshotAppData() throws Exception {
64         Installer installer = mock(Installer.class);
65         AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer, mApexManager));
66 
67         // All users are unlocked so we should snapshot data for them.
68         doReturn(true).when(helper).isUserCredentialLocked(eq(10));
69         doReturn(true).when(helper).isUserCredentialLocked(eq(11));
70         PackageRollbackInfo info = createPackageRollbackInfo("com.foo.bar");
71         helper.snapshotAppData(5, info, new int[]{10, 11});
72 
73         assertEquals(2, info.getPendingBackups().size());
74         assertEquals(10, (int) info.getPendingBackups().get(0));
75         assertEquals(11, (int) info.getPendingBackups().get(1));
76 
77         InOrder inOrder = Mockito.inOrder(installer);
78         inOrder.verify(installer).snapshotAppData(
79                 eq("com.foo.bar"), eq(10), eq(5), eq(Installer.FLAG_STORAGE_DE));
80         inOrder.verify(installer).snapshotAppData(
81                 eq("com.foo.bar"), eq(11), eq(5), eq(Installer.FLAG_STORAGE_DE));
82         inOrder.verifyNoMoreInteractions();
83 
84         // One of the users is unlocked but the other isn't
85         doReturn(false).when(helper).isUserCredentialLocked(eq(10));
86         doReturn(true).when(helper).isUserCredentialLocked(eq(11));
87         when(installer.snapshotAppData(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(true);
88 
89         PackageRollbackInfo info2 = createPackageRollbackInfo("com.foo.bar");
90         helper.snapshotAppData(7, info2, new int[]{10, 11});
91         assertEquals(1, info2.getPendingBackups().size());
92         assertEquals(11, (int) info2.getPendingBackups().get(0));
93 
94         inOrder = Mockito.inOrder(installer);
95         inOrder.verify(installer).snapshotAppData(
96                 eq("com.foo.bar"), eq(10), eq(7),
97                 eq(Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE));
98         inOrder.verify(installer).snapshotAppData(
99                 eq("com.foo.bar"), eq(11), eq(7), eq(Installer.FLAG_STORAGE_DE));
100         inOrder.verifyNoMoreInteractions();
101     }
102 
toList(int[] arr)103     private static List<Integer> toList(int[] arr) {
104         List<Integer> ret = new ArrayList<>();
105         for (int i = 0; i < arr.length; ++i) {
106             ret.add(arr[i]);
107         }
108         return ret;
109     }
110 
createPackageRollbackInfo(String packageName, final int[] installedUsers)111     private static PackageRollbackInfo createPackageRollbackInfo(String packageName,
112             final int[] installedUsers) {
113         return new PackageRollbackInfo(
114                 new VersionedPackage(packageName, 2), new VersionedPackage(packageName, 1),
115                 new ArrayList<>(), new ArrayList<>(), false, false, toList(installedUsers));
116     }
117 
createPackageRollbackInfo(String packageName)118     private static PackageRollbackInfo createPackageRollbackInfo(String packageName) {
119         return createPackageRollbackInfo(packageName, new int[] {});
120     }
121 
createRollbackForId(int rollbackId)122     private static Rollback createRollbackForId(int rollbackId) {
123         return new Rollback(rollbackId, new File("/does/not/exist"), -1, /* isStaged */ false, 0,
124                 "com.xyz", null, new SparseIntArray(0));
125 
126     }
127 
128     @Test
testRestoreAppDataSnapshot_pendingBackupForUser()129     public void testRestoreAppDataSnapshot_pendingBackupForUser() throws Exception {
130         Installer installer = mock(Installer.class);
131         AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer, mApexManager));
132 
133         PackageRollbackInfo info = createPackageRollbackInfo("com.foo");
134         List<Integer> pendingBackups = info.getPendingBackups();
135         pendingBackups.add(10);
136         pendingBackups.add(11);
137 
138         assertTrue(helper.restoreAppData(13 /* rollbackId */, info, 10 /* userId */, 1 /* appId */,
139                       "seinfo"));
140 
141         // Should only require FLAG_STORAGE_DE here because we have a pending backup that we
142         // didn't manage to execute.
143         InOrder inOrder = Mockito.inOrder(installer);
144         inOrder.verify(installer).restoreAppDataSnapshot(
145                 eq("com.foo"), eq(1) /* appId */, eq("seinfo"), eq(10) /* userId */,
146                 eq(13) /* rollbackId */, eq(Installer.FLAG_STORAGE_DE));
147         inOrder.verifyNoMoreInteractions();
148 
149         assertEquals(1, pendingBackups.size());
150         assertEquals(11, (int) pendingBackups.get(0));
151     }
152 
153     @Test
testRestoreAppDataSnapshot_availableBackupForLockedUser()154     public void testRestoreAppDataSnapshot_availableBackupForLockedUser() throws Exception {
155         Installer installer = mock(Installer.class);
156         AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer, mApexManager));
157         doReturn(true).when(helper).isUserCredentialLocked(eq(10));
158 
159         PackageRollbackInfo info = createPackageRollbackInfo("com.foo");
160 
161         assertTrue(helper.restoreAppData(73 /* rollbackId */, info, 10 /* userId */, 1 /* appId */,
162                       "seinfo"));
163 
164         InOrder inOrder = Mockito.inOrder(installer);
165         inOrder.verify(installer).restoreAppDataSnapshot(
166                 eq("com.foo"), eq(1) /* appId */, eq("seinfo"), eq(10) /* userId */,
167                 eq(73) /* rollbackId */, eq(Installer.FLAG_STORAGE_DE));
168         inOrder.verifyNoMoreInteractions();
169 
170         ArrayList<RestoreInfo> pendingRestores = info.getPendingRestores();
171         assertEquals(1, pendingRestores.size());
172         assertEquals(10, pendingRestores.get(0).userId);
173         assertEquals(1, pendingRestores.get(0).appId);
174         assertEquals("seinfo", pendingRestores.get(0).seInfo);
175     }
176 
177     @Test
testRestoreAppDataSnapshot_availableBackupForUnlockedUser()178     public void testRestoreAppDataSnapshot_availableBackupForUnlockedUser() throws Exception {
179         Installer installer = mock(Installer.class);
180         AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer, mApexManager));
181         doReturn(false).when(helper).isUserCredentialLocked(eq(10));
182 
183         PackageRollbackInfo info = createPackageRollbackInfo("com.foo");
184         assertFalse(helper.restoreAppData(101 /* rollbackId */, info, 10 /* userId */,
185                       1 /* appId */, "seinfo"));
186 
187         InOrder inOrder = Mockito.inOrder(installer);
188         inOrder.verify(installer).restoreAppDataSnapshot(
189                 eq("com.foo"), eq(1) /* appId */, eq("seinfo"), eq(10) /* userId */,
190                 eq(101) /* rollbackId */,
191                 eq(Installer.FLAG_STORAGE_DE | Installer.FLAG_STORAGE_CE));
192         inOrder.verifyNoMoreInteractions();
193 
194         ArrayList<RestoreInfo> pendingRestores = info.getPendingRestores();
195         assertEquals(0, pendingRestores.size());
196     }
197 
198     @Test
destroyAppData()199     public void destroyAppData() throws Exception {
200         Installer installer = mock(Installer.class);
201         AppDataRollbackHelper helper = new AppDataRollbackHelper(installer, mApexManager);
202 
203         PackageRollbackInfo info = createPackageRollbackInfo("com.foo.bar");
204         helper.destroyAppDataSnapshot(5 /* rollbackId */, info, 10 /* userId */);
205         helper.destroyAppDataSnapshot(5 /* rollbackId */, info, 11 /* userId */);
206 
207         InOrder inOrder = Mockito.inOrder(installer);
208         inOrder.verify(installer).destroyAppDataSnapshot(
209                 eq("com.foo.bar"), eq(10) /* userId */,
210                 eq(5) /* rollbackId */, eq(Installer.FLAG_STORAGE_DE | Installer.FLAG_STORAGE_CE));
211         inOrder.verify(installer).destroyAppDataSnapshot(
212                 eq("com.foo.bar"), eq(11) /* userId */,
213                 eq(5) /* rollbackId */, eq(Installer.FLAG_STORAGE_DE | Installer.FLAG_STORAGE_CE));
214         inOrder.verifyNoMoreInteractions();
215     }
216 
217     @Test
commitPendingBackupAndRestoreForUser()218     public void commitPendingBackupAndRestoreForUser() throws Exception {
219         Installer installer = mock(Installer.class);
220         AppDataRollbackHelper helper = new AppDataRollbackHelper(installer, mApexManager);
221 
222         when(installer.snapshotAppData(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(true);
223 
224         // This one should be backed up.
225         PackageRollbackInfo pendingBackup = createPackageRollbackInfo("com.foo", new int[]{37, 73});
226         pendingBackup.addPendingBackup(37);
227 
228         // Nothing should be done for this one.
229         PackageRollbackInfo wasRecentlyRestored = createPackageRollbackInfo("com.bar",
230                 new int[]{37, 73});
231         wasRecentlyRestored.addPendingBackup(37);
232         wasRecentlyRestored.getPendingRestores().add(
233                 new RestoreInfo(37 /* userId */, 239 /* appId*/, "seInfo"));
234 
235         // This one should be restored
236         PackageRollbackInfo pendingRestore = createPackageRollbackInfo("com.abc",
237                 new int[]{37, 73});
238         pendingRestore.getPendingRestores().add(
239                 new RestoreInfo(37 /* userId */, 57 /* appId*/, "seInfo"));
240 
241         // This one shouldn't be processed, because it hasn't pending backups/restores for userId
242         // 37.
243         PackageRollbackInfo ignoredInfo = createPackageRollbackInfo("com.bar",
244                 new int[]{3, 73});
245         wasRecentlyRestored.addPendingBackup(3);
246         wasRecentlyRestored.addPendingBackup(73);
247         wasRecentlyRestored.getPendingRestores().add(
248                 new RestoreInfo(73 /* userId */, 239 /* appId*/, "seInfo"));
249 
250         Rollback dataWithPendingBackup = createRollbackForId(101);
251         dataWithPendingBackup.info.getPackages().add(pendingBackup);
252 
253         Rollback dataWithRecentRestore = createRollbackForId(17239);
254         dataWithRecentRestore.info.getPackages().add(wasRecentlyRestored);
255 
256         Rollback dataForDifferentUser = createRollbackForId(17239);
257         dataForDifferentUser.info.getPackages().add(ignoredInfo);
258 
259         Rollback dataForRestore = createRollbackForId(17239);
260         dataForRestore.info.getPackages().add(pendingRestore);
261         dataForRestore.info.getPackages().add(wasRecentlyRestored);
262 
263         InOrder inOrder = Mockito.inOrder(installer);
264 
265         // Check that pending backup and restore for the same package mutually destroyed each other.
266         assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithRecentRestore));
267         assertEquals(-1, wasRecentlyRestored.getPendingBackups().indexOf(37));
268         assertNull(wasRecentlyRestored.getRestoreInfo(37));
269 
270         // Check that backup was performed.
271         assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithPendingBackup));
272         inOrder.verify(installer).snapshotAppData(eq("com.foo"), eq(37), eq(101),
273                 eq(Installer.FLAG_STORAGE_CE));
274         assertEquals(-1, pendingBackup.getPendingBackups().indexOf(37));
275 
276         // Check that restore was performed.
277         assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataForRestore));
278         inOrder.verify(installer).restoreAppDataSnapshot(
279                 eq("com.abc"), eq(57) /* appId */, eq("seInfo"), eq(37) /* userId */,
280                 eq(17239) /* rollbackId */, eq(Installer.FLAG_STORAGE_CE));
281         assertNull(pendingRestore.getRestoreInfo(37));
282 
283         inOrder.verifyNoMoreInteractions();
284     }
285 }
286