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.launcher3.model;
18 
19 import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
20 import static android.os.Process.myUserHandle;
21 
22 import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
23 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
24 import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
25 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
26 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
27 import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
28 import static com.android.launcher3.util.LauncherModelHelper.NO__ICON;
29 import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
30 import static com.android.launcher3.util.ReflectionHelpers.getField;
31 import static com.android.launcher3.util.ReflectionHelpers.setField;
32 
33 import static org.junit.Assert.assertEquals;
34 import static org.junit.Assert.assertTrue;
35 import static org.mockito.ArgumentMatchers.eq;
36 import static org.mockito.Mockito.doReturn;
37 import static org.mockito.Mockito.spy;
38 
39 import android.app.backup.BackupManager;
40 import android.content.pm.PackageInstaller;
41 import android.database.Cursor;
42 import android.database.sqlite.SQLiteDatabase;
43 import android.os.UserHandle;
44 import android.util.ArrayMap;
45 import android.util.LongSparseArray;
46 
47 import androidx.test.ext.junit.runners.AndroidJUnit4;
48 import androidx.test.filters.SmallTest;
49 
50 import com.android.launcher3.InvariantDeviceProfile;
51 import com.android.launcher3.pm.UserCache;
52 import com.android.launcher3.provider.RestoreDbTask;
53 import com.android.launcher3.util.LauncherModelHelper;
54 import com.android.launcher3.util.SafeCloseable;
55 
56 import org.junit.After;
57 import org.junit.Before;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 
61 /**
62  * Tests to verify backup and restore flow.
63  */
64 @SmallTest
65 @RunWith(AndroidJUnit4.class)
66 public class BackupRestoreTest {
67 
68     private static final int PER_USER_RANGE = 200000;
69 
70 
71     private long mCurrentMyProfileId;
72     private long mOldMyProfileId;
73 
74     private long mCurrentWorkProfileId;
75     private long mOldWorkProfileId;
76 
77     private BackupManager mBackupManager;
78     private LauncherModelHelper mModelHelper;
79     private SQLiteDatabase mDb;
80     private InvariantDeviceProfile mIdp;
81 
82     private UserHandle mWorkUserHandle;
83 
84     private SafeCloseable mUserChangeListener;
85 
86     @Before
setUp()87     public void setUp() {
88         mModelHelper = new LauncherModelHelper();
89 
90         mCurrentMyProfileId = mModelHelper.defaultProfileId;
91         mOldMyProfileId = mCurrentMyProfileId + 1;
92         mCurrentWorkProfileId = mOldMyProfileId + 1;
93         mOldWorkProfileId = mCurrentWorkProfileId + 1;
94 
95         mWorkUserHandle = UserHandle.getUserHandleForUid(PER_USER_RANGE);
96         mUserChangeListener = UserCache.INSTANCE.get(mModelHelper.sandboxContext)
97                 .addUserChangeListener(() -> { });
98 
99         setupUserManager();
100         setupBackupManager();
101         RestoreDbTask.setPending(mModelHelper.sandboxContext);
102         mDb = mModelHelper.provider.getDb();
103         mIdp = InvariantDeviceProfile.INSTANCE.get(mModelHelper.sandboxContext);
104 
105     }
106 
107     @After
tearDown()108     public void tearDown() {
109         mUserChangeListener.close();
110         mModelHelper.destroy();
111     }
112 
setupUserManager()113     private void setupUserManager() {
114         UserCache cache = UserCache.INSTANCE.get(mModelHelper.sandboxContext);
115         synchronized (cache) {
116             LongSparseArray<UserHandle> users = getField(cache, "mUsers");
117             users.clear();
118             users.put(mCurrentMyProfileId, myUserHandle());
119             users.put(mCurrentWorkProfileId, mWorkUserHandle);
120 
121             ArrayMap<UserHandle, Long> userMap = getField(cache, "mUserToSerialMap");
122             userMap.clear();
123             userMap.put(myUserHandle(), mCurrentMyProfileId);
124             userMap.put(mWorkUserHandle, mCurrentWorkProfileId);
125         }
126     }
127 
setupBackupManager()128     private void setupBackupManager() {
129         mBackupManager = spy(new BackupManager(mModelHelper.sandboxContext));
130         doReturn(myUserHandle()).when(mBackupManager)
131                 .getUserForAncestralSerialNumber(eq(mOldMyProfileId));
132         doReturn(mWorkUserHandle).when(mBackupManager)
133                 .getUserForAncestralSerialNumber(eq(mOldWorkProfileId));
134     }
135 
136     @Test
testOnCreateDbIfNotExists_CreatesBackup()137     public void testOnCreateDbIfNotExists_CreatesBackup() {
138         assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
139     }
140 
141     @Test
testOnRestoreSessionWithValidCondition_PerformsRestore()142     public void testOnRestoreSessionWithValidCondition_PerformsRestore() throws Exception {
143         setupBackup();
144         verifyTableIsFilled(BACKUP_TABLE_NAME, false);
145         verifyTableIsEmpty(TABLE_NAME);
146         createRestoreSession();
147         verifyTableIsFilled(TABLE_NAME, true);
148     }
149 
setupBackup()150     private void setupBackup() {
151         createTableUsingOldProfileId();
152         // setup grid for main user on first screen
153         mModelHelper.createGrid(new int[][][]{{
154                 { APP_ICON, APP_ICON, SHORTCUT, SHORTCUT},
155                 { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
156                 { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
157                 { APP_ICON, SHORTCUT, SHORTCUT, APP_ICON},
158             }}, 1, mOldMyProfileId);
159         // setup grid for work profile on second screen
160         mModelHelper.createGrid(new int[][][]{{
161                 { NO__ICON, APP_ICON, SHORTCUT, SHORTCUT},
162                 { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
163                 { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
164                 { APP_ICON, SHORTCUT, SHORTCUT, NO__ICON},
165             }}, 2, mOldWorkProfileId);
166         // simulates the creation of backup upon restore
167         new GridBackupTable(mModelHelper.sandboxContext, mDb, mIdp.numDatabaseHotseatIcons,
168                 mIdp.numColumns, mIdp.numRows).doBackup(
169                 mOldMyProfileId, GridBackupTable.OPTION_REQUIRES_SANITIZATION);
170         // reset favorites table
171         createTableUsingOldProfileId();
172     }
173 
verifyTableIsEmpty(String tableName)174     private void verifyTableIsEmpty(String tableName) {
175         assertEquals(0, getCount(mDb, "SELECT * FROM " + tableName));
176     }
177 
verifyTableIsFilled(String tableName, boolean sanitized)178     private void verifyTableIsFilled(String tableName, boolean sanitized) {
179         assertEquals(sanitized ? 12 : 13, getCount(mDb,
180                 "SELECT * FROM " + tableName + " WHERE profileId = "
181                         + (sanitized ? mCurrentMyProfileId : mOldMyProfileId)));
182         assertEquals(10, getCount(mDb, "SELECT * FROM " + tableName + " WHERE profileId = "
183                 + (sanitized ? mCurrentWorkProfileId : mOldWorkProfileId)));
184     }
185 
createTableUsingOldProfileId()186     private void createTableUsingOldProfileId() {
187         // simulates the creation of favorites table on old device
188         dropTable(mDb, TABLE_NAME);
189         addTableToDb(mDb, mOldMyProfileId, false);
190     }
191 
createRestoreSession()192     private void createRestoreSession() throws Exception {
193         final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
194                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
195         final PackageInstaller installer = mModelHelper.sandboxContext.getPackageManager()
196                 .getPackageInstaller();
197         final int sessionId = installer.createSession(params);
198         final PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);
199         setField(info, "installReason", INSTALL_REASON_DEVICE_RESTORE);
200         // TODO: (b/148410677) we should verify the following call instead
201         //  InstallSessionHelper.INSTANCE.get(getContext()).restoreDbIfApplicable(info);
202         RestoreDbTask.restoreIfPossible(mModelHelper.sandboxContext,
203                 mModelHelper.provider.getHelper(), mBackupManager);
204     }
205 
getCount(SQLiteDatabase db, String sql)206     private static int getCount(SQLiteDatabase db, String sql) {
207         try (Cursor c = db.rawQuery(sql, null)) {
208             return c.getCount();
209         }
210     }
211 }
212