修改README

This commit is contained in:
yuchuangu85 committed 2017-05-15 14:56:57 +08:00
1 parent 96a8a5df5d
commit e243fa08a7
20 files changed
+2315 -1

No files matched your search

+2 -1
View File
@@ -58,8 +58,9 @@ workspace类中的screenScrolled方法中添加:
...
// add:mx---特效----------end包含的代码----
// add:mx---特效----------end----
中包含的代码。
#### 5.预览图
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2015 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.config;
/**
* Defines a set of flags used to control various launcher behaviors
*/
public final class FeatureFlags {
private FeatureFlags() {}
// Custom flags go below this
public static boolean LAUNCHER3_DISABLE_ICON_NORMALIZATION = false;
// As opposed to the new spring-loaded workspace.
public static boolean LAUNCHER3_LEGACY_WORKSPACE_DND = false;
public static boolean LAUNCHER3_LEGACY_FOLDER_ICON = false;
public static boolean LAUNCHER3_USE_SYSTEM_DRAG_DRIVER = true;
public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false;
public static boolean LAUNCHER3_ALL_APPS_PULL_UP = true;
// Feature flag to enable moving the QSB on the 0th screen of the workspace.
public static final boolean QSB_ON_FIRST_SCREEN = true;
// When enabled the all-apps icon is not added to the hotseat.
public static final boolean NO_ALL_APPS_ICON = true;
// When enabled fling down gesture on the first workspace triggers search.
public static final boolean PULLDOWN_SEARCH = false;
// When enabled the status bar may show dark icons based on the top of the wallpaper.
public static final boolean LIGHT_STATUS_BAR = false;
}
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2013 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.config;
public class ProviderConfig {
public static final String AUTHORITY = "com.android.launcher3.settings".intern();
public static boolean IS_DOGFOOD_BUILD = true;
}
@@ -0,0 +1,343 @@
package com.android.launcher3;
import android.annotation.TargetApi;
import android.appwidget.AppWidgetHost;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Build;
import android.os.Bundle;
import android.support.test.uiautomator.UiSelector;
import android.test.suitebuilder.annotation.LargeTest;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.ui.LauncherInstrumentationTestCase;
import com.android.launcher3.util.ManagedProfileHeuristic;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetHostViewLoader;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Tests for bind widget flow.
*
* Note running these tests will clear the workspace on the device.
*/
@LargeTest
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class BindWidgetTest extends LauncherInstrumentationTestCase {
private ContentResolver mResolver;
private AppWidgetManagerCompat mWidgetManager;
// Objects created during test, which should be cleaned up in the end.
private Cursor mCursor;
// App install session id.
private int mSessionId = -1;
@Override
protected void setUp() throws Exception {
super.setUp();
mResolver = mTargetContext.getContentResolver();
mWidgetManager = AppWidgetManagerCompat.getInstance(mTargetContext);
grantWidgetPermission();
// Clear all existing data
LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
if (mCursor != null) {
mCursor.close();
}
if (mSessionId > -1) {
mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
}
}
public void testBindNormalWidget_withConfig() {
LauncherAppWidgetProviderInfo info = findWidgetProvider(true);
LauncherAppWidgetInfo item = createWidgetInfo(info, true);
setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label);
}
public void testBindNormalWidget_withoutConfig() {
LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
LauncherAppWidgetInfo item = createWidgetInfo(info, true);
setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label);
}
public void testUnboundWidget_removed() throws Exception {
LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
LauncherAppWidgetInfo item = createWidgetInfo(info, false);
item.appWidgetId = -33;
// Since there is no widget to verify, just wait until the workspace is ready.
setupAndVerifyContents(item, Workspace.class, null);
waitUntilLoaderIdle();
// Item deleted from db
mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
null, null, null, null, null);
assertEquals(0, mCursor.getCount());
// The view does not exist
assertFalse(mDevice.findObject(new UiSelector().description(info.label)).exists());
}
public void testPendingWidget_autoRestored() {
// A non-restored widget with no config screen gets restored automatically.
LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
// Do not bind the widget
LauncherAppWidgetInfo item = createWidgetInfo(info, false);
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label);
}
public void testPendingWidget_withConfigScreen() throws Exception {
// A non-restored widget with config screen get bound and shows a 'Click to setup' UI.
LauncherAppWidgetProviderInfo info = findWidgetProvider(true);
// Do not bind the widget
LauncherAppWidgetInfo item = createWidgetInfo(info, false);
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
setupAndVerifyContents(item, PendingAppWidgetHostView.class, null);
waitUntilLoaderIdle();
// Item deleted from db
mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
null, null, null, null, null);
mCursor.moveToNext();
// Widget has a valid Id now.
assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
& LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
assertNotNull(mWidgetManager.getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
LauncherSettings.Favorites.APPWIDGET_ID))));
}
public void testPendingWidget_notRestored_removed() throws Exception {
LauncherAppWidgetInfo item = getInvalidWidgetInfo();
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
| LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
setupAndVerifyContents(item, Workspace.class, null);
// The view does not exist
assertFalse(mDevice.findObject(
new UiSelector().className(PendingAppWidgetHostView.class)).exists());
waitUntilLoaderIdle();
// Item deleted from db
mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
null, null, null, null, null);
assertEquals(0, mCursor.getCount());
}
public void testPendingWidget_notRestored_brokenInstall() throws Exception {
// A widget which is was being installed once, even if its not being
// installed at the moment is not removed.
LauncherAppWidgetInfo item = getInvalidWidgetInfo();
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
| LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
| LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
setupAndVerifyContents(item, PendingAppWidgetHostView.class, null);
// Verify item still exists in db
waitUntilLoaderIdle();
mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
null, null, null, null, null);
assertEquals(1, mCursor.getCount());
// Widget still has an invalid id.
mCursor.moveToNext();
assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
& LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
}
public void testPendingWidget_notRestored_activeInstall() throws Exception {
// A widget which is being installed is not removed
LauncherAppWidgetInfo item = getInvalidWidgetInfo();
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
| LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
// Create an active installer session
SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(item.providerName.getPackageName());
PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
mSessionId = installer.createSession(params);
setupAndVerifyContents(item, PendingAppWidgetHostView.class, null);
// Verify item still exists in db
waitUntilLoaderIdle();
mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
null, null, null, null, null);
assertEquals(1, mCursor.getCount());
// Widget still has an invalid id.
mCursor.moveToNext();
assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
& LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
}
/**
* Adds {@param item} on the homescreen on the 0th screen at 0,0, and verifies that the
* widget class is displayed on the homescreen.
* @param widgetClass the View class which is displayed on the homescreen
* @param desc the content description of the view or null.
*/
private void setupAndVerifyContents(
LauncherAppWidgetInfo item, Class<?> widgetClass, String desc) {
long screenId = Workspace.FIRST_SCREEN_ID;
// Update the screen id counter for the provider.
LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
if (screenId > Workspace.FIRST_SCREEN_ID) {
screenId = Workspace.FIRST_SCREEN_ID;
}
ContentValues v = new ContentValues();
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, 0);
mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
// Insert the item
v = new ContentValues();
item.id = LauncherSettings.Settings.call(
mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
.getLong(LauncherSettings.Settings.EXTRA_VALUE);
item.screenId = screenId;
item.onAddToDatabase(mTargetContext, v);
v.put(LauncherSettings.Favorites._ID, item.id);
mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, v);
// Reset loader
try {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
LauncherClings.markFirstRunClingDismissed(mTargetContext);
ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
LauncherAppState.getInstance().getModel().resetLoadedState(true, true);
}
});
} catch (Throwable t) {
throw new IllegalArgumentException(t);
}
// Launch the home activity
startLauncher();
// Verify UI
UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
.className(widgetClass);
if (desc != null) {
selector = selector.description(desc);
}
assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
}
/**
* Creates a LauncherAppWidgetInfo corresponding to {@param info}
* @param bindWidget if true the info is bound and a valid widgetId is assigned to
* the LauncherAppWidgetInfo
*/
private LauncherAppWidgetInfo createWidgetInfo(
LauncherAppWidgetProviderInfo info, boolean bindWidget) {
LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(
LauncherAppWidgetInfo.NO_ID, info.provider);
item.spanX = info.minSpanX;
item.spanY = info.minSpanY;
item.minSpanX = info.minSpanX;
item.minSpanY = info.minSpanY;
item.user = mWidgetManager.getUser(info);
item.cellX = 0;
item.cellY = 1;
item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
if (bindWidget) {
PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(mTargetContext, info);
pendingInfo.spanX = item.spanX;
pendingInfo.spanY = item.spanY;
pendingInfo.minSpanX = item.minSpanX;
pendingInfo.minSpanY = item.minSpanY;
Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(mTargetContext, pendingInfo);
AppWidgetHost host = new AppWidgetHost(mTargetContext, Launcher.APPWIDGET_HOST_ID);
int widgetId = host.allocateAppWidgetId();
if (!mWidgetManager.bindAppWidgetIdIfAllowed(widgetId, info, options)) {
host.deleteAppWidgetId(widgetId);
throw new IllegalArgumentException("Unable to bind widget id");
}
item.appWidgetId = widgetId;
}
return item;
}
/**
* Returns a LauncherAppWidgetInfo with package name which is not present on the device
*/
private LauncherAppWidgetInfo getInvalidWidgetInfo() {
String invalidPackage = "com.invalidpackage";
int count = 0;
String pkg = invalidPackage;
Set<String> activePackage = getOnUiThread(new Callable<Set<String>>() {
@Override
public Set<String> call() throws Exception {
return PackageInstallerCompat.getInstance(mTargetContext)
.updateAndGetActiveSessionCache().keySet();
}
});
while(true) {
try {
mTargetContext.getPackageManager().getPackageInfo(
pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
} catch (Exception e) {
if (!activePackage.contains(pkg)) {
break;
}
}
pkg = invalidPackage + count;
count ++;
}
LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(10,
new ComponentName(pkg, "com.test.widgetprovider"));
item.spanX = 2;
item.spanY = 2;
item.minSpanX = 2;
item.minSpanY = 2;
item.cellX = 0;
item.cellY = 1;
item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
return item;
}
/**
* Blocks the current thread until all the jobs in the main worker thread are complete.
*/
private void waitUntilLoaderIdle() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
LauncherModel.sWorker.post(new Runnable() {
@Override
public void run() {
latch.countDown();
}
});
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}
@@ -0,0 +1,116 @@
/*
* Copyright (C) 2015 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;
import android.graphics.PointF;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import java.util.ArrayList;
/**
* Tests the {@link DeviceProfile} and {@link InvariantDeviceProfile}.
*/
@SmallTest
public class InvariantDeviceProfileTest extends AndroidTestCase {
private static final String TAG = "DeviceProfileTest";
private static final boolean DEBUG = false;
private InvariantDeviceProfile mInvariantProfile;
private ArrayList<InvariantDeviceProfile> mPredefinedDeviceProfiles;
@Override
protected void setUp() throws Exception {
super.setUp();
mInvariantProfile = new InvariantDeviceProfile(getContext());
mPredefinedDeviceProfiles = mInvariantProfile.getPredefinedDeviceProfiles(getContext());
}
@Override
protected void tearDown() throws Exception {
// Nothing to tear down as this class only tests static methods.
}
public void testFindClosestDeviceProfile2() {
for (InvariantDeviceProfile idf: mPredefinedDeviceProfiles) {
ArrayList<InvariantDeviceProfile> predefinedProfilesCopy =
new ArrayList<>(mPredefinedDeviceProfiles);
ArrayList<InvariantDeviceProfile> closestProfiles =
mInvariantProfile.findClosestDeviceProfiles(
idf.minWidthDps, idf.minHeightDps, predefinedProfilesCopy
);
assertTrue(closestProfiles.get(0).equals(idf));
}
}
/**
* Used to print out how the invDistWeightedInterpolate works between device profiles to
* tweak the two constants that control how the interpolation curve is shaped.
*/
public void testInvInterpolation() {
InvariantDeviceProfile p1 = mPredefinedDeviceProfiles.get(7); // e.g., Large Phone
InvariantDeviceProfile p2 = mPredefinedDeviceProfiles.get(8); // e.g., Nexus 7
ArrayList<PointF> pts = createInterpolatedPoints(
new PointF(p1.minWidthDps, p1.minHeightDps),
new PointF(p2.minWidthDps, p2.minHeightDps),
20f);
for (int i = 0; i < pts.size(); i++) {
ArrayList<InvariantDeviceProfile> closestProfiles =
mInvariantProfile.findClosestDeviceProfiles(
pts.get(i).x, pts.get(i).y, mPredefinedDeviceProfiles);
InvariantDeviceProfile result =
mInvariantProfile.invDistWeightedInterpolate(
pts.get(i).x, pts.get(i).y, closestProfiles);
if (DEBUG) {
Log.d(TAG, String.format("width x height = (%f, %f)] iconSize = %f",
pts.get(i).x, pts.get(i).y, result.iconSize));
}
}
}
private ArrayList<PointF> createInterpolatedPoints(PointF a, PointF b, float numPts) {
ArrayList<PointF> result = new ArrayList<PointF>();
result.add(a);
for (float i = 1; i < numPts; i = i + 1.0f) {
result.add(new PointF((b.x * i + a.x * (numPts - i)) / numPts,
(b.y * i + a.y * (numPts - i)) / numPts));
}
result.add(b);
return result;
}
/**
* Ensures that system calls (e.g., WindowManager, DisplayMetrics) that require contexts are
* properly working to generate minimum width and height of the display.
*/
public void test_hammerhead() {
if (!android.os.Build.DEVICE.equals("hammerhead")) {
return;
}
assertEquals(4, mInvariantProfile.numRows);
assertEquals(4, mInvariantProfile.numColumns);
assertEquals(5, mInvariantProfile.numHotseatIcons);
}
// Add more tests for other devices, however, running them once on a single device is enough
// for verifying that for a platform version, the WindowManager and DisplayMetrics is
// working as intended.
}
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2016 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.allapps;
import android.content.ComponentName;
import android.test.InstrumentationTestCase;
import com.android.launcher3.AppInfo;
import java.util.ArrayList;
import java.util.List;
/**
* Unit tests for {@link DefaultAppSearchAlgorithm}
*/
public class DefaultAppSearchAlgorithmTest extends InstrumentationTestCase {
private List<AppInfo> mAppsList;
private DefaultAppSearchAlgorithm mAlgorithm;
@Override
protected void setUp() throws Exception {
super.setUp();
mAppsList = new ArrayList<>();
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
mAlgorithm = new DefaultAppSearchAlgorithm(mAppsList);
}
});
}
public void testMatches() {
assertTrue(mAlgorithm.matches(getInfo("white cow"), "cow"));
assertTrue(mAlgorithm.matches(getInfo("whiteCow"), "cow"));
assertTrue(mAlgorithm.matches(getInfo("whiteCOW"), "cow"));
assertTrue(mAlgorithm.matches(getInfo("whitecowCOW"), "cow"));
assertTrue(mAlgorithm.matches(getInfo("white2cow"), "cow"));
assertFalse(mAlgorithm.matches(getInfo("whitecow"), "cow"));
assertFalse(mAlgorithm.matches(getInfo("whitEcow"), "cow"));
assertTrue(mAlgorithm.matches(getInfo("whitecowCow"), "cow"));
assertTrue(mAlgorithm.matches(getInfo("whitecow cow"), "cow"));
assertFalse(mAlgorithm.matches(getInfo("whitecowcow"), "cow"));
assertFalse(mAlgorithm.matches(getInfo("whit ecowcow"), "cow"));
assertTrue(mAlgorithm.matches(getInfo("cats&dogs"), "dog"));
assertTrue(mAlgorithm.matches(getInfo("cats&Dogs"), "dog"));
assertTrue(mAlgorithm.matches(getInfo("cats&Dogs"), "&"));
assertTrue(mAlgorithm.matches(getInfo("2+43"), "43"));
assertFalse(mAlgorithm.matches(getInfo("2+43"), "3"));
assertTrue(mAlgorithm.matches(getInfo("Q"), "q"));
assertTrue(mAlgorithm.matches(getInfo(" Q"), "q"));
// match lower case words
assertTrue(mAlgorithm.matches(getInfo("elephant"), "e"));
}
private AppInfo getInfo(String title) {
AppInfo info = new AppInfo();
info.title = title;
info.componentName = new ComponentName("Test", title);
return info;
}
}
@@ -0,0 +1,77 @@
package com.android.launcher3.logging;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Calendar;
/**
* Tests for {@link FileLog}
*/
@SmallTest
public class FileLogTest extends AndroidTestCase {
private File mTempDir;
@Override
protected void setUp() throws Exception {
super.setUp();
int count = 0;
do {
mTempDir = new File(getContext().getCacheDir(), "log-test-" + (count++));
} while(!mTempDir.mkdir());
FileLog.setDir(mTempDir);
}
@Override
protected void tearDown() throws Exception {
// Clear existing logs
new File(mTempDir, "log-0").delete();
new File(mTempDir, "log-1").delete();
mTempDir.delete();
super.tearDown();
}
public void testPrintLog() throws Exception {
FileLog.print("Testing", "hoolalala");
StringWriter writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("hoolalala"));
FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("abracadabra"));
// Exception is also printed
assertTrue(writer.toString().contains("cat! cat!"));
// Old logs still present after flush
assertTrue(writer.toString().contains("hoolalala"));
}
public void testOldFileTruncated() throws Exception {
FileLog.print("Testing", "hoolalala");
StringWriter writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("hoolalala"));
Calendar threeDaysAgo = Calendar.getInstance();
threeDaysAgo.add(Calendar.HOUR, -72);
new File(mTempDir, "log-0").setLastModified(threeDaysAgo.getTimeInMillis());
new File(mTempDir, "log-1").setLastModified(threeDaysAgo.getTimeInMillis());
FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("abracadabra"));
// Exception is also printed
assertTrue(writer.toString().contains("cat! cat!"));
// Old logs have been truncated
assertFalse(writer.toString().contains("hoolalala"));
}
}
@@ -0,0 +1,442 @@
package com.android.launcher3.model;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Point;
import android.test.ProviderTestCase2;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
import com.android.launcher3.util.TestLauncherProvider;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
/**
* Unit tests for {@link GridSizeMigrationTask}
*/
@MediumTest
public class GridSizeMigrationTaskTest extends ProviderTestCase2<TestLauncherProvider> {
private static final long DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
private static final long HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
private static final int APPLICATION = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
private static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
private static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
private static final String VALID_INTENT =
new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0);
private HashSet<String> mValidPackages;
private InvariantDeviceProfile mIdp;
public GridSizeMigrationTaskTest() {
super(TestLauncherProvider.class, ProviderConfig.AUTHORITY);
}
@Override
protected void setUp() throws Exception {
super.setUp();
mValidPackages = new HashSet<>();
mValidPackages.add(TEST_PACKAGE);
mIdp = new InvariantDeviceProfile();
}
public void testHotseatMigration_apps_dropped() throws Exception {
long[] hotseatItems = {
addItem(APPLICATION, 0, HOTSEAT, 0, 0),
addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
-1,
addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
addItem(APPLICATION, 4, HOTSEAT, 0, 0),
};
mIdp.numHotseatIcons = 3;
new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, 5, 3)
.migrateHotseat();
if (FeatureFlags.NO_ALL_APPS_ICON) {
// First item is dropped as it has the least weight.
verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
} else {
// First & last items are dropped as they have the least weight.
verifyHotseat(hotseatItems[1], -1, hotseatItems[3]);
}
}
public void testHotseatMigration_shortcuts_dropped() throws Exception {
long[] hotseatItems = {
addItem(APPLICATION, 0, HOTSEAT, 0, 0),
addItem(30, 1, HOTSEAT, 0, 0),
-1,
addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
addItem(10, 4, HOTSEAT, 0, 0),
};
mIdp.numHotseatIcons = 3;
new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, 5, 3)
.migrateHotseat();
if (FeatureFlags.NO_ALL_APPS_ICON) {
// First item is dropped as it has the least weight.
verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
} else {
// First & third items are dropped as they have the least weight.
verifyHotseat(hotseatItems[1], -1, hotseatItems[4]);
}
}
private void verifyHotseat(long... sortedIds) {
int screenId = 0;
int total = 0;
for (long id : sortedIds) {
Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites._ID},
"container=-101 and screen=" + screenId, null, null, null);
if (id == -1) {
assertEquals(0, c.getCount());
} else {
assertEquals(1, c.getCount());
c.moveToNext();
assertEquals(id, c.getLong(0));
total ++;
}
c.close();
screenId++;
}
// Verify that not other entry exist in the DB.
Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites._ID},
"container=-101", null, null, null);
assertEquals(total, c.getCount());
c.close();
}
public void testWorkspace_empty_row_column_removed() throws Exception {
long[][][] ids = createGrid(new int[][][]{{
{ 0, 0, -1, 1},
{ 3, 1, -1, 4},
{ -1, -1, -1, -1},
{ 5, 2, -1, 6},
}});
new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Column 2 and row 2 got removed.
verifyWorkspace(new long[][][] {{
{ids[0][0][0], ids[0][0][1], ids[0][0][3]},
{ids[0][1][0], ids[0][1][1], ids[0][1][3]},
{ids[0][3][0], ids[0][3][1], ids[0][3][3]},
}});
}
public void testWorkspace_new_screen_created() throws Exception {
long[][][] ids = createGrid(new int[][][]{{
{ 0, 0, 0, 1},
{ 3, 1, 0, 4},
{ -1, -1, -1, -1},
{ 5, 2, -1, 6},
}});
new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column get moved to new screen
verifyWorkspace(new long[][][] {{
{ids[0][0][0], ids[0][0][1], ids[0][0][3]},
{ids[0][1][0], ids[0][1][1], ids[0][1][3]},
{ids[0][3][0], ids[0][3][1], ids[0][3][3]},
}, {
{ids[0][0][2], ids[0][1][2], -1},
}});
}
public void testWorkspace_items_merged_in_next_screen() throws Exception {
long[][][] ids = createGrid(new int[][][]{{
{ 0, 0, 0, 1},
{ 3, 1, 0, 4},
{ -1, -1, -1, -1},
{ 5, 2, -1, 6},
},{
{ 0, 0, -1, 1},
{ 3, 1, -1, 4},
}});
new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on the 3rd
// row of the second screen
verifyWorkspace(new long[][][] {{
{ids[0][0][0], ids[0][0][1], ids[0][0][3]},
{ids[0][1][0], ids[0][1][1], ids[0][1][3]},
{ids[0][3][0], ids[0][3][1], ids[0][3][3]},
}, {
{ids[1][0][0], ids[1][0][1], ids[1][0][3]},
{ids[1][1][0], ids[1][1][1], ids[1][1][3]},
{ids[0][0][2], ids[0][1][2], -1},
}});
}
public void testWorkspace_items_not_merged_in_next_screen() throws Exception {
// First screen has 2 items that need to be moved, but second screen has only one
// empty space after migration (top-left corner)
long[][][] ids = createGrid(new int[][][]{{
{ 0, 0, 0, 1},
{ 3, 1, 0, 4},
{ -1, -1, -1, -1},
{ 5, 2, -1, 6},
},{
{ -1, 0, -1, 1},
{ 3, 1, -1, 4},
{ -1, -1, -1, -1},
{ 5, 2, -1, 6},
}});
new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
verifyWorkspace(new long[][][] {{
{ids[0][0][0], ids[0][0][1], ids[0][0][3]},
{ids[0][1][0], ids[0][1][1], ids[0][1][3]},
{ids[0][3][0], ids[0][3][1], ids[0][3][3]},
}, {
{ -1, ids[1][0][1], ids[1][0][3]},
{ids[1][1][0], ids[1][1][1], ids[1][1][3]},
{ids[1][3][0], ids[1][3][1], ids[1][3][3]},
}, {
{ids[0][0][2], ids[0][1][2], -1},
}});
}
public void testWorkspace_first_row_blocked() throws Exception {
// The first screen has one item on the 4th column which needs moving, as the first row
// will be kept empty.
long[][][] ids = createGrid(new int[][][]{{
{ -1, -1, -1, -1},
{ 3, 1, 7, 0},
{ 8, 7, 7, -1},
{ 5, 2, 7, -1},
}}, 0);
new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
new Point(4, 4), new Point(3, 4)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
verifyWorkspace(new long[][][] {{
{ -1, -1, -1},
{ids[0][1][0], ids[0][1][1], ids[0][1][2]},
{ids[0][2][0], ids[0][2][1], ids[0][2][2]},
{ids[0][3][0], ids[0][3][1], ids[0][3][2]},
}, {
{ids[0][1][3]},
}});
}
public void testWorkspace_items_moved_to_empty_first_row() throws Exception {
// Items will get moved to the next screen to keep the first screen empty.
long[][][] ids = createGrid(new int[][][]{{
{ -1, -1, -1, -1},
{ 0, 1, 0, 0},
{ 8, 7, 7, -1},
{ 5, 6, 7, -1},
}}, 0);
new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
verifyWorkspace(new long[][][] {{
{ -1, -1, -1},
{ids[0][2][0], ids[0][2][1], ids[0][2][2]},
{ids[0][3][0], ids[0][3][1], ids[0][3][2]},
}, {
{ids[0][1][1], ids[0][1][0], ids[0][1][2]},
{ids[0][1][3]},
}});
}
private long[][][] createGrid(int[][][] typeArray) throws Exception {
return createGrid(typeArray, 1);
}
/**
* Initializes the DB with dummy elements to represent the provided grid structure.
* @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
* type definitions. The first dimension represents the screens and the next
* two represent the workspace grid.
* @return the same grid representation where each entry is the corresponding item id.
*/
private long[][][] createGrid(int[][][] typeArray, long startScreen) throws Exception {
LauncherSettings.Settings.call(getMockContentResolver(),
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
long[][][] ids = new long[typeArray.length][][];
for (int i = 0; i < typeArray.length; i++) {
// Add screen to DB
long screenId = startScreen + i;
// Keep the screen id counter up to date
LauncherSettings.Settings.call(getMockContentResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
ContentValues v = new ContentValues();
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
getMockContentResolver().insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
ids[i] = new long[typeArray[i].length][];
for (int y = 0; y < typeArray[i].length; y++) {
ids[i][y] = new long[typeArray[i][y].length];
for (int x = 0; x < typeArray[i][y].length; x++) {
if (typeArray[i][y][x] < 0) {
// Empty cell
ids[i][y][x] = -1;
} else {
ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y);
}
}
}
}
return ids;
}
/**
* Verifies that the workspace items are arranged in the provided order.
* @param ids A 3d array where the first dimension represents the screen, and the rest two
* represent the workspace grid.
*/
private void verifyWorkspace(long[][][] ids) {
ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(getMockContext());
assertEquals(ids.length, allScreens.size());
int total = 0;
for (int i = 0; i < ids.length; i++) {
long screenId = allScreens.get(i);
for (int y = 0; y < ids[i].length; y++) {
for (int x = 0; x < ids[i][y].length; x++) {
long id = ids[i][y][x];
Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites._ID},
"container=-100 and screen=" + screenId +
" and cellX=" + x + " and cellY=" + y, null, null, null);
if (id == -1) {
assertEquals(0, c.getCount());
} else {
assertEquals(1, c.getCount());
c.moveToNext();
assertEquals(String.format("Failed to verify item at %d %d, %d", i, y, x),
id, c.getLong(0));
total++;
}
c.close();
}
}
}
// Verify that not other entry exist in the DB.
Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites._ID},
"container=-100", null, null, null);
assertEquals(total, c.getCount());
c.close();
}
/**
* Adds a dummy item in the DB.
* @param type {@link #APPLICATION} or {@link #SHORTCUT} or >= 2 for
* folder (where the type represents the number of items in the folder).
*/
private long addItem(int type, long screen, long container, int x, int y) throws Exception {
long id = LauncherSettings.Settings.call(getMockContentResolver(),
LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
.getLong(LauncherSettings.Settings.EXTRA_VALUE);
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites._ID, id);
values.put(LauncherSettings.Favorites.CONTAINER, container);
values.put(LauncherSettings.Favorites.SCREEN, screen);
values.put(LauncherSettings.Favorites.CELLX, x);
values.put(LauncherSettings.Favorites.CELLY, y);
values.put(LauncherSettings.Favorites.SPANX, 1);
values.put(LauncherSettings.Favorites.SPANY, 1);
if (type == APPLICATION || type == SHORTCUT) {
values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
values.put(LauncherSettings.Favorites.INTENT, VALID_INTENT);
} else {
values.put(LauncherSettings.Favorites.ITEM_TYPE,
LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
// Add folder items.
for (int i = 0; i < type; i++) {
addItem(APPLICATION, 0, id, 0, 0);
}
}
getMockContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
return id;
}
public void testMultiStepMigration_small_to_large() throws Exception {
MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier();
verifier.migrate(new Point(3, 3), new Point(5, 5));
verifier.assertCompleted();
}
public void testMultiStepMigration_large_to_small() throws Exception {
MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
5, 5, 4, 4,
4, 4, 3, 4
);
verifier.migrate(new Point(5, 5), new Point(3, 4));
verifier.assertCompleted();
}
public void testMultiStepMigration_zig_zag() throws Exception {
MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
5, 7, 4, 7,
4, 7, 3, 7
);
verifier.migrate(new Point(5, 5), new Point(3, 7));
verifier.assertCompleted();
}
private static class MultiStepMigrationTaskVerifier extends MultiStepMigrationTask {
private final LinkedList<Point> mPoints;
public MultiStepMigrationTaskVerifier(int... points) {
super(null, null);
mPoints = new LinkedList<>();
for (int i = 0; i < points.length; i += 2) {
mPoints.add(new Point(points[i], points[i + 1]));
}
}
@Override
protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
assertEquals(sourceSize, mPoints.poll());
assertEquals(nextSize, mPoints.poll());
return false;
}
public void assertCompleted() {
assertTrue(mPoints.isEmpty());
}
}
}
@@ -0,0 +1,74 @@
package com.android.launcher3.provider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
/**
* Tests for {@link RestoreDbTask}
*/
@MediumTest
public class RestoreDbTaskTest extends AndroidTestCase {
public void testGetProfileId() throws Exception {
SQLiteDatabase db = new MyDatabaseHelper(23).getWritableDatabase();
assertEquals(23, new RestoreDbTask().getDefaultProfileId(db));
}
public void testMigrateProfileId() throws Exception {
SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
// Add some dummy data
for (int i = 0; i < 5; i++) {
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites._ID, i);
values.put(LauncherSettings.Favorites.TITLE, "item " + i);
db.insert(LauncherSettings.Favorites.TABLE_NAME, null, values);
}
// Verify item add
assertEquals(5, getCount(db, "select * from favorites where profileId = 42"));
new RestoreDbTask().migrateProfileId(db, 33);
// verify data migrated
assertEquals(0, getCount(db, "select * from favorites where profileId = 42"));
assertEquals(5, getCount(db, "select * from favorites where profileId = 33"));
// Verify default value changed
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites._ID, 100);
values.put(LauncherSettings.Favorites.TITLE, "item 100");
db.insert(LauncherSettings.Favorites.TABLE_NAME, null, values);
assertEquals(6, getCount(db, "select * from favorites where profileId = 33"));
}
private int getCount(SQLiteDatabase db, String sql) {
Cursor c = db.rawQuery(sql, null);
try {
return c.getCount();
} finally {
c.getCount();
}
}
private class MyDatabaseHelper extends LauncherProvider.DatabaseHelper {
private final long mProfileId;
public MyDatabaseHelper(long profileId) {
super(getContext(), null, null);
mProfileId = profileId;
}
@Override
public long getDefaultUserSerial() {
return mProfileId;
}
protected void onEmptyDbCreated() { }
}
}
@@ -0,0 +1,137 @@
/*
* Copyright (C) 2016 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.shortcuts;
import android.content.pm.ShortcutInfo;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests the sorting and filtering of shortcuts in {@link ShortcutFilter}.
*/
@RunWith(AndroidJUnit4.class)
public class ShortcutFilterTest {
@Test
public void testSortAndFilterShortcuts() {
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 3), 0, 3);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 0), ShortcutFilter.MAX_SHORTCUTS, 0);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 5), 0, ShortcutFilter.MAX_SHORTCUTS);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 3),
ShortcutFilter.MAX_SHORTCUTS - ShortcutFilter.NUM_DYNAMIC, ShortcutFilter.NUM_DYNAMIC);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 5),
ShortcutFilter.MAX_SHORTCUTS - ShortcutFilter.NUM_DYNAMIC, ShortcutFilter.NUM_DYNAMIC);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 1), ShortcutFilter.MAX_SHORTCUTS - 1, 1);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(1, 5), 1, ShortcutFilter.MAX_SHORTCUTS - 1);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 3),
ShortcutFilter.MAX_SHORTCUTS - ShortcutFilter.NUM_DYNAMIC, ShortcutFilter.NUM_DYNAMIC);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 5),
ShortcutFilter.MAX_SHORTCUTS - ShortcutFilter.NUM_DYNAMIC, ShortcutFilter.NUM_DYNAMIC);
}
private void filterShortcutsAndAssertNumStaticAndDynamic(
List<ShortcutInfoCompat> shortcuts, int expectedStatic, int expectedDynamic) {
Collections.shuffle(shortcuts);
List<ShortcutInfoCompat> filteredShortcuts = ShortcutFilter.sortAndFilterShortcuts(shortcuts);
assertIsSorted(filteredShortcuts);
int numStatic = 0;
int numDynamic = 0;
for (ShortcutInfoCompat shortcut : filteredShortcuts) {
if (shortcut.isDeclaredInManifest()) {
numStatic++;
}
if (shortcut.isDynamic()) {
numDynamic++;
}
}
assertEquals(expectedStatic, numStatic);
assertEquals(expectedDynamic, numDynamic);
}
private void assertIsSorted(List<ShortcutInfoCompat> shortcuts) {
int lastStaticRank = -1;
int lastDynamicRank = -1;
boolean hasSeenDynamic = false;
for (ShortcutInfoCompat shortcut : shortcuts) {
int rank = shortcut.getRank();
if (shortcut.isDeclaredInManifest()) {
assertFalse("Static shortcuts should come before all dynamic shortcuts.",
hasSeenDynamic);
assertTrue(rank > lastStaticRank);
lastStaticRank = rank;
}
if (shortcut.isDynamic()) {
hasSeenDynamic = true;
assertTrue(rank > lastDynamicRank);
lastDynamicRank = rank;
}
}
}
private List<ShortcutInfoCompat> createShortcutsList(int numStatic, int numDynamic) {
List<ShortcutInfoCompat> shortcuts = new ArrayList<>();
for (int i = 0; i < numStatic; i++) {
shortcuts.add(new Shortcut(true, i));
}
for (int i = 0; i < numDynamic; i++) {
shortcuts.add(new Shortcut(false, i));
}
return shortcuts;
}
private class Shortcut extends ShortcutInfoCompat {
private boolean mIsStatic;
private int mRank;
public Shortcut(ShortcutInfo shortcutInfo) {
super(shortcutInfo);
}
public Shortcut(boolean isStatic, int rank) {
this(null);
mIsStatic = isStatic;
mRank = rank;
}
@Override
public boolean isDeclaredInManifest() {
return mIsStatic;
}
@Override
public boolean isDynamic() {
return !mIsStatic;
}
@Override
public int getRank() {
return mRank;
}
}
}
@@ -0,0 +1,63 @@
package com.android.launcher3.ui;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject2;
import android.test.suitebuilder.annotation.LargeTest;
import android.view.View;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Workspace.ItemOperator;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
import com.android.launcher3.widget.WidgetCell;
/**
* Test to add widget from widget tray
*/
@LargeTest
public class AddWidgetTest extends LauncherInstrumentationTestCase {
private LauncherAppWidgetProviderInfo widgetInfo;
@Override
protected void setUp() throws Exception {
super.setUp();
widgetInfo = findWidgetProvider(false /* hasConfigureScreen */);
}
public void testDragIcon_portrait() throws Throwable {
lockRotation(true);
performTest();
}
public void testDragIcon_landscape() throws Throwable {
lockRotation(false);
performTest();
}
private void performTest() throws Throwable {
clearHomescreen();
Launcher launcher = startLauncher();
// Open widget tray and wait for load complete.
final UiObject2 widgetContainer = openWidgetsTray();
assertTrue(Wait.atMost(Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT));
// Drag widget to homescreen
UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
.hasDescendant(By.text(widgetInfo.getLabel(mTargetContext.getPackageManager()))));
dragToWorkspace(widget);
assertNotNull(launcher.getWorkspace().getFirstMatch(new ItemOperator() {
@Override
public boolean evaluate(ItemInfo info, View view) {
return info instanceof LauncherAppWidgetInfo &&
((LauncherAppWidgetInfo) info).providerName.equals(widgetInfo.provider);
}
}));
}
}
@@ -0,0 +1,52 @@
package com.android.launcher3.ui;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.test.suitebuilder.annotation.LargeTest;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
/**
* Test for verifying apps is launched from all-apps
*/
@LargeTest
public class AllAppsAppLaunchTest extends LauncherInstrumentationTestCase {
private LauncherActivityInfoCompat mSettingsApp;
@Override
protected void setUp() throws Exception {
super.setUp();
mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
.getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0);
}
public void testAppLauncher_portrait() throws Exception {
lockRotation(true);
performTest();
}
public void testAppLauncher_landscape() throws Exception {
lockRotation(false);
performTest();
}
private void performTest() throws Exception {
startLauncher();
// Open all apps and wait for load complete
final UiObject2 appsContainer = openAllApps();
assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
// Open settings app and verify app launched
scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString())).click();
assertTrue(mDevice.wait(Until.hasObject(By.pkg(
mSettingsApp.getComponentName().getPackageName()).depth(0)), DEFAULT_UI_TIMEOUT));
}
}
@@ -0,0 +1,57 @@
package com.android.launcher3.ui;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.test.suitebuilder.annotation.LargeTest;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
/**
* Test for dragging an icon from all-apps to homescreen.
*/
@LargeTest
public class AllAppsIconToHomeTest extends LauncherInstrumentationTestCase {
private LauncherActivityInfoCompat mSettingsApp;
@Override
protected void setUp() throws Exception {
super.setUp();
mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
.getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0);
}
public void testDragIcon_portrait() throws Throwable {
lockRotation(true);
performTest();
}
public void testDragIcon_landscape() throws Throwable {
lockRotation(false);
performTest();
}
private void performTest() throws Throwable {
clearHomescreen();
startLauncher();
// Open all apps and wait for load complete.
final UiObject2 appsContainer = openAllApps();
assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
// Drag icon to homescreen.
UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString()));
dragToWorkspace(icon);
// Verify that the icon works on homescreen.
mDevice.findObject(By.text(mSettingsApp.getLabel().toString())).click();
assertTrue(mDevice.wait(Until.hasObject(By.pkg(
mSettingsApp.getComponentName().getPackageName()).depth(0)), DEFAULT_UI_TIMEOUT));
}
}
@@ -0,0 +1,273 @@
package com.android.launcher3.ui;
import android.app.SearchManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.test.InstrumentationTestCase;
import android.view.MotionEvent;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherClings;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ManagedProfileHeuristic;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
/**
* Base class for all instrumentation tests providing various utility methods.
*/
public class LauncherInstrumentationTestCase extends InstrumentationTestCase {
public static final long DEFAULT_UI_TIMEOUT = 3000;
protected UiDevice mDevice;
protected Context mTargetContext;
protected String mTargetPackage;
@Override
protected void setUp() throws Exception {
super.setUp();
mDevice = UiDevice.getInstance(getInstrumentation());
mTargetContext = getInstrumentation().getTargetContext();
mTargetPackage = mTargetContext.getPackageName();
}
protected void lockRotation(boolean naturalOrientation) throws RemoteException {
Utilities.getPrefs(mTargetContext)
.edit()
.putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, !naturalOrientation)
.commit();
if (naturalOrientation) {
mDevice.setOrientationNatural();
} else {
mDevice.setOrientationRight();
}
}
/**
* Starts the launcher activity in the target package and returns the Launcher instance.
*/
protected Launcher startLauncher() {
Intent homeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setPackage(mTargetPackage)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return (Launcher) getInstrumentation().startActivitySync(homeIntent);
}
/**
* Grants the launcher permission to bind widgets.
*/
protected void grantWidgetPermission() throws IOException {
// Check bind widget permission
if (mTargetContext.getPackageManager().checkPermission(
mTargetPackage, android.Manifest.permission.BIND_APPWIDGET)
!= PackageManager.PERMISSION_GRANTED) {
ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand(
"appwidget grantbind --package " + mTargetPackage);
// Read the input stream fully.
FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
while (fis.read() != -1);
fis.close();
}
}
/**
* Opens all apps and returns the recycler view
*/
protected UiObject2 openAllApps() {
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
// clicking on the page indicator brings up all apps tray on non tablets.
findViewById(R.id.page_indicator).click();
} else {
mDevice.wait(Until.findObject(
By.desc(mTargetContext.getString(R.string.all_apps_button_label))),
DEFAULT_UI_TIMEOUT).click();
}
return findViewById(R.id.apps_list_view);
}
/**
* Opens widget tray and returns the recycler view.
*/
protected UiObject2 openWidgetsTray() {
mDevice.pressMenu(); // Enter overview mode.
mDevice.wait(Until.findObject(
By.text(mTargetContext.getString(R.string.widget_button_text)
.toUpperCase(Locale.getDefault()))), DEFAULT_UI_TIMEOUT).click();
return findViewById(R.id.widgets_list_view);
}
/**
* Scrolls the {@param container} until it finds an object matching {@param condition}.
* @return the matching object.
*/
protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) {
do {
UiObject2 widget = container.findObject(condition);
if (widget != null) {
return widget;
}
} while (container.scroll(Direction.DOWN, 1f));
return container.findObject(condition);
}
/**
* Drags an icon to the center of homescreen.
*/
protected void dragToWorkspace(UiObject2 icon) {
Point center = icon.getVisibleCenter();
// Action Down
sendPointer(MotionEvent.ACTION_DOWN, center);
// Wait until "Remove/Delete target is visible
assertNotNull(findViewById(R.id.delete_target_text));
Point moveLocation = findViewById(R.id.drag_layer).getVisibleCenter();
// Move to center
while(!moveLocation.equals(center)) {
center.x = getNextMoveValue(moveLocation.x, center.x);
center.y = getNextMoveValue(moveLocation.y, center.y);
sendPointer(MotionEvent.ACTION_MOVE, center);
}
sendPointer(MotionEvent.ACTION_UP, center);
// Wait until remove target is gone.
mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT);
}
private int getNextMoveValue(int targetValue, int oldValue) {
if (targetValue - oldValue > 10) {
return oldValue + 10;
} else if (targetValue - oldValue < -10) {
return oldValue - 10;
} else {
return targetValue;
}
}
private void sendPointer(int action, Point point) {
MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), action, point.x, point.y, 0);
getInstrumentation().sendPointerSync(event);
event.recycle();
}
/**
* Removes all icons from homescreen and hotseat.
*/
public void clearHomescreen() throws Throwable {
LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
LauncherClings.markFirstRunClingDismissed(mTargetContext);
ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
runTestOnUiThread(new Runnable() {
@Override
public void run() {
// Reset the loader state
LauncherAppState.getInstance().getModel().resetLoadedState(true, true);
}
});
}
/**
* Runs the callback on the UI thread and returns the result.
*/
protected <T> T getOnUiThread(final Callable<T> callback) {
final AtomicReference<T> result = new AtomicReference<>(null);
try {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
try {
result.set(callback.call());
} catch (Exception e) { }
}
});
} catch (Throwable t) { }
return result.get();
}
/**
* Finds a widget provider which can fit on the home screen.
* @param hasConfigureScreen if true, a provider with a config screen is returned.
*/
protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) {
LauncherAppWidgetProviderInfo info = getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
@Override
public LauncherAppWidgetProviderInfo call() throws Exception {
InvariantDeviceProfile idv =
LauncherAppState.getInstance().getInvariantDeviceProfile();
ComponentName searchComponent = ((SearchManager) mTargetContext
.getSystemService(Context.SEARCH_SERVICE)).getGlobalSearchActivity();
String searchPackage = searchComponent == null
? null : searchComponent.getPackageName();
for (AppWidgetProviderInfo info :
AppWidgetManagerCompat.getInstance(mTargetContext).getAllProviders()) {
if ((info.configure != null) ^ hasConfigureScreen) {
continue;
}
// Exclude the widgets in search package, as Launcher already binds them in
// QSB, so they can cause conflicts.
if (info.provider.getPackageName().equals(searchPackage)) {
continue;
}
LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo
.fromProviderInfo(mTargetContext, info);
if (widgetInfo.minSpanX >= idv.numColumns
|| widgetInfo.minSpanY >= idv.numRows) {
continue;
}
return widgetInfo;
}
return null;
}
});
if (info == null) {
throw new IllegalArgumentException("No valid widget provider");
}
return info;
}
protected UiObject2 findViewById(int id) {
return mDevice.wait(Until.findObject(getSelectorForId(id)), DEFAULT_UI_TIMEOUT);
}
protected BySelector getSelectorForId(int id) {
String name = mTargetContext.getResources().getResourceEntryName(id);
return By.res(mTargetPackage, name);
}
}
@@ -0,0 +1,76 @@
package com.android.launcher3.ui;
import android.content.SharedPreferences;
import android.graphics.Rect;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiSelector;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.launcher3.Utilities;
/**
* Test for auto rotate preference.
*/
@MediumTest
public class RotationPreferenceTest extends LauncherInstrumentationTestCase {
private SharedPreferences mPrefs;
private boolean mOriginalRotationValue;
@Override
protected void setUp() throws Exception {
super.setUp();
mDevice = UiDevice.getInstance(getInstrumentation());
mTargetContext = getInstrumentation().getTargetContext();
mTargetPackage = mTargetContext.getPackageName();
mPrefs = Utilities.getPrefs(mTargetContext);
mOriginalRotationValue = mPrefs.getBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, false);
}
@Override
protected void tearDown() throws Exception {
setRotationEnabled(mOriginalRotationValue);
super.tearDown();
}
public void testRotation_disabled() throws Exception {
if (mTargetContext.getResources().getBoolean(com.android.launcher3.R.bool.allow_rotation)) {
// This is a tablet. The test is only valid to mobile devices.
return;
}
setRotationEnabled(false);
mDevice.setOrientationRight();
startLauncher();
Rect hotseat = getHotseatBounds();
assertTrue(hotseat.width() > hotseat.height());
}
public void testRotation_enabled() throws Exception {
if (mTargetContext.getResources().getBoolean(com.android.launcher3.R.bool.allow_rotation)) {
// This is a tablet. The test is only valid to mobile devices.
return;
}
setRotationEnabled(true);
mDevice.setOrientationRight();
startLauncher();
Rect hotseat = getHotseatBounds();
assertTrue(hotseat.width() < hotseat.height());
}
private void setRotationEnabled(boolean enabled) {
mPrefs.edit().putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, enabled).commit();
}
private Rect getHotseatBounds() throws Exception {
UiObject hotseat = mDevice.findObject(
new UiSelector().resourceId(mTargetPackage + ":id/hotseat"));
hotseat.waitForExists(6000);
return hotseat.getVisibleBounds();
}
}
@@ -0,0 +1,54 @@
package com.android.launcher3.util;
import android.support.test.uiautomator.UiObject2;
import com.android.launcher3.MainThreadExecutor;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class Condition {
public abstract boolean isTrue() throws Throwable;
/**
* Converts the condition to be run on UI thread.
*/
public static Condition runOnUiThread(final Condition condition) {
final MainThreadExecutor executor = new MainThreadExecutor();
return new Condition() {
@Override
public boolean isTrue() throws Throwable {
final AtomicBoolean value = new AtomicBoolean(false);
final Throwable[] exceptions = new Throwable[1];
final CountDownLatch latch = new CountDownLatch(1);
executor.execute(new Runnable() {
@Override
public void run() {
try {
value.set(condition.isTrue());
} catch (Throwable e) {
exceptions[0] = e;
}
}
});
latch.await(1, TimeUnit.SECONDS);
if (exceptions[0] != null) {
throw exceptions[0];
}
return value.get();
}
};
}
public static Condition minChildCount(final UiObject2 obj, final int childCount) {
return new Condition() {
@Override
public boolean isTrue() {
return obj.getChildCount() >= childCount;
}
};
}
}
@@ -0,0 +1,254 @@
/*
* Copyright (C) 2015 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.util;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
/**
* Tests the {@link FocusLogic} class that handles key event based focus handling.
*/
@SmallTest
public final class FocusLogicTest extends AndroidTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
// Nothing to set up as this class only tests static methods.
}
@Override
protected void tearDown() throws Exception {
// Nothing to tear down as this class only tests static methods.
}
public void testShouldConsume() {
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_LEFT));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_RIGHT));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_UP));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_DOWN));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_HOME));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DEL));
assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_FORWARD_DEL));
}
public void testCreateSparseMatrix() {
// Either, 1) create a helper method to generate/instantiate all possible cell layout that
// may get created in real world to test this method. OR 2) Move all the matrix
// management routine to celllayout and write tests for them.
}
public void testMoveFromBottomRightToBottomLeft() {
int[][] map = transpose(new int[][] {
{-1, 0, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1},
{100, 1, -1, -1, -1, -1},
});
int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 100, 1, 2, false);
assertEquals(1, i);
}
public void testMoveFromBottomRightToTopLeft() {
int[][] map = transpose(new int[][] {
{-1, 0, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1},
{100, -1, -1, -1, -1, -1},
});
int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 100, 1, 2, false);
assertEquals(FocusLogic.NEXT_PAGE_FIRST_ITEM, i);
}
public void testMoveIntoHotseatWithEqualHotseatAndWorkspaceColumns() {
// Test going from an icon right above the All Apps button to the All Apps button.
int[][] map = transpose(new int[][] {
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, 0, -1, -1},
{ 2, 3, 1, 4, 5},
});
int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
assertEquals(1, i);
// Test going from an icon above and to the right of the All Apps
// button to an icon to the right of the All Apps button.
map = transpose(new int[][] {
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, 0, -1},
{ 2, 3, 1, 4, 5},
});
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
assertEquals(4, i);
}
public void testMoveIntoHotseatWithExtraColumnForAllApps() {
// Test going from an icon above and to the left
// of the All Apps button to the All Apps button.
int[][] map = transpose(new int[][] {
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, 0,-11, -1, -1, -1},
{-1, -1, -1, 1, 1, -1, -1},
});
int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
assertEquals(1, i);
// Test going from an icon above and to the right
// of the All Apps button to the All Apps button.
map = transpose(new int[][] {
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, 0, -1, -1},
{-1, -1, -1, 1, -1, -1, -1},
});
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
assertEquals(1, i);
// Test going from the All Apps button to an icon
// above and to the right of the All Apps button.
map = transpose(new int[][] {
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, 0, -1, -1},
{-1, -1, -1, 1, -1, -1, -1},
});
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_UP, map, 1, 1, 1, true);
assertEquals(0, i);
// Test going from an icon above and to the left of the
// All Apps button in landscape to the All Apps button.
map = transpose(new int[][] {
{ -1, -1, -1, -1, -1},
{ -1, -1, -1, 0, -1},
{-11,-11,-11,-11, 1},
{ -1, -1, -1, -1, -1},
{ -1, -1, -1, -1, -1},
});
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 0, 1, 1, true);
assertEquals(1, i);
// Test going from the All Apps button in landscape to
// an icon above and to the left of the All Apps button.
map = transpose(new int[][] {
{ -1, -1, -1, -1, -1},
{ -1, -1, -1, 0, -1},
{-11,-11,-11,-11, 1},
{ -1, -1, -1, -1, -1},
{ -1, -1, -1, -1, -1},
});
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT, map, 1, 1, 1, true);
assertEquals(0, i);
// Test that going to the hotseat always goes to the same row as the original icon.
map = transpose(new int[][]{
{ 0, 1, 2,-11, 3, 4, 5},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{-1, -1, -1,-11, -1, -1, -1},
{ 7, 8, 9, 6, 10, 11, 12},
});
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
assertEquals(7, i);
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 1, 1, 1, true);
assertEquals(8, i);
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 2, 1, 1, true);
assertEquals(9, i);
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 3, 1, 1, true);
assertEquals(10, i);
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 4, 1, 1, true);
assertEquals(11, i);
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 5, 1, 1, true);
assertEquals(12, i);
}
public void testCrossingAllAppsColumn() {
// Test crossing from left to right in portrait.
int[][] map = transpose(new int[][] {
{-1, -1,-11, -1, -1},
{-1, 0,-11, -1, -1},
{-1, -1,-11, 1, -1},
{-1, -1,-11, -1, -1},
{-1, -1, 2, -1, -1},
});
int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
assertEquals(1, i);
// Test crossing from right to left in portrait.
map = transpose(new int[][] {
{-1, -1,-11, -1, -1},
{-1, -1,-11, 0, -1},
{-1, 1,-11, -1, -1},
{-1, -1,-11, -1, -1},
{-1, -1, 2, -1, -1},
});
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
assertEquals(1, i);
// Test crossing from left to right in landscape.
map = transpose(new int[][] {
{ -1, -1, -1, -1, -1},
{ -1, -1, -1, 0, -1},
{-11,-11,-11,-11, 2},
{ -1, 1, -1, -1, -1},
{ -1, -1, -1, -1, -1},
});
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT, map, 0, 1, 1, true);
assertEquals(1, i);
// Test crossing from right to left in landscape.
map = transpose(new int[][] {
{ -1, -1, -1, -1, -1},
{ -1, 0, -1, -1, -1},
{-11,-11,-11,-11, 2},
{ -1, -1, 1, -1, -1},
{ -1, -1, -1, -1, -1},
});
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 0, 1, 1, true);
assertEquals(1, i);
// Test NOT crossing it, if the All Apps button is the only suitable candidate.
map = transpose(new int[][]{
{-1, 0, -1, -1, -1},
{-1, 1, -1, -1, -1},
{-11, -11, -11, -11, 4},
{-1, 2, -1, -1, -1},
{-1, 3, -1, -1, -1},
});
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 1, 1, 1, true);
assertEquals(4, i);
i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 2, 1, 1, true);
assertEquals(4, i);
}
/** Transposes the matrix so that we can write it in human-readable format in the tests. */
private int[][] transpose(int[][] m) {
int[][] t = new int[m[0].length][m.length];
for (int i = 0; i < m.length; i++) {
for (int j = 0; j < m[0].length; j++) {
t[j][i] = m[i][j];
}
}
return t;
}
}
@@ -0,0 +1,61 @@
package com.android.launcher3.util;
import android.test.suitebuilder.annotation.SmallTest;
import junit.framework.TestCase;
/**
* Unit tests for {@link GridOccupancy}
*/
@SmallTest
public class GridOccupancyTest extends TestCase {
public void testFindVacantCell() {
GridOccupancy grid = initGrid(4,
1, 1, 1, 0, 0,
0, 0, 1, 1, 0,
0, 0, 0, 0, 0,
1, 1, 0, 0, 0
);
int[] vacant = new int[2];
assertTrue(grid.findVacantCell(vacant, 2, 2));
assertEquals(vacant[0], 0);
assertEquals(vacant[1], 1);
assertTrue(grid.findVacantCell(vacant, 3, 2));
assertEquals(vacant[0], 2);
assertEquals(vacant[1], 2);
assertFalse(grid.findVacantCell(vacant, 3, 3));
}
public void testIsRegionVacant() {
GridOccupancy grid = initGrid(4,
1, 1, 1, 0, 0,
0, 0, 1, 1, 0,
0, 0, 0, 0, 0,
1, 1, 0, 0, 0
);
assertTrue(grid.isRegionVacant(4, 0, 1, 4));
assertTrue(grid.isRegionVacant(0, 1, 2, 2));
assertTrue(grid.isRegionVacant(2, 2, 3, 2));
assertFalse(grid.isRegionVacant(3, 0, 2, 4));
assertFalse(grid.isRegionVacant(0, 0, 2, 1));
}
private GridOccupancy initGrid(int rows, int... cells) {
int cols = cells.length / rows;
int i = 0;
GridOccupancy grid = new GridOccupancy(cols, rows);
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
grid.cells[x][y] = cells[i] != 0;
i++;
}
}
return grid;
}
}
@@ -0,0 +1,47 @@
package com.android.launcher3.util;
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import com.android.launcher3.LauncherProvider;
/**
* An extension of LauncherProvider backed up by in-memory database.
*/
public class TestLauncherProvider extends LauncherProvider {
@Override
public boolean onCreate() {
return true;
}
@Override
protected synchronized void createDbIfNotExists() {
if (mOpenHelper == null) {
mOpenHelper = new MyDatabaseHelper(getContext());
}
}
public SQLiteOpenHelper getHelper() {
createDbIfNotExists();
return mOpenHelper;
}
@Override
protected void notifyListeners() { }
private static class MyDatabaseHelper extends DatabaseHelper {
public MyDatabaseHelper(Context context) {
super(context, null, null);
initIds();
}
@Override
public long getDefaultUserSerial() {
return 0;
}
@Override
protected void onEmptyDbCreated() { }
}
}
@@ -0,0 +1,39 @@
package com.android.launcher3.util;
import android.os.SystemClock;
/**
* A utility class for waiting for a condition to be true.
*/
public class Wait {
private static final long DEFAULT_SLEEP_MS = 200;
public static boolean atMost(Condition condition, long timeout) {
return atMost(condition, timeout, DEFAULT_SLEEP_MS);
}
public static boolean atMost(Condition condition, long timeout, long sleepMillis) {
long endTime = SystemClock.uptimeMillis() + timeout;
while (SystemClock.uptimeMillis() < endTime) {
try {
if (condition.isTrue()) {
return true;
}
} catch (Throwable t) {
// Ignore
}
SystemClock.sleep(sleepMillis);
}
// Check once more before returning false.
try {
if (condition.isTrue()) {
return true;
}
} catch (Throwable t) {
// Ignore
}
return false;
}
}