Snap for 11641371 from 0ec7c1e4a8 to 24Q3-release
Change-Id: I468a7f5f952a2fd425003766731720cdb40953b8
This commit is contained in:
@@ -155,6 +155,11 @@ android_library {
|
||||
"animationlib",
|
||||
"SystemUI-statsd",
|
||||
"launcher-testing-shared",
|
||||
"androidx.lifecycle_lifecycle-common-java8",
|
||||
"androidx.lifecycle_lifecycle-extensions",
|
||||
"androidx.lifecycle_lifecycle-runtime-ktx",
|
||||
"kotlinx_coroutines_android",
|
||||
"kotlinx_coroutines",
|
||||
"com_android_launcher3_flags_lib",
|
||||
"com_android_wm_shell_flags_lib",
|
||||
"android.appwidget.flags-aconfig-java",
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
style="@style/GoOverviewActionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableStart="@drawable/ic_save_app_pair"
|
||||
android:drawableStart="@drawable/ic_save_app_pair_up_down"
|
||||
android:text="@string/action_save_app_pair"
|
||||
android:theme="@style/ThemeControlHighlightWorkspaceColor"
|
||||
android:visibility="gone" />
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
Copyright (C) 2023 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M13.329,2.305H4.242C2.751,2.305 1.542,3.514 1.542,5.005V13.005C1.542,14.496 2.751,15.705 4.242,15.705H7.875V19.011C7.875,20.502 9.084,21.711 10.575,21.711H19.662C21.153,21.711 22.362,20.502 22.362,19.011V10.011C22.362,8.52 21.153,7.311 19.662,7.311H16.029V5.005C16.029,3.514 14.821,2.305 13.329,2.305ZM14.329,7.311V5.005C14.329,4.452 13.882,4.005 13.329,4.005H4.242C3.69,4.005 3.242,4.452 3.242,5.005V13.005C3.242,13.557 3.69,14.005 4.242,14.005H7.875V10.011C7.875,8.52 9.084,7.311 10.575,7.311H14.329ZM9.575,14.005V10.011C9.575,9.611 9.81,9.266 10.15,9.106C10.285,9.037 10.438,8.999 10.6,8.999H19.687C20.239,8.999 20.687,9.447 20.687,9.999V18.999C20.687,19.399 20.452,19.744 20.113,19.904C19.977,19.972 19.824,20.011 19.662,20.011H10.575C10.023,20.011 9.575,19.563 9.575,19.011V15.705H9.6V14.005H9.575ZM15.542,11.996V14H17.588V15H15.542V16.996H14.542V15H12.464V14H14.542V11.996H15.542Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
Copyright (C) 2024 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M8.5,4.5H3.5C2.4,4.5 1.5,5.4 1.5,6.5V18.5C1.5,19.6 2.4,20.5 3.5,20.5H8.5C9.6,20.5 10.5,19.6 10.5,18.5V6.5C10.5,5.4 9.6,4.5 8.5,4.5ZM8.5,18.5H3.5V6.5H8.5V18.5ZM14.5,6.5H19.5V13.5H21.5V6.5C21.5,5.4 20.6,4.5 19.5,4.5H14.5C13.4,4.5 12.5,5.4 12.5,6.5V18.5C12.5,19.6 13.4,20.5 14.5,20.5H15.5V18.5H14.5V6.5ZM20.5,14.5V16.5H22.5V18.5H20.5V20.5H18.5V18.5H16.5V16.5H18.5V14.5H20.5Z"
|
||||
android:fillColor="#48473A"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
Copyright (C) 2024 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M18,2L6,2C4.9,2 4,2.9 4,4L4,9C4,10.1 4.9,11 6,11L18,11C19.1,11 20,10.1 20,9L20,4C20,2.9 19.1,2 18,2ZM18,9L6,9L6,4L18,4L18,9ZM18,13L6,13C4.9,13 4,13.9 4,15L4,20C4,21.1 4.9,22 6,22L13,22L13,20L6,20L6,15L18,15L18,16L20,16L20,15C20,13.9 19.1,13 18,13ZM16,17L18,17L18,19L20,19L20,21L18,21L18,23L16,23L16,21L14,21L14,19L16,19L16,17Z"
|
||||
android:fillColor="#48473A"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
@@ -23,6 +23,7 @@
|
||||
android:id="@+id/action_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/overview_actions_height"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
android:layout_marginStart="@dimen/task_menu_option_text_start_margin"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?androidprv:attr/materialColorOnSurface"
|
||||
android:focusable="false" />
|
||||
android:focusable="false"
|
||||
android:gravity="start"
|
||||
android:ellipsize="end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -21,7 +21,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.wm.shell.transition.IHomeTransitionListener;
|
||||
import com.android.wm.shell.shared.IHomeTransitionListener;
|
||||
|
||||
/**
|
||||
* Controls launcher response to home activity visibility changing.
|
||||
|
||||
@@ -32,6 +32,8 @@ import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.quickstep.util.BaseDepthController;
|
||||
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Definition for AllApps state
|
||||
*/
|
||||
@@ -39,6 +41,8 @@ public class AllAppsState extends LauncherState {
|
||||
|
||||
private static final int STATE_FLAGS =
|
||||
FLAG_WORKSPACE_INACCESSIBLE | FLAG_CLOSE_POPUPS | FLAG_HOTSEAT_INACCESSIBLE;
|
||||
private static final long BACK_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
|
||||
|
||||
|
||||
public AllAppsState(int id) {
|
||||
super(id, LAUNCHER_STATE_ALLAPPS, STATE_FLAGS);
|
||||
@@ -53,14 +57,33 @@ public class AllAppsState extends LauncherState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed(Launcher launcher) {
|
||||
public void onBackStarted(Launcher launcher) {
|
||||
// Because the back gesture can take longer time depending on when user release the finger,
|
||||
// we pass BACK_CUJ_TIMEOUT_MS as timeout to the jank monitor.
|
||||
InteractionJankMonitorWrapper.begin(launcher.getAppsView(),
|
||||
Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
|
||||
super.onBackPressed(launcher);
|
||||
Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK, BACK_CUJ_TIMEOUT_MS);
|
||||
super.onBackStarted(launcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackPressCompleted(boolean success) {
|
||||
public void onBackInvoked(Launcher launcher) {
|
||||
// In predictive back swipe, onBackInvoked() will be called after onBackStarted().
|
||||
// Because the 2nd InteractionJankMonitor.begin() will be ignore within timeout, it's safe
|
||||
// to call InteractionJankMonitorWrapper.begin here.
|
||||
InteractionJankMonitorWrapper.begin(launcher.getAppsView(),
|
||||
Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
|
||||
super.onBackInvoked(launcher);
|
||||
}
|
||||
|
||||
/** Called when predictive back swipe is cancelled. */
|
||||
@Override
|
||||
public void onBackCancelled(Launcher launcher) {
|
||||
super.onBackCancelled(launcher);
|
||||
InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackAnimationCompleted(boolean success) {
|
||||
if (success) {
|
||||
// Animation was successful.
|
||||
InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
|
||||
|
||||
@@ -60,7 +60,7 @@ public class OverviewModalTaskState extends OverviewState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed(Launcher launcher) {
|
||||
public void onBackInvoked(Launcher launcher) {
|
||||
launcher.getStateManager().goToState(LauncherState.OVERVIEW);
|
||||
}
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ public class OverviewState extends LauncherState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed(Launcher launcher) {
|
||||
public void onBackInvoked(Launcher launcher) {
|
||||
RecentsView recentsView = launcher.getOverviewPanel();
|
||||
TaskView taskView = recentsView.getRunningTaskView();
|
||||
if (taskView != null) {
|
||||
@@ -209,7 +209,7 @@ public class OverviewState extends LauncherState {
|
||||
recentsView.snapToPage(recentsView.indexOfChild(taskView));
|
||||
}
|
||||
} else {
|
||||
super.onBackPressed(launcher);
|
||||
super.onBackInvoked(launcher);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ class AllAppsActionManager(
|
||||
}
|
||||
|
||||
fun onDestroy() {
|
||||
isActionRegistered = false
|
||||
context
|
||||
.getSystemService(AccessibilityManager::class.java)
|
||||
?.unregisterSystemAction(
|
||||
|
||||
@@ -91,13 +91,13 @@ import com.android.wm.shell.draganddrop.IDragAndDrop;
|
||||
import com.android.wm.shell.onehanded.IOneHanded;
|
||||
import com.android.wm.shell.recents.IRecentTasks;
|
||||
import com.android.wm.shell.recents.IRecentTasksListener;
|
||||
import com.android.wm.shell.shared.IHomeTransitionListener;
|
||||
import com.android.wm.shell.shared.IShellTransitions;
|
||||
import com.android.wm.shell.splitscreen.ISplitScreen;
|
||||
import com.android.wm.shell.splitscreen.ISplitScreenListener;
|
||||
import com.android.wm.shell.splitscreen.ISplitSelectListener;
|
||||
import com.android.wm.shell.startingsurface.IStartingWindow;
|
||||
import com.android.wm.shell.startingsurface.IStartingWindowListener;
|
||||
import com.android.wm.shell.transition.IHomeTransitionListener;
|
||||
import com.android.wm.shell.transition.IShellTransitions;
|
||||
import com.android.wm.shell.util.GroupedRecentTaskInfo;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -136,8 +136,9 @@ public interface TaskShortcutFactory {
|
||||
class SaveAppPairSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
|
||||
private final GroupedTaskView mTaskView;
|
||||
|
||||
public SaveAppPairSystemShortcut(BaseDraggingActivity activity, GroupedTaskView taskView) {
|
||||
super(R.drawable.ic_save_app_pair, R.string.save_app_pair, activity,
|
||||
public SaveAppPairSystemShortcut(BaseDraggingActivity activity, GroupedTaskView taskView,
|
||||
int iconResId) {
|
||||
super(iconResId, R.string.save_app_pair, activity,
|
||||
taskView.getItemInfo(), taskView);
|
||||
mTaskView = taskView;
|
||||
}
|
||||
@@ -342,8 +343,12 @@ public interface TaskShortcutFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
int iconResId = deviceProfile.isLeftRightSplit
|
||||
? R.drawable.ic_save_app_pair_left_right
|
||||
: R.drawable.ic_save_app_pair_up_down;
|
||||
|
||||
return Collections.singletonList(
|
||||
new SaveAppPairSystemShortcut(activity, (GroupedTaskView) taskView));
|
||||
new SaveAppPairSystemShortcut(activity, (GroupedTaskView) taskView, iconResId));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -135,9 +135,9 @@ import com.android.wm.shell.desktopmode.IDesktopMode;
|
||||
import com.android.wm.shell.draganddrop.IDragAndDrop;
|
||||
import com.android.wm.shell.onehanded.IOneHanded;
|
||||
import com.android.wm.shell.recents.IRecentTasks;
|
||||
import com.android.wm.shell.shared.IShellTransitions;
|
||||
import com.android.wm.shell.splitscreen.ISplitScreen;
|
||||
import com.android.wm.shell.startingsurface.IStartingWindow;
|
||||
import com.android.wm.shell.transition.IShellTransitions;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
@@ -442,8 +442,10 @@ public class TouchInteractionService extends Service {
|
||||
|
||||
/** Refreshes the current overview target. */
|
||||
public void refreshOverviewTarget() {
|
||||
executeForTouchInteractionService(tis -> tis.onOverviewTargetChange(
|
||||
tis.mOverviewComponentObserver.isHomeAndOverviewSame()));
|
||||
executeForTouchInteractionService(tis -> {
|
||||
tis.mAllAppsActionManager.onDestroy();
|
||||
tis.onOverviewTargetChange(tis.mOverviewComponentObserver.isHomeAndOverviewSame());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
@@ -43,8 +42,6 @@ import com.android.quickstep.util.LayoutUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* View for showing action buttons in Overview
|
||||
@@ -297,17 +294,6 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
|
||||
int desiredVisibility = mSplitButtonHiddenFlags == 0 ? VISIBLE : GONE;
|
||||
mSplitButton.setVisibility(desiredVisibility);
|
||||
findViewById(R.id.action_split_space).setVisibility(desiredVisibility);
|
||||
|
||||
String callStack = Arrays.stream(
|
||||
Log.getStackTraceString(new Exception("thread stacktrace"))
|
||||
.split("\\n"))
|
||||
.limit(5)
|
||||
.skip(1) // Removes the line "java.lang.Exception: thread stacktrace"
|
||||
.collect(Collectors.joining("\n"));
|
||||
Log.d("b/321291049", "updateSplitButtonHiddenFlags called with flag: " + flag
|
||||
+ " enabled: " + enable
|
||||
+ " visibility: " + desiredVisibility
|
||||
+ " partial trace: \n" + callStack);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -407,7 +393,11 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
|
||||
? R.drawable.ic_split_horizontal
|
||||
: R.drawable.ic_split_vertical;
|
||||
mSplitButton.setCompoundDrawablesRelativeWithIntrinsicBounds(splitIconRes, 0, 0, 0);
|
||||
|
||||
int appPairIconRes = dp.isLeftRightSplit
|
||||
? R.drawable.ic_save_app_pair_left_right
|
||||
: R.drawable.ic_save_app_pair_up_down;
|
||||
mSaveAppPairButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_save_app_pair, 0, 0, 0);
|
||||
appPairIconRes, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,8 +135,6 @@ import java.util.stream.Stream;
|
||||
public class TaskView extends FrameLayout implements Reusable {
|
||||
|
||||
private static final String TAG = TaskView.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
public static final int FLAG_UPDATE_ICON = 1;
|
||||
public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
|
||||
public static final int FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL << 1;
|
||||
@@ -184,7 +182,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
Collections.singletonList(new Rect());
|
||||
|
||||
public static final FloatProperty<TaskView> FOCUS_TRANSITION =
|
||||
new FloatProperty<TaskView>("focusTransition") {
|
||||
new FloatProperty<>("focusTransition") {
|
||||
@Override
|
||||
public void setValue(TaskView taskView, float v) {
|
||||
taskView.setIconsAndBannersTransitionProgress(v, false /* invert */);
|
||||
@@ -197,7 +195,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
};
|
||||
|
||||
private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_X =
|
||||
new FloatProperty<TaskView>("splitSelectTranslationX") {
|
||||
new FloatProperty<>("splitSelectTranslationX") {
|
||||
@Override
|
||||
public void setValue(TaskView taskView, float v) {
|
||||
taskView.setSplitSelectTranslationX(v);
|
||||
@@ -210,7 +208,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
};
|
||||
|
||||
private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_Y =
|
||||
new FloatProperty<TaskView>("splitSelectTranslationY") {
|
||||
new FloatProperty<>("splitSelectTranslationY") {
|
||||
@Override
|
||||
public void setValue(TaskView taskView, float v) {
|
||||
taskView.setSplitSelectTranslationY(v);
|
||||
@@ -223,7 +221,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
};
|
||||
|
||||
private static final FloatProperty<TaskView> DISMISS_TRANSLATION_X =
|
||||
new FloatProperty<TaskView>("dismissTranslationX") {
|
||||
new FloatProperty<>("dismissTranslationX") {
|
||||
@Override
|
||||
public void setValue(TaskView taskView, float v) {
|
||||
taskView.setDismissTranslationX(v);
|
||||
@@ -236,7 +234,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
};
|
||||
|
||||
private static final FloatProperty<TaskView> DISMISS_TRANSLATION_Y =
|
||||
new FloatProperty<TaskView>("dismissTranslationY") {
|
||||
new FloatProperty<>("dismissTranslationY") {
|
||||
@Override
|
||||
public void setValue(TaskView taskView, float v) {
|
||||
taskView.setDismissTranslationY(v);
|
||||
@@ -249,7 +247,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
};
|
||||
|
||||
private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_X =
|
||||
new FloatProperty<TaskView>("taskOffsetTranslationX") {
|
||||
new FloatProperty<>("taskOffsetTranslationX") {
|
||||
@Override
|
||||
public void setValue(TaskView taskView, float v) {
|
||||
taskView.setTaskOffsetTranslationX(v);
|
||||
@@ -262,7 +260,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
};
|
||||
|
||||
private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_Y =
|
||||
new FloatProperty<TaskView>("taskOffsetTranslationY") {
|
||||
new FloatProperty<>("taskOffsetTranslationY") {
|
||||
@Override
|
||||
public void setValue(TaskView taskView, float v) {
|
||||
taskView.setTaskOffsetTranslationY(v);
|
||||
@@ -275,7 +273,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
};
|
||||
|
||||
private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_X =
|
||||
new FloatProperty<TaskView>("taskResistanceTranslationX") {
|
||||
new FloatProperty<>("taskResistanceTranslationX") {
|
||||
@Override
|
||||
public void setValue(TaskView taskView, float v) {
|
||||
taskView.setTaskResistanceTranslationX(v);
|
||||
@@ -288,7 +286,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
};
|
||||
|
||||
private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_Y =
|
||||
new FloatProperty<TaskView>("taskResistanceTranslationY") {
|
||||
new FloatProperty<>("taskResistanceTranslationY") {
|
||||
@Override
|
||||
public void setValue(TaskView taskView, float v) {
|
||||
taskView.setTaskResistanceTranslationY(v);
|
||||
@@ -301,7 +299,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
};
|
||||
|
||||
public static final FloatProperty<TaskView> GRID_END_TRANSLATION_X =
|
||||
new FloatProperty<TaskView>("gridEndTranslationX") {
|
||||
new FloatProperty<>("gridEndTranslationX") {
|
||||
@Override
|
||||
public void setValue(TaskView taskView, float v) {
|
||||
taskView.setGridEndTranslationX(v);
|
||||
@@ -314,7 +312,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
};
|
||||
|
||||
public static final FloatProperty<TaskView> SNAPSHOT_SCALE =
|
||||
new FloatProperty<TaskView>("snapshotScale") {
|
||||
new FloatProperty<>("snapshotScale") {
|
||||
@Override
|
||||
public void setValue(TaskView taskView, float v) {
|
||||
taskView.setSnapshotScale(v);
|
||||
@@ -602,10 +600,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
computeAndSetIconTouchDelegate(mIconView, mIconCenterCoords, mIconTouchDelegate);
|
||||
}
|
||||
if (mIconTouchDelegate != null && mIconTouchDelegate.onTouchEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return mIconTouchDelegate != null && mIconTouchDelegate.onTouchEvent(event);
|
||||
}
|
||||
|
||||
protected void computeAndSetIconTouchDelegate(TaskViewIcon view, float[] tempCenterCoords,
|
||||
@@ -647,9 +642,6 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
|
||||
/**
|
||||
* Updates this task view to the given {@param task}.
|
||||
*
|
||||
* TODO(b/142282126) Re-evaluate if we need to pass in isMultiWindowMode after
|
||||
* that issue is fixed
|
||||
*/
|
||||
public void bind(Task task, RecentsOrientedState orientedState) {
|
||||
cancelPendingLoadTasks();
|
||||
@@ -1594,19 +1586,6 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
mEndQuickswitchCuj = endQuickswitchCuj;
|
||||
}
|
||||
|
||||
private int getExpectedViewHeight(View view) {
|
||||
int expectedHeight;
|
||||
int h = view.getLayoutParams().height;
|
||||
if (h > 0) {
|
||||
expectedHeight = h;
|
||||
} else {
|
||||
int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST);
|
||||
view.measure(m, m);
|
||||
expectedHeight = view.getMeasuredHeight();
|
||||
}
|
||||
return expectedHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
|
||||
super.onInitializeAccessibilityNodeInfo(info);
|
||||
|
||||
@@ -660,6 +660,11 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
|
||||
// #5 state handler
|
||||
return new OnBackAnimationCallback() {
|
||||
@Override
|
||||
public void onBackStarted(BackEvent backEvent) {
|
||||
Launcher.this.onBackStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackInvoked() {
|
||||
onStateBack();
|
||||
@@ -2063,8 +2068,12 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
getOnBackAnimationCallback().onBackInvoked();
|
||||
}
|
||||
|
||||
protected void onBackStarted() {
|
||||
mStateManager.getState().onBackStarted(this);
|
||||
}
|
||||
|
||||
protected void onStateBack() {
|
||||
mStateManager.getState().onBackPressed(this);
|
||||
mStateManager.getState().onBackInvoked(this);
|
||||
}
|
||||
|
||||
protected void onScreenOnChanged(boolean isOn) {
|
||||
|
||||
@@ -424,20 +424,29 @@ public abstract class LauncherState implements BaseState<LauncherState> {
|
||||
return TestProtocol.stateOrdinalToString(ordinal);
|
||||
}
|
||||
|
||||
public void onBackPressed(Launcher launcher) {
|
||||
/** Called when predictive back gesture is started. */
|
||||
public void onBackStarted(Launcher launcher) {}
|
||||
|
||||
/**
|
||||
* Called when back action is invoked. This can happen when:
|
||||
* 1. back button is pressed in 3-button navigation.
|
||||
* 2. when back is committed during back swiped (predictive or non-predictive).
|
||||
* 3. when we programmatically perform back action.
|
||||
*/
|
||||
public void onBackInvoked(Launcher launcher) {
|
||||
if (this != NORMAL) {
|
||||
StateManager<LauncherState> lsm = launcher.getStateManager();
|
||||
LauncherState lastState = lsm.getLastState();
|
||||
lsm.goToState(lastState, forEndCallback(this::onBackPressCompleted));
|
||||
lsm.goToState(lastState, forEndCallback(this::onBackAnimationCompleted));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called if back press is completed in a launcher state.
|
||||
* To be called if back animation is completed in a launcher state.
|
||||
*
|
||||
* @param success whether back press animation was successful or canceled.
|
||||
* @param success whether back animation was successful or canceled.
|
||||
*/
|
||||
protected void onBackPressCompleted(boolean success) {
|
||||
protected void onBackAnimationCompleted(boolean success) {
|
||||
// Do nothing. To be overridden by child class.
|
||||
}
|
||||
|
||||
|
||||
@@ -397,11 +397,6 @@ public final class FeatureFlags {
|
||||
+ "waiting for SystemUI and then merging the SystemUI progress whenever we "
|
||||
+ "start receiving the events");
|
||||
|
||||
// TODO(Block 24): Clean up flags
|
||||
public static final BooleanFlag ENABLE_NEW_MIGRATION_LOGIC = getDebugFlag(270393455,
|
||||
"ENABLE_NEW_MIGRATION_LOGIC", ENABLED,
|
||||
"Enable the new grid migration logic, keeping pages when src < dest");
|
||||
|
||||
// TODO(Block 25): Clean up flags
|
||||
public static final BooleanFlag ENABLE_NEW_GESTURE_NAV_TUTORIAL = getDebugFlag(270396257,
|
||||
"ENABLE_NEW_GESTURE_NAV_TUTORIAL", ENABLED,
|
||||
|
||||
@@ -156,11 +156,11 @@ public class DeviceGridState implements Comparable<DeviceGridState> {
|
||||
}
|
||||
|
||||
public Integer getColumns() {
|
||||
return Integer.parseInt(String.valueOf(mGridSizeString.charAt(0)));
|
||||
return Integer.parseInt(String.valueOf(mGridSizeString.split(",")[0]));
|
||||
}
|
||||
|
||||
public Integer getRows() {
|
||||
return Integer.parseInt(String.valueOf(mGridSizeString.charAt(2)));
|
||||
return Integer.parseInt(String.valueOf(mGridSizeString.split(",")[1]));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -223,19 +223,13 @@ public class GridSizeMigrationUtil {
|
||||
screens.add(screenId);
|
||||
}
|
||||
|
||||
boolean preservePages = false;
|
||||
if (screens.isEmpty() && FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.get()) {
|
||||
preservePages = destDeviceState.compareTo(srcDeviceState) >= 0
|
||||
&& destDeviceState.getColumns() - srcDeviceState.getColumns() <= 2;
|
||||
}
|
||||
|
||||
// Then we place the items on the screens
|
||||
for (int screenId : screens) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Migrating " + screenId);
|
||||
}
|
||||
solveGridPlacement(helper, srcReader,
|
||||
destReader, screenId, trgX, trgY, workspaceToBeAdded, false);
|
||||
destReader, screenId, trgX, trgY, workspaceToBeAdded);
|
||||
if (workspaceToBeAdded.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
@@ -245,8 +239,8 @@ public class GridSizeMigrationUtil {
|
||||
// any of the screens, in this case we add them to new screens until all of them are placed.
|
||||
int screenId = destReader.mLastScreenId + 1;
|
||||
while (!workspaceToBeAdded.isEmpty()) {
|
||||
solveGridPlacement(helper, srcReader,
|
||||
destReader, screenId, trgX, trgY, workspaceToBeAdded, preservePages);
|
||||
solveGridPlacement(helper, srcReader, destReader, screenId, trgX, trgY,
|
||||
workspaceToBeAdded);
|
||||
screenId++;
|
||||
}
|
||||
|
||||
@@ -348,7 +342,7 @@ public class GridSizeMigrationUtil {
|
||||
private static void solveGridPlacement(@NonNull final DatabaseHelper helper,
|
||||
@NonNull final DbReader srcReader, @NonNull final DbReader destReader,
|
||||
final int screenId, final int trgX, final int trgY,
|
||||
@NonNull final List<DbEntry> sortedItemsToPlace, final boolean matchingScreenIdOnly) {
|
||||
@NonNull final List<DbEntry> sortedItemsToPlace) {
|
||||
final GridOccupancy occupied = new GridOccupancy(trgX, trgY);
|
||||
final Point trg = new Point(trgX, trgY);
|
||||
final Point next = new Point(0, screenId == 0
|
||||
@@ -366,8 +360,6 @@ public class GridSizeMigrationUtil {
|
||||
Iterator<DbEntry> iterator = sortedItemsToPlace.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final DbEntry entry = iterator.next();
|
||||
if (matchingScreenIdOnly && entry.screenId < screenId) continue;
|
||||
if (matchingScreenIdOnly && entry.screenId > screenId) break;
|
||||
if (entry.minSpanX > trgX || entry.minSpanY > trgY) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
@@ -435,7 +427,8 @@ public class GridSizeMigrationUtil {
|
||||
}
|
||||
}
|
||||
|
||||
protected static class DbReader {
|
||||
@VisibleForTesting
|
||||
public static class DbReader {
|
||||
|
||||
private final SQLiteDatabase mDb;
|
||||
private final String mTableName;
|
||||
@@ -446,7 +439,7 @@ public class GridSizeMigrationUtil {
|
||||
private final Map<Integer, ArrayList<DbEntry>> mWorkspaceEntriesByScreenId =
|
||||
new ArrayMap<>();
|
||||
|
||||
DbReader(SQLiteDatabase db, String tableName, Context context,
|
||||
public DbReader(SQLiteDatabase db, String tableName, Context context,
|
||||
Set<String> validPackages) {
|
||||
mDb = db;
|
||||
mTableName = tableName;
|
||||
|
||||
@@ -86,6 +86,7 @@ android_library {
|
||||
"mockito-target-extended-minus-junit4",
|
||||
"launcher_log_protos_lite",
|
||||
"truth",
|
||||
"kotlinx_coroutines_test",
|
||||
"platform-test-rules",
|
||||
"testables",
|
||||
"com_android_launcher3_flags_lib",
|
||||
|
||||
@@ -21,6 +21,13 @@ import java.util.Random
|
||||
|
||||
/** Generates a random CellLayoutBoard. */
|
||||
open class RandomBoardGenerator(generator: Random) : DeterministicRandomGenerator(generator) {
|
||||
|
||||
companion object {
|
||||
// This is the max number of widgets because we encode the widgets as letters A-Z and we
|
||||
// already have some of those letter used by other things so 22 is a safe number
|
||||
val MAX_NUMBER_OF_WIDGETS = 22
|
||||
}
|
||||
|
||||
/**
|
||||
* @param remainingEmptySpaces the maximum number of spaces we will fill with icons and widgets
|
||||
* meaning that if the number is 100 we will try to fill the board with at most 100 spaces
|
||||
@@ -33,9 +40,9 @@ open class RandomBoardGenerator(generator: Random) : DeterministicRandomGenerato
|
||||
}
|
||||
|
||||
protected fun fillBoard(
|
||||
board: CellLayoutBoard,
|
||||
area: Rect,
|
||||
remainingEmptySpacesArg: Int
|
||||
board: CellLayoutBoard,
|
||||
area: Rect,
|
||||
remainingEmptySpacesArg: Int
|
||||
): CellLayoutBoard {
|
||||
var remainingEmptySpaces = remainingEmptySpacesArg
|
||||
if (area.height() * area.width() <= 0) return board
|
||||
@@ -45,11 +52,18 @@ open class RandomBoardGenerator(generator: Random) : DeterministicRandomGenerato
|
||||
val y = area.top + getRandom(0, area.height() - height)
|
||||
if (remainingEmptySpaces > 0) {
|
||||
remainingEmptySpaces -= width * height
|
||||
} else if (board.widgets.size <= 22 && width * height > 1) {
|
||||
}
|
||||
|
||||
if (board.widgets.size <= MAX_NUMBER_OF_WIDGETS && width * height > 1) {
|
||||
board.addWidget(x, y, width, height)
|
||||
} else {
|
||||
board.addIcon(x, y)
|
||||
}
|
||||
|
||||
if (remainingEmptySpaces < 0) {
|
||||
// optimization, no need to keep going
|
||||
return board
|
||||
}
|
||||
fillBoard(board, Rect(area.left, area.top, area.right, y), remainingEmptySpaces)
|
||||
fillBoard(board, Rect(area.left, y, x, area.bottom), remainingEmptySpaces)
|
||||
fillBoard(board, Rect(x, y + height, area.right, area.bottom), remainingEmptySpaces)
|
||||
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.celllayout.testgenerator
|
||||
|
||||
import android.graphics.Point
|
||||
import com.android.launcher3.LauncherSettings
|
||||
import com.android.launcher3.celllayout.board.CellLayoutBoard
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo
|
||||
import com.android.launcher3.model.gridmigration.WorkspaceItem
|
||||
import java.util.Random
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* Generate a list of WorkspaceItem's for the given test case.
|
||||
*
|
||||
* @param repeatAfter a number after which we would repeat the same number of icons and widgets to
|
||||
* account for cases where the user have the same item multiple times.
|
||||
*/
|
||||
fun generateItemsForTest(
|
||||
testCase: GridMigrationUnitTestCase,
|
||||
repeatAfter: Int
|
||||
): List<WorkspaceItem> {
|
||||
val id = AtomicInteger(0)
|
||||
val widgetId = AtomicInteger(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - 1)
|
||||
val boards = testCase.boards
|
||||
// Repeat the same appWidgetProvider and intent to have repeating widgets and icons and test
|
||||
// that case too
|
||||
val getIntent = { i: Int -> "Intent ${i % repeatAfter}" }
|
||||
val getProvider = { i: Int -> "com.test/test.Provider${i % repeatAfter}" }
|
||||
val hotseatEntries =
|
||||
(0 until boards[0].width).map {
|
||||
WorkspaceItem(
|
||||
x = it,
|
||||
y = 0,
|
||||
spanX = 1,
|
||||
spanY = 1,
|
||||
id = id.getAndAdd(1),
|
||||
screenId = it,
|
||||
title = "Hotseat ${id.get()}",
|
||||
appWidgetId = -1,
|
||||
appWidgetProvider = "Hotseat icons don't have a provider",
|
||||
intent = getIntent(id.get()),
|
||||
type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION,
|
||||
container = LauncherSettings.Favorites.CONTAINER_HOTSEAT
|
||||
)
|
||||
}
|
||||
var widgetEntries =
|
||||
boards
|
||||
.flatMapIndexed { i, board -> board.widgets.map { Pair(i, it) } }
|
||||
.map {
|
||||
WorkspaceItem(
|
||||
x = it.second.cellX,
|
||||
y = it.second.cellY,
|
||||
spanX = it.second.spanX,
|
||||
spanY = it.second.spanY,
|
||||
id = id.getAndAdd(1),
|
||||
screenId = it.first,
|
||||
title = "Title Widget ${id.get()}",
|
||||
appWidgetId = widgetId.getAndAdd(-1),
|
||||
appWidgetProvider = getProvider(id.get()),
|
||||
intent = "Widgets don't have intent",
|
||||
type = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET,
|
||||
container = LauncherSettings.Favorites.CONTAINER_DESKTOP
|
||||
)
|
||||
}
|
||||
widgetEntries = widgetEntries.filter { it.appWidgetProvider.contains("Provider4") }
|
||||
val iconEntries =
|
||||
boards
|
||||
.flatMapIndexed { i, board -> board.icons.map { Pair(i, it) } }
|
||||
.map {
|
||||
WorkspaceItem(
|
||||
x = it.second.coord.x,
|
||||
y = it.second.coord.y,
|
||||
spanX = 1,
|
||||
spanY = 1,
|
||||
id = id.getAndAdd(1),
|
||||
screenId = it.first,
|
||||
title = "Title Icon ${id.get()}",
|
||||
appWidgetId = -1,
|
||||
appWidgetProvider = "Icons don't have providers",
|
||||
intent = getIntent(id.get()),
|
||||
type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION,
|
||||
container = LauncherSettings.Favorites.CONTAINER_DESKTOP
|
||||
)
|
||||
}
|
||||
return widgetEntries + hotseatEntries // + iconEntries
|
||||
}
|
||||
|
||||
data class GridMigrationUnitTestCase(
|
||||
val boards: List<CellLayoutBoard>,
|
||||
val srcSize: Point,
|
||||
val targetSize: Point,
|
||||
val seed: Long
|
||||
)
|
||||
|
||||
class ValidGridMigrationTestCaseGenerator(private val generator: Random) :
|
||||
DeterministicRandomGenerator(generator) {
|
||||
|
||||
companion object {
|
||||
const val MAX_BOARD_SIZE = 12
|
||||
const val MAX_BOARD_COUNT = 10
|
||||
const val SEED = 10342
|
||||
}
|
||||
|
||||
private fun generateBoards(
|
||||
boardGenerator: RandomBoardGenerator,
|
||||
width: Int,
|
||||
height: Int,
|
||||
boardCount: Int
|
||||
): List<CellLayoutBoard> {
|
||||
val boards = mutableListOf<CellLayoutBoard>()
|
||||
for (i in 0 until boardCount) {
|
||||
boards.add(
|
||||
boardGenerator.generateBoard(
|
||||
width,
|
||||
height,
|
||||
boardGenerator.getRandom(0, width * height)
|
||||
)
|
||||
)
|
||||
}
|
||||
return boards
|
||||
}
|
||||
|
||||
fun generateTestCase(): GridMigrationUnitTestCase {
|
||||
var seed = generator.nextLong()
|
||||
val randomBoardGenerator = RandomBoardGenerator(Random(seed))
|
||||
val width = randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE)
|
||||
val height = randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE)
|
||||
return GridMigrationUnitTestCase(
|
||||
boards =
|
||||
generateBoards(
|
||||
boardGenerator = randomBoardGenerator,
|
||||
width = width,
|
||||
height = height,
|
||||
boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT)
|
||||
),
|
||||
srcSize = Point(width, height),
|
||||
targetSize =
|
||||
Point(
|
||||
randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE),
|
||||
randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE)
|
||||
),
|
||||
seed = seed
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ import com.android.launcher3.InvariantDeviceProfile
|
||||
import com.android.launcher3.LauncherPrefs
|
||||
import com.android.launcher3.LauncherPrefs.Companion.WORKSPACE_SIZE
|
||||
import com.android.launcher3.LauncherSettings.Favorites.*
|
||||
import com.android.launcher3.config.FeatureFlags
|
||||
import com.android.launcher3.model.GridSizeMigrationUtil.DbReader
|
||||
import com.android.launcher3.pm.UserCache
|
||||
import com.android.launcher3.provider.LauncherDbUtils
|
||||
@@ -98,10 +97,7 @@ class GridSizeMigrationUtilTest {
|
||||
modelHelper.destroy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Old migration logic, should be modified once [FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC] is not
|
||||
* needed anymore
|
||||
*/
|
||||
/** Old migration logic, should be modified once is not needed anymore */
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testMigration() {
|
||||
@@ -208,10 +204,7 @@ class GridSizeMigrationUtilTest {
|
||||
assertThat(locMap[testPackage9]).isEqualTo(Point(0, 2))
|
||||
}
|
||||
|
||||
/**
|
||||
* Old migration logic, should be modified once [FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC] is not
|
||||
* needed anymore
|
||||
*/
|
||||
/** Old migration logic, should be modified once is not needed anymore */
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testMigrationBackAndForth() {
|
||||
@@ -605,68 +598,6 @@ class GridSizeMigrationUtilTest {
|
||||
c.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrating from a smaller grid to a large one should keep the pages if the column difference
|
||||
* is less than 2
|
||||
*/
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun migrateFromSmallerGridSmallDifference() {
|
||||
enableNewMigrationLogic("4,4")
|
||||
|
||||
// Setup src grid
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage1, 5, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 3, testPackage2, 6, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 3, 1, testPackage3, 7, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 3, 2, testPackage4, 8, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_DESKTOP, 3, 3, testPackage5, 9, TMP_TABLE)
|
||||
|
||||
idp.numDatabaseHotseatIcons = 4
|
||||
idp.numColumns = 6
|
||||
idp.numRows = 5
|
||||
|
||||
val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
|
||||
val destReader = DbReader(db, TABLE_NAME, context, validPackages)
|
||||
GridSizeMigrationUtil.migrate(
|
||||
dbHelper,
|
||||
srcReader,
|
||||
destReader,
|
||||
idp.numDatabaseHotseatIcons,
|
||||
Point(idp.numColumns, idp.numRows),
|
||||
DeviceGridState(context),
|
||||
DeviceGridState(idp)
|
||||
)
|
||||
|
||||
// Get workspace items
|
||||
val c =
|
||||
db.query(
|
||||
TABLE_NAME,
|
||||
arrayOf(INTENT, SCREEN),
|
||||
"container=$CONTAINER_DESKTOP",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
val intentIndex = c.getColumnIndex(INTENT)
|
||||
val screenIndex = c.getColumnIndex(SCREEN)
|
||||
|
||||
// Get in which screen the icon is
|
||||
val locMap = HashMap<String?, Int>()
|
||||
while (c.moveToNext()) {
|
||||
locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
|
||||
c.getInt(screenIndex)
|
||||
}
|
||||
c.close()
|
||||
assertThat(locMap.size).isEqualTo(5)
|
||||
assertThat(locMap[testPackage1]).isEqualTo(0)
|
||||
assertThat(locMap[testPackage2]).isEqualTo(0)
|
||||
assertThat(locMap[testPackage3]).isEqualTo(1)
|
||||
assertThat(locMap[testPackage4]).isEqualTo(1)
|
||||
assertThat(locMap[testPackage5]).isEqualTo(2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrating from a smaller grid to a large one should reflow the pages if the column difference
|
||||
* is more than 2
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.model.gridmigration
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.graphics.Point
|
||||
import com.android.launcher3.LauncherSettings.Favorites
|
||||
import com.android.launcher3.celllayout.board.CellLayoutBoard
|
||||
|
||||
class MockSet(override val size: Int) : Set<String> {
|
||||
override fun contains(element: String): Boolean = true
|
||||
override fun containsAll(elements: Collection<String>): Boolean = true
|
||||
override fun isEmpty(): Boolean = false
|
||||
override fun iterator(): Iterator<String> = listOf<String>().iterator()
|
||||
}
|
||||
|
||||
fun itemListToBoard(itemsArg: List<WorkspaceItem>, boardSize: Point): List<CellLayoutBoard> {
|
||||
val items = itemsArg.filter { it.container != Favorites.CONTAINER_HOTSEAT }
|
||||
val boardList =
|
||||
List(items.maxOf { it.screenId + 1 }) { CellLayoutBoard(boardSize.x, boardSize.y) }
|
||||
items.forEach {
|
||||
when (it.type) {
|
||||
Favorites.ITEM_TYPE_FOLDER,
|
||||
Favorites.ITEM_TYPE_APP_PAIR -> throw Exception("Not implemented")
|
||||
Favorites.ITEM_TYPE_APPWIDGET ->
|
||||
boardList[it.screenId].addWidget(it.x, it.y, it.spanX, it.spanY)
|
||||
Favorites.ITEM_TYPE_APPLICATION -> boardList[it.screenId].addIcon(it.x, it.y)
|
||||
}
|
||||
}
|
||||
return boardList
|
||||
}
|
||||
|
||||
fun insertIntoDb(tableName: String, entry: WorkspaceItem, db: SQLiteDatabase) {
|
||||
val values = ContentValues()
|
||||
values.put(Favorites.SCREEN, entry.screenId)
|
||||
values.put(Favorites.CELLX, entry.x)
|
||||
values.put(Favorites.CELLY, entry.y)
|
||||
values.put(Favorites.SPANX, entry.spanX)
|
||||
values.put(Favorites.SPANY, entry.spanY)
|
||||
values.put(Favorites.TITLE, entry.title)
|
||||
values.put(Favorites.INTENT, entry.intent)
|
||||
values.put(Favorites.APPWIDGET_PROVIDER, entry.appWidgetProvider)
|
||||
values.put(Favorites.APPWIDGET_ID, entry.appWidgetId)
|
||||
values.put(Favorites.CONTAINER, entry.container)
|
||||
values.put(Favorites.ITEM_TYPE, entry.type)
|
||||
values.put(Favorites._ID, entry.id)
|
||||
db.insert(tableName, null, values)
|
||||
}
|
||||
|
||||
fun readDb(tableName: String, db: SQLiteDatabase): List<WorkspaceItem> {
|
||||
val result = mutableListOf<WorkspaceItem>()
|
||||
val cursor = db.query(tableName, null, null, null, null, null, null)
|
||||
val indexCellX: Int = cursor.getColumnIndexOrThrow(Favorites.CELLX)
|
||||
val indexCellY: Int = cursor.getColumnIndexOrThrow(Favorites.CELLY)
|
||||
val indexSpanX: Int = cursor.getColumnIndexOrThrow(Favorites.SPANX)
|
||||
val indexSpanY: Int = cursor.getColumnIndexOrThrow(Favorites.SPANY)
|
||||
val indexId: Int = cursor.getColumnIndexOrThrow(Favorites._ID)
|
||||
val indexScreen: Int = cursor.getColumnIndexOrThrow(Favorites.SCREEN)
|
||||
val indexTitle: Int = cursor.getColumnIndexOrThrow(Favorites.TITLE)
|
||||
val indexAppWidgetId: Int = cursor.getColumnIndexOrThrow(Favorites.APPWIDGET_ID)
|
||||
val indexWidgetProvider: Int = cursor.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER)
|
||||
val indexIntent: Int = cursor.getColumnIndexOrThrow(Favorites.INTENT)
|
||||
val indexItemType: Int = cursor.getColumnIndexOrThrow(Favorites.ITEM_TYPE)
|
||||
val container: Int = cursor.getColumnIndexOrThrow(Favorites.CONTAINER)
|
||||
while (cursor.moveToNext()) {
|
||||
result.add(
|
||||
WorkspaceItem(
|
||||
x = cursor.getInt(indexCellX),
|
||||
y = cursor.getInt(indexCellY),
|
||||
spanX = cursor.getInt(indexSpanX),
|
||||
spanY = cursor.getInt(indexSpanY),
|
||||
id = cursor.getInt(indexId),
|
||||
screenId = cursor.getInt(indexScreen),
|
||||
title = cursor.getString(indexTitle),
|
||||
appWidgetId = cursor.getInt(indexAppWidgetId),
|
||||
appWidgetProvider = cursor.getString(indexWidgetProvider),
|
||||
intent = cursor.getString(indexIntent),
|
||||
type = cursor.getInt(indexItemType),
|
||||
container = cursor.getInt(container)
|
||||
)
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
data class WorkspaceItem(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
val spanX: Int,
|
||||
val spanY: Int,
|
||||
val id: Int,
|
||||
val screenId: Int,
|
||||
val title: String,
|
||||
val appWidgetId: Int,
|
||||
val appWidgetProvider: String,
|
||||
val intent: String,
|
||||
val type: Int,
|
||||
val container: Int,
|
||||
)
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.model.gridmigration
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.graphics.Point
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.launcher3.InvariantDeviceProfile
|
||||
import com.android.launcher3.LauncherSettings.Favorites
|
||||
import com.android.launcher3.celllayout.testgenerator.ValidGridMigrationTestCaseGenerator
|
||||
import com.android.launcher3.celllayout.testgenerator.generateItemsForTest
|
||||
import com.android.launcher3.model.DatabaseHelper
|
||||
import com.android.launcher3.model.DeviceGridState
|
||||
import com.android.launcher3.model.GridSizeMigrationUtil
|
||||
import com.android.launcher3.pm.UserCache
|
||||
import com.android.launcher3.provider.LauncherDbUtils
|
||||
import com.android.launcher3.util.rule.TestStabilityRule
|
||||
import com.android.launcher3.util.rule.TestStabilityRule.Stability
|
||||
import java.util.Random
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ValidGridMigrationUnitTest {
|
||||
|
||||
companion object {
|
||||
const val SEED = 1044542
|
||||
const val REPEAT_AFTER = 10
|
||||
const val TAG = "ValidGridMigrationUnitTest"
|
||||
}
|
||||
|
||||
private lateinit var context: Context
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
}
|
||||
|
||||
private fun validate(
|
||||
srcItems: List<WorkspaceItem>,
|
||||
dstItems: List<WorkspaceItem>,
|
||||
destinationSize: Point
|
||||
) {
|
||||
// This returns a map with the number of repeated elements
|
||||
// ex { calculatorIcon : 6, weatherWidget : 2 }
|
||||
val itemsToSet = { it: List<WorkspaceItem> ->
|
||||
it.filter { it.container != Favorites.CONTAINER_HOTSEAT }
|
||||
.groupingBy {
|
||||
when (it.type) {
|
||||
Favorites.ITEM_TYPE_FOLDER,
|
||||
Favorites.ITEM_TYPE_APP_PAIR -> throw Exception("Not implemented")
|
||||
Favorites.ITEM_TYPE_APPWIDGET -> it.appWidgetProvider
|
||||
Favorites.ITEM_TYPE_APPLICATION -> it.intent
|
||||
else -> it.title
|
||||
}
|
||||
}
|
||||
.eachCount()
|
||||
}
|
||||
for (it in dstItems) {
|
||||
assert((it.x in 0..destinationSize.x) && (it.y in 0..destinationSize.y)) {
|
||||
"Item outside of the board size. Size = $destinationSize Item = $it"
|
||||
}
|
||||
assert(
|
||||
(it.x + it.spanX in 0..destinationSize.x) &&
|
||||
(it.y + it.spanY in 0..destinationSize.y)
|
||||
) {
|
||||
"Item doesn't fit in the grid. Size = $destinationSize Item = $it"
|
||||
}
|
||||
}
|
||||
|
||||
assert(itemsToSet(srcItems) == itemsToSet(dstItems)) {
|
||||
"The srcItems do not match the dstItems src = $srcItems dst = $dstItems"
|
||||
}
|
||||
}
|
||||
|
||||
private fun addItemsToDb(db: SQLiteDatabase, tableName: String, items: List<WorkspaceItem>) {
|
||||
LauncherDbUtils.SQLiteTransaction(db).use { transaction ->
|
||||
items.forEach { insertIntoDb(tableName, it, transaction.db) }
|
||||
transaction.commit()
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrate(
|
||||
srcItems: List<WorkspaceItem>,
|
||||
srcSize: Point,
|
||||
targetSize: Point
|
||||
): List<WorkspaceItem> {
|
||||
val userSerial = UserCache.INSTANCE[context].getSerialNumberForUser(Process.myUserHandle())
|
||||
val dbHelper =
|
||||
DatabaseHelper(
|
||||
context,
|
||||
null,
|
||||
{ UserCache.INSTANCE.get(context).getSerialNumberForUser(it) },
|
||||
{}
|
||||
)
|
||||
val srcTableName = Favorites.TMP_TABLE
|
||||
val dstTableName = Favorites.TABLE_NAME
|
||||
Favorites.addTableToDb(dbHelper.writableDatabase, userSerial, false, srcTableName)
|
||||
addItemsToDb(dbHelper.writableDatabase, srcTableName, srcItems)
|
||||
LauncherDbUtils.SQLiteTransaction(dbHelper.writableDatabase).use {
|
||||
GridSizeMigrationUtil.migrate(
|
||||
dbHelper,
|
||||
GridSizeMigrationUtil.DbReader(it.db, srcTableName, context, MockSet(1)),
|
||||
GridSizeMigrationUtil.DbReader(it.db, dstTableName, context, MockSet(1)),
|
||||
targetSize.x,
|
||||
targetSize,
|
||||
DeviceGridState(
|
||||
srcSize.x,
|
||||
srcSize.y,
|
||||
srcSize.x,
|
||||
InvariantDeviceProfile.TYPE_PHONE,
|
||||
srcTableName
|
||||
),
|
||||
DeviceGridState(
|
||||
targetSize.x,
|
||||
targetSize.y,
|
||||
targetSize.x,
|
||||
InvariantDeviceProfile.TYPE_PHONE,
|
||||
dstTableName
|
||||
)
|
||||
)
|
||||
it.commit()
|
||||
}
|
||||
return readDb(dstTableName, dbHelper.readableDatabase)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runTestCase() {
|
||||
val caseGenerator = ValidGridMigrationTestCaseGenerator(Random(SEED.toLong()))
|
||||
for (i in 0..50) {
|
||||
val testCase = caseGenerator.generateTestCase()
|
||||
Log.d(TAG, "Test case = $testCase")
|
||||
val srcItemList = generateItemsForTest(testCase, REPEAT_AFTER)
|
||||
val dstItemList = migrate(srcItemList, testCase.srcSize, testCase.targetSize)
|
||||
validate(srcItemList, dstItemList, testCase.targetSize)
|
||||
}
|
||||
}
|
||||
|
||||
// This test takes about 4 minutes, there is no need to run it in presubmit.
|
||||
@Stability(flavors = TestStabilityRule.LOCAL or TestStabilityRule.PLATFORM_POSTSUBMIT)
|
||||
@Test
|
||||
fun runExtensiveTestCases() {
|
||||
val caseGenerator = ValidGridMigrationTestCaseGenerator(Random(SEED.toLong()))
|
||||
for (i in 0..1000) {
|
||||
val testCase = caseGenerator.generateTestCase()
|
||||
Log.d(TAG, "Test case = $testCase")
|
||||
val srcItemList = generateItemsForTest(testCase, REPEAT_AFTER)
|
||||
val dstItemList = migrate(srcItemList, testCase.srcSize, testCase.targetSize)
|
||||
validate(srcItemList, dstItemList, testCase.targetSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user