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