Files
Lawnchair/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
T
Brian Isganitis 9cbc478574 Split LauncherPrefs into abs class / impl.
Splitting allows easily making a fake that doesn't have to override the implementation details of LauncherPrefs. The fake should not deal with SharedPreferences.

LauncherPrefs could be an interface, but then the companion object has more limitations. The solution there is to have a dedicated object class, e.g. `LauncherPrefItems`. I went with an abs class to make the refactor simpler.

Flag: EXEMPT refactor
Bug: 369641781
Test: go/testedequals
Change-Id: I97a2d495d3b5fa892fa53a11fb3f7a7dfb98515b
2024-10-09 18:55:51 -04:00

418 lines
17 KiB
Java

/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.provider;
import static android.os.Process.myUserHandle;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS;
import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.backup.BackupManager;
import android.appwidget.AppWidgetHost;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.LongSparseArray;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LauncherModelHelper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.util.Arrays;
import java.util.stream.IntStream;
/**
* Tests for {@link RestoreDbTask}
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RestoreDbTaskTest {
private static final int PER_USER_RANGE = 200000;
private final UserHandle mWorkUser = UserHandle.getUserHandleForUid(PER_USER_RANGE);
private LauncherModelHelper mModelHelper;
private Context mContext;
private RestoreDbTask mTask;
private ModelDbController mMockController;
private SQLiteDatabase mMockDb;
private Cursor mMockCursor;
private LauncherPrefs mPrefs;
private LauncherRestoreEventLogger mMockRestoreEventLogger;
@Before
public void setup() {
mModelHelper = new LauncherModelHelper();
mContext = mModelHelper.sandboxContext;
mTask = new RestoreDbTask();
mMockController = Mockito.mock(ModelDbController.class);
mMockDb = mock(SQLiteDatabase.class);
mMockCursor = mock(Cursor.class);
mPrefs = LauncherPrefs.get(mContext);
mMockRestoreEventLogger = mock(LauncherRestoreEventLogger.class);
}
@After
public void teardown() {
mModelHelper.destroy();
LauncherPrefs.get(mContext).removeSync(RESTORE_DEVICE);
}
@Test
public void testGetProfileId() throws Exception {
SQLiteDatabase db = new MyModelDbController(23).getDb();
assertEquals(23, new RestoreDbTask().getDefaultProfileId(db));
}
@Test
public void testMigrateProfileId() throws Exception {
SQLiteDatabase db = new MyModelDbController(42).getDb();
// Add some mock data
for (int i = 0; i < 5; i++) {
ContentValues values = new ContentValues();
values.put(Favorites._ID, i);
values.put(Favorites.TITLE, "item " + i);
db.insert(Favorites.TABLE_NAME, null, values);
}
// Verify item add
assertEquals(5, getCount(db, "select * from favorites where profileId = 42"));
new RestoreDbTask().migrateProfileId(db, 42, 33);
// verify data migrated
assertEquals(0, getCount(db, "select * from favorites where profileId = 42"));
assertEquals(5, getCount(db, "select * from favorites where profileId = 33"));
}
@Test
public void testChangeDefaultColumn() throws Exception {
SQLiteDatabase db = new MyModelDbController(42).getDb();
// Add some mock data
for (int i = 0; i < 5; i++) {
ContentValues values = new ContentValues();
values.put(Favorites._ID, i);
values.put(Favorites.TITLE, "item " + i);
db.insert(Favorites.TABLE_NAME, null, values);
}
// Verify default column is 42
assertEquals(5, getCount(db, "select * from favorites where profileId = 42"));
new RestoreDbTask().changeDefaultColumn(db, 33);
// Verify default value changed
ContentValues values = new ContentValues();
values.put(Favorites._ID, 100);
values.put(Favorites.TITLE, "item 100");
db.insert(Favorites.TABLE_NAME, null, values);
assertEquals(1, getCount(db, "select * from favorites where profileId = 33"));
}
@Test
public void testSanitizeDB_bothProfiles() throws Exception {
UserHandle myUser = myUserHandle();
long myProfileId = mContext.getSystemService(UserManager.class)
.getSerialNumberForUser(myUser);
long myProfileId_old = myProfileId + 1;
long workProfileId = myProfileId + 2;
long workProfileId_old = myProfileId + 3;
MyModelDbController controller = new MyModelDbController(myProfileId);
SQLiteDatabase db = controller.getDb();
BackupManager bm = spy(new BackupManager(mContext));
doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old));
doReturn(mWorkUser).when(bm).getUserForAncestralSerialNumber(eq(workProfileId_old));
controller.users.put(workProfileId, mWorkUser);
addIconsBulk(controller, 10, 1, myProfileId_old);
addIconsBulk(controller, 6, 2, workProfileId_old);
assertEquals(10, getItemCountForProfile(db, myProfileId_old));
assertEquals(6, getItemCountForProfile(db, workProfileId_old));
mTask.sanitizeDB(mContext, controller, controller.getDb(), bm, mMockRestoreEventLogger);
// All the data has been migrated to the new user ids
assertEquals(0, getItemCountForProfile(db, myProfileId_old));
assertEquals(0, getItemCountForProfile(db, workProfileId_old));
assertEquals(10, getItemCountForProfile(db, myProfileId));
assertEquals(6, getItemCountForProfile(db, workProfileId));
}
@Test
public void testSanitizeDB_workItemsRemoved() throws Exception {
UserHandle myUser = myUserHandle();
long myProfileId = mContext.getSystemService(UserManager.class)
.getSerialNumberForUser(myUser);
long myProfileId_old = myProfileId + 1;
long workProfileId_old = myProfileId + 3;
MyModelDbController controller = new MyModelDbController(myProfileId);
SQLiteDatabase db = controller.getDb();
BackupManager bm = spy(new BackupManager(mContext));
doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old));
// Work profile is not migrated
doReturn(null).when(bm).getUserForAncestralSerialNumber(eq(workProfileId_old));
addIconsBulk(controller, 10, 1, myProfileId_old);
addIconsBulk(controller, 6, 2, workProfileId_old);
assertEquals(10, getItemCountForProfile(db, myProfileId_old));
assertEquals(6, getItemCountForProfile(db, workProfileId_old));
mTask.sanitizeDB(mContext, controller, controller.getDb(), bm, mMockRestoreEventLogger);
// All the data has been migrated to the new user ids
assertEquals(0, getItemCountForProfile(db, myProfileId_old));
assertEquals(0, getItemCountForProfile(db, workProfileId_old));
assertEquals(10, getItemCountForProfile(db, myProfileId));
assertEquals(10, getCount(db, "select * from favorites"));
}
@Test
public void givenLauncherPrefsHasNoIds_whenRestoreAppWidgetIdsIfExists_thenIdsAreRemoved() {
// When
mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
// Then
assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
}
@Test
public void givenNoPendingRestore_WhenRestoreAppWidgetIds_ThenRemoveNewWidgetIds() {
// Given
AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID);
int[] expectedOldIds = generateOldWidgetIds(expectedHost);
int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds);
when(mMockController.getDb()).thenReturn(mMockDb);
mPrefs.remove(RESTORE_DEVICE);
// When
setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
// Then
assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
// b/343530737
verifyNoMoreInteractions(mMockController);
}
@Test
public void givenRestoreWithNonExistingWidgets_WhenRestoreAppWidgetIds_ThenRemoveNewIds() {
// Given
AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID);
int[] expectedOldIds = generateOldWidgetIds(expectedHost);
int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds);
when(mMockController.getDb()).thenReturn(mMockDb);
when(mMockDb.query(any(), any(), any(), any(), any(), any(), any())).thenReturn(
mMockCursor);
when(mMockCursor.moveToFirst()).thenReturn(false);
RestoreDbTask.setPending(mContext);
// When
setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
// Then
assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
verify(mMockController, times(expectedOldIds.length)).update(any(), any(), any(), any());
}
@Test
public void givenRestore_WhenRestoreAppWidgetIds_ThenAddNewIds() {
// Given
AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID);
int[] expectedOldIds = generateOldWidgetIds(expectedHost);
int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds);
int[] allExpectedIds = IntStream.concat(
Arrays.stream(expectedOldIds),
Arrays.stream(expectedNewIds)
).toArray();
when(mMockController.getDb()).thenReturn(mMockDb);
when(mMockDb.query(any(), any(), any(), any(), any(), any(), any()))
.thenReturn(mMockCursor);
when(mMockCursor.moveToFirst()).thenReturn(true);
when(mMockCursor.getColumnNames()).thenReturn(new String[] {});
when(mMockCursor.isAfterLast()).thenReturn(true);
RestoreDbTask.setPending(mContext);
// When
setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
// Then
assertThat(expectedHost.getAppWidgetIds()).isEqualTo(allExpectedIds);
assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
verify(mMockController, times(expectedOldIds.length)).update(any(), any(), any(), any());
}
private void addIconsBulk(MyModelDbController controller,
int count, int screen, long profileId) {
int columns = LauncherAppState.getIDP(mContext).numColumns;
String packageName = getInstrumentation().getContext().getPackageName();
for (int i = 0; i < count; i++) {
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites._ID, controller.generateNewItemId());
values.put(LauncherSettings.Favorites.CONTAINER, CONTAINER_DESKTOP);
values.put(LauncherSettings.Favorites.SCREEN, screen);
values.put(LauncherSettings.Favorites.CELLX, i % columns);
values.put(LauncherSettings.Favorites.CELLY, i / columns);
values.put(LauncherSettings.Favorites.SPANX, 1);
values.put(LauncherSettings.Favorites.SPANY, 1);
values.put(LauncherSettings.Favorites.PROFILE_ID, profileId);
values.put(LauncherSettings.Favorites.ITEM_TYPE, ITEM_TYPE_APPLICATION);
values.put(LauncherSettings.Favorites.INTENT,
new Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0));
controller.insert(TABLE_NAME, values);
}
}
@Test
public void testRemoveScreenIdGaps_firstScreenEmpty() {
runRemoveScreenIdGapsTest(
new int[]{1, 2, 5, 6, 6, 7, 9, 9},
new int[]{1, 2, 3, 4, 4, 5, 6, 6});
}
@Test
public void testRemoveScreenIdGaps_firstScreenOccupied() {
runRemoveScreenIdGapsTest(
new int[]{0, 2, 5, 6, 6, 7, 9, 9},
new int[]{0, 1, 2, 3, 3, 4, 5, 5});
}
@Test
public void testRemoveScreenIdGaps_noGap() {
runRemoveScreenIdGapsTest(
new int[]{0, 1, 1, 2, 3, 3, 4, 5},
new int[]{0, 1, 1, 2, 3, 3, 4, 5});
}
private void runRemoveScreenIdGapsTest(int[] screenIds, int[] expectedScreenIds) {
SQLiteDatabase db = new MyModelDbController(42).getDb();
// Add some mock data
for (int i = 0; i < screenIds.length; i++) {
ContentValues values = new ContentValues();
values.put(Favorites._ID, i);
values.put(Favorites.SCREEN, screenIds[i]);
values.put(Favorites.CONTAINER, CONTAINER_DESKTOP);
db.insert(Favorites.TABLE_NAME, null, values);
}
// Verify items are added
assertEquals(screenIds.length,
getCount(db, "select * from favorites where container = -100"));
new RestoreDbTask().removeScreenIdGaps(db);
// verify screenId gaps removed
int[] resultScreenIds = new int[screenIds.length];
try (Cursor c = db.rawQuery(
"select screen from favorites where container = -100 order by screen", null)) {
int i = 0;
while (c.moveToNext()) {
resultScreenIds[i++] = c.getInt(0);
}
}
assertArrayEquals(expectedScreenIds, resultScreenIds);
}
public int getItemCountForProfile(SQLiteDatabase db, long profileId) {
return getCount(db, "select * from favorites where profileId = " + profileId);
}
private int getCount(SQLiteDatabase db, String sql) {
try (Cursor c = db.rawQuery(sql, null)) {
return c.getCount();
}
}
private int[] generateOldWidgetIds(AppWidgetHost host) {
// generate some widget ids in case there are none
host.allocateAppWidgetId();
host.allocateAppWidgetId();
return host.getAppWidgetIds();
}
private int[] generateNewWidgetIds(AppWidgetHost host, int[] oldWidgetIds) {
// map as many new ids as old ids
return Arrays.stream(oldWidgetIds)
.map(id -> host.allocateAppWidgetId()).toArray();
}
private class MyModelDbController extends ModelDbController {
public final LongSparseArray<UserHandle> users = new LongSparseArray<>();
MyModelDbController(long profileId) {
super(mContext);
users.put(profileId, myUserHandle());
}
@Override
public long getSerialNumberForUser(UserHandle user) {
int index = users.indexOfValue(user);
return index >= 0 ? users.keyAt(index) : -1;
}
}
private void setRestoredAppWidgetIds(Context context, int[] oldIds, int[] newIds) {
LauncherPrefs.get(context).putSync(
OLD_APP_WIDGET_IDS.to(IntArray.wrap(oldIds).toConcatString()),
APP_WIDGET_IDS.to(IntArray.wrap(newIds).toConcatString()));
}
}