mirror of
https://github.com/alesimula/Murine-launcher.git
synced 2026-10-28 18:02:18 +00:00
修改README
This commit is contained in:
1 parent
96a8a5df5d
commit
e243fa08a7
20 files changed
+2315
-1
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user