Merge "Use ACTION_SCROLL to scroll TAPL workspace objects" into main
diff --git a/Android.bp b/Android.bp
index 010ea45..19d2a58 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,42 +23,66 @@
// All sources are split so they can be reused in many other libraries/apps in other folders
filegroup {
name: "launcher-src",
- srcs: [ "src/**/*.java", "src/**/*.kt" ],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
}
filegroup {
name: "launcher-quickstep-src",
- srcs: [ "quickstep/src/**/*.java", "quickstep/src/**/*.kt" ],
+ srcs: [
+ "quickstep/src/**/*.java",
+ "quickstep/src/**/*.kt",
+ ],
}
filegroup {
name: "launcher-go-src",
- srcs: [ "go/src/**/*.java", "go/src/**/*.kt" ],
+ srcs: [
+ "go/src/**/*.java",
+ "go/src/**/*.kt",
+ ],
}
filegroup {
name: "launcher-go-quickstep-src",
- srcs: [ "go/quickstep/src/**/*.java", "go/quickstep/src/**/*.kt" ],
+ srcs: [
+ "go/quickstep/src/**/*.java",
+ "go/quickstep/src/**/*.kt",
+ ],
}
filegroup {
name: "launcher-src_shortcuts_overrides",
- srcs: [ "src_shortcuts_overrides/**/*.java", "src_shortcuts_overrides/**/*.kt" ],
+ srcs: [
+ "src_shortcuts_overrides/**/*.java",
+ "src_shortcuts_overrides/**/*.kt",
+ ],
}
filegroup {
name: "launcher-src_ui_overrides",
- srcs: [ "src_ui_overrides/**/*.java", "src_ui_overrides/**/*.kt" ],
+ srcs: [
+ "src_ui_overrides/**/*.java",
+ "src_ui_overrides/**/*.kt",
+ ],
}
filegroup {
name: "launcher-ext_tests",
- srcs: [ "ext_tests/**/*.java", "ext_tests/**/*.kt" ],
+ srcs: [
+ "ext_tests/**/*.java",
+ "ext_tests/**/*.kt",
+ ],
}
filegroup {
name: "launcher-quickstep-ext_tests",
- srcs: [ "quickstep/ext_tests/**/*.java", "quickstep/ext_tests/**/*.kt" ],
+ srcs: [
+ "quickstep/ext_tests/**/*.java",
+ "quickstep/ext_tests/**/*.kt",
+ ],
}
// Proguard files for Launcher3
@@ -85,9 +109,12 @@
srcs: [
"tests/tapl/**/*.java",
],
- resource_dirs: [ ],
+ resource_dirs: [],
manifest: "tests/tapl/AndroidManifest.xml",
platform_apis: true,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library_static {
@@ -99,12 +126,15 @@
sdk_version: "current",
proto: {
type: "lite",
- local_include_dirs:[
+ local_include_dirs: [
"protos",
"protos_overrides",
],
},
static_libs: ["libprotobuf-java-lite"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library_static {
@@ -115,14 +145,17 @@
sdk_version: "current",
proto: {
type: "lite",
- local_include_dirs:[
+ local_include_dirs: [
"quickstep/protos_overrides",
],
},
static_libs: [
- "libprotobuf-java-lite",
- "launcher_log_protos_lite"
- ],
+ "libprotobuf-java-lite",
+ "launcher_log_protos_lite",
+ ],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -134,12 +167,15 @@
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Library with all the dependencies for building Launcher3
android_library {
name: "Launcher3ResLib",
- srcs: [ ],
+ srcs: [],
resource_dirs: ["res"],
static_libs: [
"LauncherPluginLib",
@@ -154,7 +190,7 @@
"com.google.android.material_material",
"iconloader_base",
"view_capture",
- "animationlib"
+ "animationlib",
],
manifest: "AndroidManifest-common.xml",
sdk_version: "current",
@@ -236,7 +272,7 @@
// Library with all the dependencies for building quickstep
android_library {
name: "QuickstepResLib",
- srcs: [ ],
+ srcs: [],
resource_dirs: [
"quickstep/res",
],
@@ -253,9 +289,11 @@
],
manifest: "quickstep/AndroidManifest.xml",
min_sdk_version: "current",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
-
// Library with all the dependencies for building Launcher Go
android_library {
name: "LauncherGoResLib",
@@ -360,7 +398,10 @@
manifest: "go/AndroidManifest.xml",
jacoco: {
include_filter: ["com.android.launcher3.*"],
- }
+ },
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
@@ -396,7 +437,10 @@
manifest: "quickstep/AndroidManifest.xml",
jacoco: {
include_filter: ["com.android.launcher3.*"],
- }
+ },
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
@@ -414,7 +458,7 @@
min_sdk_version: "current",
target_sdk_version: "current",
- srcs: [ ],
+ srcs: [],
resource_dirs: [
"go/quickstep/res",
@@ -446,7 +490,9 @@
manifest: "quickstep/AndroidManifest.xml",
jacoco: {
include_filter: ["com.android.launcher3.*"],
- }
+ },
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
-
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 210cfd0..228c34a 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -67,7 +67,7 @@
name: "enable_taskbar_pinning"
namespace: "launcher"
description: "Enables taskbar pinning to allow user to switch between transient and persistent taskbar flavors."
- bug: "270396583"
+ bug: "296231746"
}
flag {
diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig
index fc79200..97e56b7 100644
--- a/aconfig/launcher_search.aconfig
+++ b/aconfig/launcher_search.aconfig
@@ -12,4 +12,18 @@
namespace: "launcher_search"
description: "This flag enables the animation of the Private Space container"
bug: "299294792"
+}
+
+flag {
+ name: "private_space_sys_apps_separation"
+ namespace: "launcher_search"
+ description: "This flag enables showing system apps separate in Private Space container."
+ bug: "308054233"
+}
+
+flag {
+ name: "private_space_app_installer_button"
+ namespace: "launcher_search"
+ description: "This flag enables addition of App Installer button in Private Space container."
+ bug: "308064949"
}
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/launcher3/AppSharing.java b/go/quickstep/src/com/android/launcher3/AppSharing.java
index cb1f1c7..78524d1 100644
--- a/go/quickstep/src/com/android/launcher3/AppSharing.java
+++ b/go/quickstep/src/com/android/launcher3/AppSharing.java
@@ -27,6 +27,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -64,14 +65,17 @@
private static final String TAG = "AppSharing";
private static final String FILE_PROVIDER_SUFFIX = ".overview.fileprovider";
- private static final String APP_EXSTENSION = ".apk";
+ private static final String APP_EXTENSION = ".apk";
private static final String APP_MIME_TYPE = "application/application";
private final String mSharingComponent;
private AppShareabilityManager mShareabilityMgr;
private AppSharing(Launcher launcher) {
- mSharingComponent = launcher.getText(R.string.app_sharing_component).toString();
+ String sharingComponent = Settings.Secure.getString(launcher.getContentResolver(),
+ Settings.Secure.NEARBY_SHARING_COMPONENT);
+ mSharingComponent = TextUtils.isEmpty(sharingComponent) ? launcher.getText(
+ R.string.app_sharing_component).toString() : sharingComponent;
}
private Uri getShareableUri(Context context, String path, String displayName) {
@@ -147,7 +151,7 @@
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
sourceDir = packageInfo.applicationInfo.sourceDir;
appLabel = packageManager.getApplicationLabel(packageInfo.applicationInfo)
- .toString() + APP_EXSTENSION;
+ + APP_EXTENSION;
} catch (Exception e) {
Log.e(TAG, "Could not find info for package \"" + packageName + "\"");
return;
@@ -175,9 +179,7 @@
return;
}
checkShareability(/* requestUpdateIfUnknown */ false);
- mTarget.runOnUiThread(() -> {
- mPopupDataProvider.redrawSystemShortcuts();
- });
+ mTarget.runOnUiThread(mPopupDataProvider::redrawSystemShortcuts);
}
private void checkShareability(boolean requestUpdateIfUnknown) {
diff --git a/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml b/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml
index a07aeaa..8b4127a 100644
--- a/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml
+++ b/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml
@@ -16,7 +16,8 @@
android:width="84dp"
android:height="208dp"
android:viewportWidth="84"
- android:viewportHeight="208">
+ android:viewportHeight="208"
+ android:autoMirrored="true">
<path
android:pathData="M24.53,169.2L32.09,165.56C77.7,143.55 77.7,64.45 32.09,42.35L24.53,38.71C14.55,33.95 6.06,25.56 0,14.92V193.08C6.06,182.44 14.55,174.05 24.53,169.2Z"
android:fillColor="?attr/onSurfaceBack"/>
diff --git a/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml b/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml
index e20458e..3a11f21 100644
--- a/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml
+++ b/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml
@@ -16,7 +16,8 @@
android:width="122dp"
android:height="303dp"
android:viewportWidth="122"
- android:viewportHeight="303">
+ android:viewportHeight="303"
+ android:autoMirrored="true">
<path
android:pathData="M35.65,245.9L46.63,240.61C112.92,208.62 112.92,93.67 46.63,61.54L35.65,56.26C21.15,49.34 8.81,37.14 0,21.69V280.6C8.81,265.15 21.15,252.95 35.65,245.9Z"
android:fillColor="?attr/onSurfaceBack"/>
diff --git a/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml b/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
index 9389340..c217be2 100644
--- a/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
+++ b/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
@@ -16,7 +16,8 @@
android:width="83dp"
android:height="208dp"
android:viewportWidth="83"
- android:viewportHeight="208">
+ android:viewportHeight="208"
+ android:autoMirrored="true">
<path
android:pathData="M23.53,169.2L31.09,165.56C76.7,143.55 76.7,64.45 31.09,42.35L23.53,38.71C13.55,33.95 5.06,25.56 -1,14.92V193.08C5.06,182.44 13.55,174.05 23.53,169.2Z"
android:fillColor="?attr/onSurfaceBack"/>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 2c740b7..4d21108 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -114,7 +114,7 @@
<string name="taskbar_edu_close" msgid="887022990168191073">"关闭"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"完成"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"主屏幕"</string>
- <string name="taskbar_button_a11y" msgid="5241161324875094465">"无障碍"</string>
+ <string name="taskbar_button_a11y" msgid="5241161324875094465">"无障碍功能"</string>
<string name="taskbar_button_back" msgid="8558862226461164514">"返回"</string>
<string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IME 切换器"</string>
<string name="taskbar_button_recents" msgid="7273376136216613134">"最近用过"</string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 232c441..2a1f39f 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -322,7 +322,6 @@
<!-- Taskbar -->
<dimen name="taskbar_size">@*android:dimen/taskbar_frame_height</dimen>
- <dimen name="taskbar_phone_size">@*android:dimen/navigation_bar_frame_height</dimen>
<dimen name="taskbar_ime_size">48dp</dimen>
<dimen name="taskbar_icon_min_touch_size">48dp</dimen>
<!-- Note that this applies to both sides of all icons, so visible space is double this. -->
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 477cd84..575ad9e 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
@@ -23,6 +24,8 @@
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.appwidget.AppWidgetProviderInfo;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
@@ -43,6 +46,14 @@
/** An Activity that can host Launcher's widget picker. */
public class WidgetPickerActivity extends BaseActivity {
+ /**
+ * Name of the extra that indicates that a widget being dragged.
+ *
+ * <p>When set to "true" in the result of startActivityForResult, the client that launched the
+ * picker knows that activity was closed due to pending drag.
+ */
+ private static final String EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag";
+
private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
private WidgetsModel mModel;
private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
@@ -102,6 +113,46 @@
};
}
+ @Override
+ public View.OnLongClickListener getAllAppsItemLongClickListener() {
+ return view -> {
+ if (!(view instanceof WidgetCell widgetCell)) return false;
+
+ if (widgetCell.getWidgetView().getDrawable() == null
+ && widgetCell.getAppWidgetHostViewPreview() == null) {
+ // The widget preview hasn't been loaded; so, we abort the drag.
+ return false;
+ }
+
+ final AppWidgetProviderInfo info = widgetCell.getWidgetItem().widgetInfo;
+ if (info == null || info.provider == null) {
+ return false;
+ }
+
+ ClipData clipData = new ClipData(
+ new ClipDescription(
+ /* label= */ "", // not displayed anywhere; so, set to empty.
+ new String[]{MIMETYPE_TEXT_INTENT}
+ ),
+ new ClipData.Item(new Intent()
+ .putExtra(Intent.EXTRA_USER, info.getProfile())
+ .putExtra(Intent.EXTRA_COMPONENT_NAME, info.provider))
+ );
+
+ // Set result indicating activity was closed due a widget being dragged.
+ setResult(RESULT_OK, new Intent()
+ .putExtra(EXTRA_IS_PENDING_WIDGET_DRAG, true));
+
+ // DRAG_FLAG_GLOBAL permits dragging data beyond app window.
+ return view.startDragAndDrop(
+ clipData,
+ new View.DragShadowBuilder(view),
+ /* myLocalState= */ null,
+ View.DRAG_FLAG_GLOBAL
+ );
+ };
+ }
+
private void refreshAndBindWidgets() {
MODEL_EXECUTOR.execute(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 0dde1bd..7209bd8 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -25,6 +25,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_DRAGDROP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
@@ -71,12 +72,13 @@
import com.android.launcher3.logging.StatsLogManager.EventEnum;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.util.UserIconInfo;
import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
+import com.android.systemui.shared.system.SysUiStatsLog;
import java.util.Locale;
import java.util.Optional;
import java.util.function.ObjIntConsumer;
-import java.util.function.Predicate;
/**
* Utility class to track stats log and emit corresponding app events
@@ -132,7 +134,8 @@
|| event == LAUNCHER_TASK_LAUNCH_SWIPE_DOWN
|| event == LAUNCHER_TASK_LAUNCH_TAP
|| event == LAUNCHER_QUICKSWITCH_RIGHT
- || event == LAUNCHER_QUICKSWITCH_LEFT) {
+ || event == LAUNCHER_QUICKSWITCH_LEFT
+ || event == LAUNCHER_APP_LAUNCH_DRAGDROP) {
sendEvent(atomInfo, ACTION_LAUNCH, CONTAINER_PREDICTION);
} else if (event == LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST) {
sendEvent(atomInfo, ACTION_DISMISS, CONTAINER_PREDICTION);
@@ -186,14 +189,13 @@
}
@Nullable
- private AppTarget toAppTarget(LauncherAtom.ItemInfo info) {
- UserHandle userHandle = Process.myUserHandle();
- if (info.getIsWork()) {
- userHandle = UserCache.INSTANCE.get(mContext).getUserProfiles().stream()
- .filter(((Predicate<UserHandle>) userHandle::equals).negate())
- .findAny()
- .orElse(null);
- }
+ AppTarget toAppTarget(LauncherAtom.ItemInfo info) {
+ int iconInfoType = getIconInfoTypeFromItemInfo(info);
+ UserCache userCache = UserCache.getInstance(mContext);
+ UserHandle userHandle = userCache.getUserProfiles().stream()
+ .filter(user -> userCache.getUserInfo(user).type == iconInfoType)
+ .findFirst()
+ .orElse(null);
if (userHandle == null) {
return null;
}
@@ -287,6 +289,9 @@
case SHORTCUTS_CONTAINER: {
return "deep-shortcuts";
}
+ case TASK_BAR_CONTAINER: {
+ return "taskbar";
+ }
case FOLDER: {
FolderContainer fc = ci.getFolder();
switch (fc.getParentContainerCase()) {
@@ -323,4 +328,16 @@
return TextUtils.isEmpty(componentNameString)
? null : ComponentName.unflattenFromString(componentNameString);
}
+
+ private int getIconInfoTypeFromItemInfo(LauncherAtom.ItemInfo info) {
+ int userType = info.getUserType();
+ return switch (userType) {
+ case SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_WORK -> UserIconInfo.TYPE_WORK;
+ case SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_CLONED ->
+ UserIconInfo.TYPE_CLONED;
+ case SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_PRIVATE ->
+ UserIconInfo.TYPE_PRIVATE;
+ default -> UserIconInfo.TYPE_MAIN;
+ };
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index f981610..535c8ec 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -105,6 +105,13 @@
}
@Override
+ protected boolean isInOverview() {
+ TopTaskTracker.CachedTaskInfo topTask = TopTaskTracker.INSTANCE
+ .get(mControllers.taskbarActivityContext).getCachedTopTask(true);
+ return topTask.isRecentsTask();
+ }
+
+ @Override
public RecentsView getRecentsView() {
return mRecentsActivity.getOverviewPanel();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index f58fd45..5caf004 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -32,6 +32,7 @@
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -115,8 +116,15 @@
&& desktopController.areFreeformTasksVisible();
if (mModel.isTaskListValid(mTaskListChangeId)) {
- mQuickSwitchViewController.openQuickSwitchView(mTasks,
- mNumHiddenTasks, /* updateTasks= */ false, currentFocusedIndex, onDesktop);
+ // When we are opening the KQS with no focus override, check if the first task is
+ // running. If not, focus that first task.
+ mQuickSwitchViewController.openQuickSwitchView(
+ mTasks,
+ mNumHiddenTasks,
+ /* updateTasks= */ false,
+ currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
+ ? 0 : currentFocusedIndex,
+ onDesktop);
return;
}
@@ -126,8 +134,15 @@
} else {
processLoadedTasks(tasks);
}
- mQuickSwitchViewController.openQuickSwitchView(mTasks,
- mNumHiddenTasks, /* updateTasks= */ true, currentFocusedIndex, onDesktop);
+ // Check if the first task is running after the recents model has updated so that we use
+ // the correct index.
+ mQuickSwitchViewController.openQuickSwitchView(
+ mTasks,
+ mNumHiddenTasks,
+ /* updateTasks= */ true,
+ currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
+ ? 0 : currentFocusedIndex,
+ onDesktop);
});
}
@@ -246,5 +261,20 @@
void onCloseComplete() {
mQuickSwitchViewController = null;
}
+
+ boolean isTaskRunning(@Nullable GroupTask task) {
+ if (task == null) {
+ return false;
+ }
+ int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
+ Task task2 = task.task2;
+
+ return runningTaskId == task.task1.key.id
+ || (task2 != null && runningTaskId == task2.key.id);
+ }
+
+ boolean isFirstTaskRunning() {
+ return isTaskRunning(getTaskAt(0));
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index b29ce6b..6e88780 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -117,9 +117,12 @@
* index will be focused.
*/
protected int launchFocusedTask() {
- // Launch the second-most recent task if the user quick switches too quickly, if possible.
- return launchTaskAt(mCurrentFocusIndex == -1
- ? (mControllerCallbacks.getTaskCount() > 1 ? 1 : 0) : mCurrentFocusIndex);
+ if (mCurrentFocusIndex != -1) {
+ return launchTaskAt(mCurrentFocusIndex);
+ }
+ // If the user quick switches too quickly, updateCurrentFocusIndex might not have run.
+ return launchTaskAt(mControllerCallbacks.isFirstTaskRunning()
+ && mControllerCallbacks.getTaskCount() > 1 ? 1 : 0);
}
private int launchTaskAt(int index) {
@@ -134,10 +137,7 @@
if (task == null) {
return Math.max(0, index);
}
- Task task2 = task.task2;
- int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
- if (runningTaskId == task.task1.key.id
- || (task2 != null && runningTaskId == task2.key.id)) {
+ if (mControllerCallbacks.isTaskRunning(task)) {
// Ignore attempts to run the selected task if it is already running.
return -1;
}
@@ -146,7 +146,7 @@
UI_HELPER_EXECUTOR.execute(() ->
SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
.showDesktopApp(task.task1.key.id));
- } else if (task2 == null) {
+ } else if (task.task2 == null) {
UI_HELPER_EXECUTOR.execute(() ->
ActivityManagerWrapper.getInstance().startActivityFromRecents(
task.task1.key,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index f6703f3..eff6e27 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -42,7 +42,9 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@@ -107,6 +109,7 @@
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
+import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
@@ -129,10 +132,13 @@
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
@@ -1056,9 +1062,8 @@
} else if (info.isPromise()) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "start: taskbarPromiseIcon");
- intent = new PackageManagerHelper(this)
- .getMarketIntent(info.getTargetPackage())
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent = ApiWrapper.getAppMarketActivityIntent(this,
+ info.getTargetPackage(), Process.myUserHandle());
startActivity(intent);
} else if (info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
@@ -1144,18 +1149,28 @@
componentKeys,
findExactPairMatch,
foundTasks -> {
- @Nullable Task foundTask = foundTasks.get(0);
+ @Nullable Task foundTask = foundTasks[0];
if (foundTask != null) {
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
- if (foundTaskView != null
- && foundTaskView.isVisibleToUser()) {
- TestLogging.recordEvent(
- TestProtocol.SEQUENCE_MAIN, "start: taskbarAppIcon");
- foundTaskView.launchTasks();
- return;
+ if (foundTaskView != null) {
+ // The foundTaskView contains the 1-2 taskIds we are looking for.
+ // If we are already in-app and running the correct tasks, no need
+ // to do anything.
+ if (FeatureFlags.enableAppPairs()
+ && isAlreadyInApp(foundTaskView.getTaskIds())) {
+ return;
+ }
+ // If we are in Overview and the TaskView tile is visible, expand that
+ // tile.
+ if (foundTaskView.isVisibleToUser()) {
+ TestLogging.recordEvent(
+ TestProtocol.SEQUENCE_MAIN, "start: taskbarAppIcon");
+ foundTaskView.launchTasks();
+ return;
+ }
}
}
-
+ // If none of the above cases apply, launch a new app or app pair.
if (findExactPairMatch) {
// We did not find the app pair we were looking for, so launch one.
recents.getSplitSelectController().getAppPairsController().launchAppPair(
@@ -1167,6 +1182,27 @@
);
}
+ /**
+ * Checks if a given list of taskIds are all already running in-app.
+ */
+ private boolean isAlreadyInApp(int[] ids) {
+ if (mControllers.uiController.isInOverview()) {
+ return false;
+ }
+
+ RunningTaskInfo[] currentlyRunningTasks = ActivityManagerWrapper.getInstance()
+ .getRunningTasks(false /* filterOnlyVisibleRecents */);
+ Set<Integer> currentlyRunningIds = Arrays.stream(currentlyRunningTasks)
+ .map(task -> task.taskId).collect(Collectors.toSet());
+
+ for (int id : ids) {
+ if (id != ActivityTaskManager.INVALID_TASK_ID && !currentlyRunningIds.contains(id)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private void startItemInfoActivity(ItemInfo info) {
Intent intent = new Intent(info.getIntent())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 6ddf9e9..faa67be 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_DRAGDROP;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -517,6 +518,9 @@
// Note, this must be done last to ensure no AutohideSuspendFlags are active, as
// that will prevent us from stashing until the timeout.
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
+
+ mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo)
+ .log(LAUNCHER_APP_LAUNCH_DRAGDROP);
}
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 831bc11..a2e5e81 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -25,6 +25,9 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
+import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
+import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
+import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
@@ -55,7 +58,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -71,7 +73,6 @@
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
-import com.android.wm.shell.Flags;
import java.io.PrintWriter;
import java.util.StringJoiner;
@@ -135,7 +136,17 @@
* We use WindowManager's ComponentCallbacks() for internal UI changes (similar to an Activity)
* which comes via a different channel
*/
- private final OnIDPChangeListener mIdpChangeListener = c -> recreateTaskbar();
+ private final RecreationListener mRecreationListener = new RecreationListener();
+
+ private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
+ @Override
+ public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+ if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE
+ | CHANGE_TASKBAR_PINNING)) != 0) {
+ recreateTaskbar();
+ }
+ }
+ }
private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> recreateTaskbar();
private boolean mUserUnlocked = false;
@@ -329,7 +340,10 @@
return;
}
- if (mActivity != null && mActivity.isResumed() && !mActivity.isInState(OVERVIEW)) {
+ if (mActivity != null
+ && mActivity.isResumed()
+ && !mActivity.isInState(OVERVIEW)
+ && !(mActivity instanceof QuickstepLauncher l && l.areFreeformTasksVisible())) {
mContext.startActivity(homeAllAppsIntent);
return;
}
@@ -353,7 +367,7 @@
*/
public void onUserUnlocked() {
mUserUnlocked = true;
- LauncherAppState.getIDP(mContext).addOnChangeListener(mIdpChangeListener);
+ DisplayController.INSTANCE.get(mContext).addChangeListener(mRecreationListener);
recreateTaskbar();
addTaskbarRootViewToWindow();
}
@@ -551,7 +565,7 @@
() -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext));
destroyExistingTaskbar();
if (mUserUnlocked) {
- LauncherAppState.getIDP(mContext).removeOnChangeListener(mIdpChangeListener);
+ DisplayController.INSTANCE.get(mContext).removeChangeListener(mRecreationListener);
}
SettingsCache.INSTANCE.get(mContext)
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index b3a8b93..2f7f6f3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -21,7 +21,9 @@
import static com.android.app.animation.Interpolators.FINAL_FRAME;
import static com.android.app.animation.Interpolators.INSTANT;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
@@ -57,6 +59,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.launcher3.Alarm;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
@@ -930,19 +933,27 @@
}
/**
- * We stash when IME or IME switcher is showing AND NOT
- * * in small screen AND
- * * 3 button nav AND
- * * landscape (or seascape)
- * We do not stash if taskbar is transient or hardware keyboard is active.
+ * We stash when IME or IME switcher is showing.
+ *
+ * <p>Do not stash if in small screen, with 3 button nav, and in landscape (or seascape).
+ * <p>Do not stash if taskbar is transient.
+ * <p>Do not stash if hardware keyboard is attached and taskbar is pinned.
*/
private boolean shouldStashForIme() {
- if (DisplayController.isTransientTaskbar(mActivity) || mActivity.isHardwareKeyboard()) {
+ if (DisplayController.isTransientTaskbar(mActivity)) {
return false;
}
- return (mIsImeShowing || mIsImeSwitcherShowing) &&
- !(mActivity.isPhoneMode() && mActivity.isThreeButtonNav()
- && mActivity.getDeviceProfile().isLandscape);
+ // Do not stash if in small screen, with 3 button nav, and in landscape.
+ if (mActivity.isPhoneMode() && mActivity.isThreeButtonNav()
+ && mActivity.getDeviceProfile().isLandscape) {
+ return false;
+ }
+ // Do not stash if pinned taskbar and hardware keyboard is attached.
+ if (mActivity.isHardwareKeyboard() && enableTaskbarPinning()
+ && LauncherPrefs.get(mActivity).get(TASKBAR_PINNING)) {
+ return false;
+ }
+ return mIsImeShowing || mIsImeSwitcherShowing;
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index df2a43b..7edf0d3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -219,7 +219,7 @@
Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
false /* findExactPairMatch */,
foundTasks -> {
- @Nullable Task foundTask = foundTasks.get(0);
+ @Nullable Task foundTask = foundTasks[0];
splitSelectSource.alreadyRunningTaskId = foundTask == null
? INVALID_TASK_ID
: foundTask.key.id;
@@ -238,7 +238,7 @@
Collections.singletonList(info.getComponentKey()),
false /* findExactPairMatch */,
foundTasks -> {
- @Nullable Task foundTask = foundTasks.get(0);
+ @Nullable Task foundTask = foundTasks[0];
if (foundTask != null) {
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
// TODO (b/266482558): This additional null check is needed because there
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 7583cc1..a96ee1f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -22,10 +22,8 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
-import androidx.core.view.children
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.util.DimensionUtils
import com.android.systemui.shared.rotation.RotationButton
open class PhoneLandscapeNavLayoutter(
@@ -48,43 +46,62 @@
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
- // TODO(b/230395757): Polish pending, this is just to make it usable
- val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
- val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile,
- resources, context.isPhoneMode)
- navButtonContainer.removeAllViews()
- navButtonContainer.orientation = LinearLayout.VERTICAL
+ val totalHeight = context.deviceProfile.heightPx
+ val homeButtonHeight = resources.getDimensionPixelSize(
+ R.dimen.taskbar_phone_home_button_size)
+ val roundedCornerContentMargin = resources.getDimensionPixelSize(
+ R.dimen.taskbar_phone_rounded_corner_content_margin)
+ val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+ val contentWidth = totalHeight - roundedCornerContentMargin * 2 - contentPadding * 2
+
+ // left:back:space(reserved for home):overview:right = 0.25:0.5:1:0.5:0.25
+ val contextualButtonHeight = contentWidth / (0.25f + 0.5f + 1f + 0.5f + 0.25f) * 0.25f
+ val sideButtonHeight = contextualButtonHeight * 2
+ val navButtonContainerHeight = contentWidth - contextualButtonHeight * 2
val navContainerParams = FrameLayout.LayoutParams(
- taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
+ ViewGroup.LayoutParams.MATCH_PARENT, navButtonContainerHeight.toInt())
navContainerParams.apply {
- topMargin = endStartMargins
- bottomMargin = endStartMargins
+ topMargin =
+ (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
+ bottomMargin =
+ (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
marginEnd = 0
marginStart = 0
}
- navButtonContainer.layoutParams = navContainerParams
- navButtonContainer.gravity = Gravity.CENTER
+ // Ensure order of buttons is correct
+ navButtonContainer.removeAllViews()
+ navButtonContainer.orientation = LinearLayout.VERTICAL
addThreeButtons()
+ navButtonContainer.layoutParams = navContainerParams
+ navButtonContainer.gravity = Gravity.CENTER
+
// Add the spaces in between the nav buttons
- val spaceInBetween: Int =
- resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
- navButtonContainer.children.forEachIndexed { i, navButton ->
+ val spaceInBetween = (navButtonContainerHeight - homeButtonHeight -
+ sideButtonHeight * 2) / 2.0f
+ for (i in 0 until navButtonContainer.childCount) {
+ val navButton = navButtonContainer.getChildAt(i)
val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
- buttonLayoutParams.weight = 1f
+ val margin = (spaceInBetween / 2).toInt()
when (i) {
0 -> {
- buttonLayoutParams.bottomMargin = spaceInBetween / 2
+ // First button
+ buttonLayoutParams.bottomMargin = margin
+ buttonLayoutParams.height = sideButtonHeight.toInt()
}
navButtonContainer.childCount - 1 -> {
- buttonLayoutParams.topMargin = spaceInBetween / 2
+ // Last button
+ buttonLayoutParams.topMargin = margin
+ buttonLayoutParams.height = sideButtonHeight.toInt()
}
else -> {
- buttonLayoutParams.bottomMargin = spaceInBetween / 2
- buttonLayoutParams.topMargin = spaceInBetween / 2
+ // other buttons
+ buttonLayoutParams.topMargin = margin
+ buttonLayoutParams.bottomMargin = margin
+ buttonLayoutParams.height = homeButtonHeight
}
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index 4388ce6..22d9f82 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -24,7 +24,6 @@
import android.widget.LinearLayout
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.util.DimensionUtils
import com.android.systemui.shared.rotation.RotationButton
class PhonePortraitNavLayoutter(
@@ -47,26 +46,33 @@
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
- // TODO(b/230395757): Polish pending, this is just to make it usable
- val taskbarDimensions =
- DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile, resources,
- context.isPhoneMode)
- val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
+ val totalWidth = context.deviceProfile.widthPx
+ val homeButtonWidth = resources.getDimensionPixelSize(R.dimen.taskbar_phone_home_button_size)
+ val roundedCornerContentMargin = resources.getDimensionPixelSize(
+ R.dimen.taskbar_phone_rounded_corner_content_margin)
+ val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+ val contentWidth = totalWidth - roundedCornerContentMargin * 2 - contentPadding * 2
+
+ // left:back:space(reserved for home):overview:right = 0.25:0.5:1:0.5:0.25
+ val contextualButtonWidth = contentWidth / (0.25f + 0.5f + 1f + 0.5f + 0.25f) * 0.25f
+ val sideButtonWidth = contextualButtonWidth * 2
+ val navButtonContainerWidth = contentWidth - contextualButtonWidth * 2
+
+ val navContainerParams = FrameLayout.LayoutParams(navButtonContainerWidth.toInt(),
+ ViewGroup.LayoutParams.MATCH_PARENT)
+ navContainerParams.apply {
+ topMargin = 0
+ bottomMargin = 0
+ marginEnd =
+ (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
+ marginStart =
+ (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
+ }
// Ensure order of buttons is correct
navButtonContainer.removeAllViews()
navButtonContainer.orientation = LinearLayout.HORIZONTAL
- val navContainerParams = FrameLayout.LayoutParams(
- taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
- navContainerParams.apply {
- topMargin = 0
- bottomMargin = 0
- marginEnd = endStartMargins
- marginStart = endStartMargins
- }
-
- // Swap recents and back button in case we were landscape prior to this
navButtonContainer.addView(backButton)
navButtonContainer.addView(homeButton)
navButtonContainer.addView(recentsButton)
@@ -75,25 +81,28 @@
navButtonContainer.gravity = Gravity.CENTER
// Add the spaces in between the nav buttons
- val spaceInBetween =
- resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
+ val spaceInBetween = (navButtonContainerWidth - homeButtonWidth -
+ sideButtonWidth * 2) / 2.0f
for (i in 0 until navButtonContainer.childCount) {
val navButton = navButtonContainer.getChildAt(i)
val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
- buttonLayoutParams.weight = 1f
+ val margin = (spaceInBetween / 2).toInt()
when (i) {
0 -> {
// First button
- buttonLayoutParams.marginEnd = spaceInBetween / 2
+ buttonLayoutParams.marginEnd = margin
+ buttonLayoutParams.width = sideButtonWidth.toInt()
}
navButtonContainer.childCount - 1 -> {
// Last button
- buttonLayoutParams.marginStart = spaceInBetween / 2
+ buttonLayoutParams.marginStart = margin
+ buttonLayoutParams.width = sideButtonWidth.toInt()
}
else -> {
// other buttons
- buttonLayoutParams.marginStart = spaceInBetween / 2
- buttonLayoutParams.marginEnd = spaceInBetween / 2
+ buttonLayoutParams.marginStart = margin
+ buttonLayoutParams.marginEnd = margin
+ buttonLayoutParams.width = homeButtonWidth
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 4a26559..784c560 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,13 +17,16 @@
package com.android.launcher3.uioverrides;
import android.app.ActivityOptions;
+import android.app.PendingIntent;
import android.app.Person;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherUserInfo;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -31,9 +34,12 @@
import com.android.launcher3.Flags;
import com.android.launcher3.Utilities;
+import com.android.launcher3.proxy.ProxyActivityStarter;
+import com.android.launcher3.util.StartActivityParams;
import com.android.launcher3.util.UserIconInfo;
import com.android.quickstep.util.FadeOutRemoteTransition;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -102,6 +108,44 @@
return users;
}
+ /**
+ * Returns the list of the system packages that are installed at user creation.
+ * An empty list denotes that all system packages are installed for that user at creation.
+ */
+ public static List<String> getPreInstalledSystemPackages(Context context, UserHandle user) {
+ LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+ if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()
+ && Flags.privateSpaceSysAppsSeparation()) {
+ return launcherApps.getPreInstalledSystemPackages(user);
+ } else {
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * Returns an intent which can be used to start the App Market activity (Installer
+ * Activity).
+ */
+ public static Intent getAppMarketActivityIntent(Context context, String packageName,
+ UserHandle user) {
+ LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+ if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()
+ && Flags.privateSpaceAppInstallerButton()) {
+ StartActivityParams params = new StartActivityParams((PendingIntent) null, 0);
+ params.intentSender = launcherApps.getAppMarketActivityIntent(packageName, user);
+ return ProxyActivityStarter.getLaunchIntent(context, params);
+ } else {
+ return new Intent(Intent.ACTION_VIEW)
+ .setData(new Uri.Builder()
+ .scheme("market")
+ .authority("details")
+ .appendQueryParameter("id", packageName)
+ .build())
+ .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
+ .authority(context.getPackageName()).build());
+ }
+ }
+
private static class NoopDrawable extends ColorDrawable {
@Override
public int getIntrinsicHeight() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 9438e00..a065387 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -644,7 +644,7 @@
Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
false /* findExactPairMatch */,
foundTasks -> {
- @Nullable Task foundTask = foundTasks.get(0);
+ @Nullable Task foundTask = foundTasks[0];
boolean taskWasFound = foundTask != null;
splitSelectSource.alreadyRunningTaskId = taskWasFound
? foundTask.key.id
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 7c24ba8..0f88aac 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -287,7 +287,7 @@
mBackInProgress = true;
RemoteAnimationTarget appTarget = backEvent.getDepartingAnimationTarget();
- if (appTarget == null) {
+ if (appTarget == null || appTarget.leash == null || !appTarget.leash.isValid()) {
return;
}
@@ -456,6 +456,7 @@
mBackInProgress /* fromPredictiveBack */);
startTransitionAnimations(pair.first, pair.second);
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
+ customizeStatusBarAppearance(true);
}
private void finishAnimation() {
diff --git a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
index 645ecf4..fc450f0 100644
--- a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
+++ b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
@@ -29,8 +29,8 @@
@BackupRestoreDataType private const val DATA_TYPE_APP_PAIR = "app_pair"
}
- private val backupManager: BackupManager = BackupManager(context)
- private val restoreEventLogger: BackupRestoreEventLogger = backupManager.delayedRestoreLogger
+ private val restoreEventLogger: BackupRestoreEventLogger =
+ BackupManager(context).delayedRestoreLogger
/**
* For logging when multiple items of a given data type failed to restore.
@@ -73,6 +73,18 @@
}
/**
+ * Helper to log successfully restoring multiple items from the Favorites table.
+ *
+ * @param favoritesId The id of the item type from [Favorites] that was restored.
+ * @param count number of items that restored.
+ */
+ override fun logFavoritesItemsRestored(favoritesId: Int, count: Int) {
+ if (Flags.enableLauncherBrMetrics()) {
+ restoreEventLogger.logItemsRestored(favoritesIdToDataType(favoritesId), count)
+ }
+ }
+
+ /**
* Helper to log a failure to restore a single item from the Favorites table.
*
* @param favoritesId The id of the item type from [Favorites] that was not restored.
@@ -114,7 +126,7 @@
*/
override fun reportLauncherRestoreResults() {
if (Flags.enableLauncherBrMetrics()) {
- backupManager.reportDelayedRestoreResult(restoreEventLogger)
+ BackupManager(context).reportDelayedRestoreResult(restoreEventLogger)
}
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 0303791..9d942c5 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -149,6 +149,11 @@
case TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET:
runOnTISBinder(TouchInteractionService.TISBinder::refreshOverviewTarget);
return response;
+
+ case TestProtocol.REQUEST_RECREATE_TASKBAR:
+ // Allow null-pointer to catch illegal states.
+ runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
+ return response;
}
return super.call(method, arg, extras);
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 8e03a91..69db91b 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -206,8 +206,9 @@
}
}
- RemoteAnimationTarget[] nonAppTargets = SystemUiProxy.INSTANCE.get(mCtx)
- .onStartingSplitLegacy(appearedTaskTargets);
+ RemoteAnimationTarget[] nonAppTargets = ENABLE_SHELL_TRANSITIONS
+ ? null : SystemUiProxy.INSTANCE.get(mCtx).onStartingSplitLegacy(
+ appearedTaskTargets);
if (nonAppTargets == null) {
nonAppTargets = new RemoteAnimationTarget[0];
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index d1d2f97..86ba7ef 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -86,7 +86,6 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ConstantItem;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.EncryptionType;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
@@ -725,8 +724,7 @@
final int action = event.getActionMasked();
// Note this will create a new consumer every mouse click, as after ACTION_UP from the click
// an ACTION_HOVER_ENTER will fire as well.
- boolean isHoverActionWithoutConsumer =
- event.isHoverEvent() && (mUncheckedConsumer.getType() & TYPE_CURSOR_HOVER) == 0;
+ boolean isHoverActionWithoutConsumer = isHoverActionWithoutConsumer(event);
CompoundString reasonString = action == ACTION_DOWN
? new CompoundString("onMotionEvent: ") : CompoundString.NO_OP;
if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
@@ -846,6 +844,15 @@
traceToken.close();
}
+ private boolean isHoverActionWithoutConsumer(MotionEvent event) {
+ // Only process these events when taskbar is present.
+ TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
+ boolean isTaskbarPresent = tac != null && tac.getDeviceProfile().isTaskbarPresent
+ && !tac.isPhoneMode();
+ return event.isHoverEvent() && (mUncheckedConsumer.getType() & TYPE_CURSOR_HOVER) == 0
+ && isTaskbarPresent;
+ }
+
// Talkback generates hover events on touch, which we do not want to consume.
private boolean isCursorHoverEvent(MotionEvent event) {
return event.isHoverEvent() && event.getSource() == InputDevice.SOURCE_MOUSE;
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 3ca2531..0f3c029 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -130,7 +130,7 @@
Arrays.asList(app1Key, app2Key),
false /* findExactPairMatch */,
foundTasks -> {
- @Nullable Task foundTask1 = foundTasks.get(0);
+ @Nullable Task foundTask1 = foundTasks[0];
Intent task1Intent;
int task1Id;
if (foundTask1 != null) {
@@ -147,7 +147,7 @@
LAUNCHER_APP_PAIR_LAUNCH,
task1Id);
- @Nullable Task foundTask2 = foundTasks.get(1);
+ @Nullable Task foundTask2 = foundTasks[1];
if (foundTask2 != null) {
mSplitSelectStateController.setSecondTask(foundTask2);
} else {
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index b2d1b43..ad9f5ea 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -49,6 +49,7 @@
import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
import com.android.launcher3.statemanager.StatefulActivity
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
import com.android.launcher3.views.BaseDragLayer
import com.android.quickstep.TaskViewUtils
@@ -390,6 +391,14 @@
"trying to launch an app pair icon, but encountered an unexpected null"
}
+ // If launching an app pair from Taskbar inside of an app context, use fade-in animation
+ // TODO (b/316485863): Replace with desired app pair launch animation
+ if (launchingIconView.context is TaskbarActivityContext) {
+ composeFadeInSplitLaunchAnimator(
+ initialTaskId, secondTaskId, info, t, finishCallback)
+ return
+ }
+
composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
} else {
// Fallback case: simple fade-in animation
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index d5899e4..a9fa337 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -105,7 +105,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@@ -225,14 +225,14 @@
* tasks (i.e. searching for a running pair of tasks.)
*/
public void findLastActiveTasksAndRunCallback(@Nullable List<ComponentKey> componentKeys,
- boolean findExactPairMatch, Consumer<List<Task>> callback) {
+ boolean findExactPairMatch, Consumer<Task[]> callback) {
mRecentTasksModel.getTasks(taskGroups -> {
if (componentKeys == null || componentKeys.isEmpty()) {
- callback.accept(Collections.emptyList());
+ callback.accept(new Task[]{});
return;
}
- List<Task> lastActiveTasks = new ArrayList<>();
+ Task[] lastActiveTasks = new Task[componentKeys.size()];
if (findExactPairMatch) {
// Loop through tasks in reverse, since they are ordered with most-recent tasks last
@@ -240,32 +240,35 @@
GroupTask groupTask = taskGroups.get(i);
if (isInstanceOfAppPair(
groupTask, componentKeys.get(0), componentKeys.get(1))) {
- lastActiveTasks.add(groupTask.task1);
+ lastActiveTasks[0] = groupTask.task1;
break;
}
}
} else {
// For each key we are looking for, add to lastActiveTasks with the corresponding
// Task (or do nothing if not found).
- for (ComponentKey key : componentKeys) {
+ for (int i = 0; i < componentKeys.size(); i++) {
+ ComponentKey key = componentKeys.get(i);
Task lastActiveTask = null;
// Loop through tasks in reverse, since they are ordered with recent tasks last
- for (int i = taskGroups.size() - 1; i >= 0; i--) {
- GroupTask groupTask = taskGroups.get(i);
+ for (int j = taskGroups.size() - 1; j >= 0; j--) {
+ GroupTask groupTask = taskGroups.get(j);
Task task1 = groupTask.task1;
// Don't add duplicate Tasks
- if (isInstanceOfComponent(task1, key) && !lastActiveTasks.contains(task1)) {
+ if (isInstanceOfComponent(task1, key)
+ && !Arrays.asList(lastActiveTasks).contains(task1)) {
lastActiveTask = task1;
break;
}
Task task2 = groupTask.task2;
- if (isInstanceOfComponent(task2, key) && !lastActiveTasks.contains(task2)) {
+ if (isInstanceOfComponent(task2, key)
+ && !Arrays.asList(lastActiveTasks).contains(task2)) {
lastActiveTask = task2;
break;
}
}
- lastActiveTasks.add(lastActiveTask);
+ lastActiveTasks[i] = lastActiveTask;
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index ca9d13e..ebb6ba8 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3246,8 +3246,9 @@
anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp);
- if (mEnableDrawingLiveTile && taskView.isRunningTask()) {
+ if (taskView.isRunningTask()) {
anim.addOnFrameCallback(() -> {
+ if (!mEnableDrawingLiveTile) return;
runActionOnRemoteHandles(
remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
.taskSecondaryTranslation.value = mOrientationHandler
diff --git a/quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java
new file mode 100644
index 0000000..d6c1447
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.app.prediction.AppTarget;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.UserIconInfo;
+import com.android.launcher3.util.rule.StaticMockitoRule;
+import com.android.systemui.shared.system.SysUiStatsLog;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AppEventProducerTest {
+
+ private static final UserHandle MAIN_HANDLE = Process.myUserHandle();
+ private static final UserHandle PRIVATE_HANDLE = new UserHandle(11);
+
+ private static final UserIconInfo MAIN_ICON_INFO =
+ new UserIconInfo(MAIN_HANDLE, UserIconInfo.TYPE_MAIN);
+ private static final UserIconInfo PRIVATE_ICON_INFO =
+ new UserIconInfo(PRIVATE_HANDLE, UserIconInfo.TYPE_PRIVATE);
+
+ private Context mContext;
+ private AppEventProducer mAppEventProducer;
+ @Mock
+ private UserCache mUserCache;
+
+ @Rule
+ public final StaticMockitoRule mStaticMockitoRule = new StaticMockitoRule(UserCache.class);
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = new ActivityContextWrapper(getApplicationContext());
+ when(UserCache.getInstance(any(Context.class))).thenReturn(mUserCache);
+ mAppEventProducer = new AppEventProducer(mContext, null);
+ }
+
+ @Test
+ public void buildAppTarget_containsCorrectUser() {
+ when(mUserCache.getUserProfiles())
+ .thenReturn(Arrays.asList(MAIN_HANDLE, PRIVATE_HANDLE));
+ when(mUserCache.getUserInfo(any(UserHandle.class)))
+ .thenReturn(MAIN_ICON_INFO, PRIVATE_ICON_INFO);
+ ComponentName gmailComponentName = new ComponentName(mContext,
+ "com.android.launcher3.tests.Activity" + "Gmail");
+ AppInfo gmailAppInfo = new
+ AppInfo(gmailComponentName, "Gmail", MAIN_HANDLE, new Intent());
+ gmailAppInfo.container = CONTAINER_ALL_APPS;
+ gmailAppInfo.itemType = ITEM_TYPE_APPLICATION;
+
+ AppTarget gmailTarget = mAppEventProducer
+ .toAppTarget(buildItemInfoProtoForAppInfo(gmailAppInfo));
+
+ assert gmailTarget != null;
+ assertEquals(gmailTarget.getUser(), MAIN_HANDLE);
+
+ when(mUserCache.getUserInfo(any(UserHandle.class)))
+ .thenReturn(MAIN_ICON_INFO, PRIVATE_ICON_INFO);
+ AppInfo gmailAppInfoPrivate = new
+ AppInfo(gmailComponentName, "Gmail", PRIVATE_HANDLE, new Intent());
+ gmailAppInfoPrivate.container = CONTAINER_ALL_APPS;
+ gmailAppInfoPrivate.itemType = ITEM_TYPE_APPLICATION;
+
+ AppTarget gmailPrivateTarget = mAppEventProducer
+ .toAppTarget(buildItemInfoProtoForAppInfo(gmailAppInfoPrivate));
+
+ assert gmailPrivateTarget != null;
+ assertEquals(gmailPrivateTarget.getUser(), PRIVATE_HANDLE);
+ }
+
+ private LauncherAtom.ItemInfo buildItemInfoProtoForAppInfo(AppInfo appInfo) {
+ LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
+ if (appInfo.user.equals(PRIVATE_HANDLE)) {
+ itemBuilder.setUserType(SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_PRIVATE);
+ } else {
+ itemBuilder.setUserType(SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_MAIN);
+ }
+ itemBuilder.setApplication(LauncherAtom.Application.newBuilder()
+ .setComponentName(appInfo.componentName.flattenToShortString())
+ .setPackageName(appInfo.componentName.getPackageName()));
+ itemBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setAllAppsContainer(LauncherAtom.AllAppsContainer.getDefaultInstance())
+ .build());
+ return itemBuilder.build();
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index 3a5fb04..9ad360f 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -16,8 +16,6 @@
package com.android.quickstep;
-import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
-
import static org.junit.Assert.assertTrue;
import android.os.SystemProperties;
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index e0ee28d..edf95ea 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -28,7 +28,7 @@
import static com.android.launcher3.ui.AbstractLauncherUiTest.resolveSystemApp;
import static com.android.launcher3.ui.AbstractLauncherUiTest.startAppFast;
import static com.android.launcher3.ui.AbstractLauncherUiTest.startTestActivity;
-import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+import static com.android.launcher3.ui.TaplTestsLauncher3Test.getAppPackageName;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
@@ -223,6 +223,10 @@
private void pressHomeAndWaitForOverviewClose() {
mDevice.pressHome();
+ waitForRecentsActivityStop();
+ }
+
+ private void waitForRecentsActivityStop() {
Wait.atMost("Recents activity didn't stop",
() -> getFromRecents(recents -> !recents.isStarted()),
DEFAULT_UI_TIMEOUT, mLauncher);
@@ -276,6 +280,7 @@
// Test dismissing all tasks.
pressHomeAndGoToOverview().dismissAllTasks();
+ waitForRecentsActivityStop(); // dismissAllTasks() will close Recents
assertTrue("Fallback Launcher not visible", TestHelpers.wait(Until.hasObject(By.pkg(
mOtherLauncherActivity.packageName).text(FALLBACK_LAUNCHER_TITLE)), WAIT_TIME_MS));
}
diff --git a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
index a347156..b1ba4c6 100644
--- a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
@@ -47,7 +47,7 @@
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(580)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1445)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1435)
}
/**
@@ -69,7 +69,7 @@
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(550)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1080)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1070)
}
/**
@@ -90,7 +90,7 @@
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(759)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1468)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1455)
}
/**
@@ -115,7 +115,7 @@
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(1040)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1233)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1223)
}
/** This is a case when after setting the hotseat, the QSB width needs to be changed to fit */
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
index 829e54b..7191f70 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
@@ -33,7 +33,16 @@
public class TaplTestsKeyboardQuickSwitch extends AbstractQuickStepTest {
private enum TestSurface {
- HOME, LAUNCHED_APP, HOME_ALL_APPS, WIDGETS,
+ HOME(true),
+ LAUNCHED_APP(false),
+ HOME_ALL_APPS(true),
+ WIDGETS(true);
+
+ private final boolean mInitialFocusAtZero;
+
+ TestSurface(boolean initialFocusAtZero) {
+ mInitialFocusAtZero = initialFocusAtZero;
+ }
}
private enum TestCase {
@@ -172,13 +181,22 @@
kqs.dismiss();
break;
case LAUNCH_LAST_APP:
- kqs.launchFocusedAppTask(CALCULATOR_APP_PACKAGE);
+ kqs.launchFocusedAppTask(testSurface.mInitialFocusAtZero
+ ? getAppPackageName() : CALCULATOR_APP_PACKAGE);
break;
case LAUNCH_SELECTED_APP:
- kqs.moveFocusForward().launchFocusedAppTask(CALCULATOR_APP_PACKAGE);
+ kqs.moveFocusForward();
+ if (testSurface.mInitialFocusAtZero) {
+ kqs.moveFocusForward();
+ }
+ kqs.launchFocusedAppTask(CALCULATOR_APP_PACKAGE);
break;
case LAUNCH_OVERVIEW:
- kqs.moveFocusBackward().moveFocusBackward().launchFocusedOverviewTask();
+ kqs.moveFocusBackward();
+ if (!testSurface.mInitialFocusAtZero) {
+ kqs.moveFocusBackward();
+ }
+ kqs.launchFocusedOverviewTask();
break;
default:
throw new IllegalStateException("Cannot run test case: " + testCase);
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 929bd8e..de152fa 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -19,6 +19,7 @@
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
+import android.view.ContextThemeWrapper
import android.view.SurfaceControl.Transaction
import android.view.View
import android.window.TransitionInfo
@@ -26,6 +27,7 @@
import com.android.launcher3.apppairs.AppPairIcon
import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.views.GroupedTaskView
import com.android.quickstep.views.IconView
@@ -64,6 +66,8 @@
private val mockTaskIdAttributeContainer: TaskIdAttributeContainer = mock()
// AppPairIcon
private val mockAppPairIcon: AppPairIcon = mock()
+ private val mockContextThemeWrapper: ContextThemeWrapper = mock()
+ private val mockTaskbarActivityContext: TaskbarActivityContext = mock()
// SplitSelectSource
private val splitSelectSource: SplitConfigurationOptions.SplitSelectSource = mock()
@@ -247,6 +251,7 @@
@Test
fun playsAppropriateSplitLaunchAnimation_playsIconLaunchCorrectly() {
val spySplitAnimationController = spy(splitAnimationController)
+ whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper)
doNothing()
.whenever(spySplitAnimationController)
.composeIconSplitLaunchAnimator(any(), any(), any(), any())
@@ -271,6 +276,33 @@
}
@Test
+ fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarContextCorrectly() {
+ val spySplitAnimationController = spy(splitAnimationController)
+ whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
+ doNothing()
+ .whenever(spySplitAnimationController)
+ .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any())
+
+ spySplitAnimationController.playSplitLaunchAnimation(
+ null /* launchingTaskView */,
+ mockAppPairIcon,
+ taskId,
+ taskId2,
+ null /* apps */,
+ null /* wallpapers */,
+ null /* nonApps */,
+ stateManager,
+ depthController,
+ transitionInfo,
+ transaction,
+ {} /* finishCallback */
+ )
+
+ verify(spySplitAnimationController)
+ .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any())
+ }
+
+ @Test
fun playsAppropriateSplitLaunchAnimation_playsFadeInLaunchCorrectly() {
val spySplitAnimationController = spy(splitAnimationController)
doNothing()
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index f41ac48..1e39a34 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -107,7 +107,7 @@
// Assertions happen in the callback we get from what we pass into
// #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<List<Task>> { assertNull("No tasks should have matched", it[0] /*task*/) }
+ Consumer<Array<Task>> { assertNull("No tasks should have matched", it[0] /*task*/) }
// Capture callback from recentsModel#getTasks()
val consumer =
@@ -148,7 +148,7 @@
// Assertions happen in the callback we get from what we pass into
// #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<List<Task>> {
+ Consumer<Array<Task>> {
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
@@ -201,7 +201,7 @@
// Assertions happen in the callback we get from what we pass into
// #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<List<Task>> { assertNull("No tasks should have matched", it[0] /*task*/) }
+ Consumer<Array<Task>> { assertNull("No tasks should have matched", it[0] /*task*/) }
// Capture callback from recentsModel#getTasks()
val consumer =
@@ -244,7 +244,7 @@
// Assertions happen in the callback we get from what we pass into
// #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<List<Task>> {
+ Consumer<Array<Task>> {
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
@@ -298,7 +298,7 @@
// Assertions happen in the callback we get from what we pass into
// #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<List<Task>> {
+ Consumer<Array<Task>> {
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
@@ -350,7 +350,7 @@
// Assertions happen in the callback we get from what we pass into
// #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<List<Task>> {
+ Consumer<Array<Task>> {
assertEquals("Expected array length 2", 2, it.size)
assertNull("No tasks should have matched", it[0] /*task*/)
assertEquals(
@@ -403,7 +403,7 @@
// Assertions happen in the callback we get from what we pass into
// #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<List<Task>> {
+ Consumer<Array<Task>> {
assertEquals("Expected array length 2", 2, it.size)
assertEquals(
"ComponentName package mismatched",
@@ -459,7 +459,7 @@
// Assertions happen in the callback we get from what we pass into
// #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<List<Task>> {
+ Consumer<Array<Task>> {
assertEquals("Expected array length 2", 2, it.size)
assertEquals(
"ComponentName package mismatched",
@@ -532,8 +532,8 @@
// Assertions happen in the callback we get from what we pass into
// #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<List<Task>> {
- assertEquals("Expected array length 1", 1, it.size)
+ Consumer<Array<Task>> {
+ assertEquals("Expected array length 2", 2, it.size)
assertEquals("Found wrong task", it[0], groupTask2.task1)
}
diff --git a/res/color-night-v31/folder_preview_dark.xml b/res/color-night-v31/folder_preview_dark.xml
index 644d61a..6dd20a1 100644
--- a/res/color-night-v31/folder_preview_dark.xml
+++ b/res/color-night-v31/folder_preview_dark.xml
@@ -16,5 +16,5 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:color="@android:color/system_neutral1_900"
- android:lStar="17" />
+ android:lStar="12" />
</selector>
diff --git a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
index c502178..379e0e5 100644
--- a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
+++ b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
@@ -17,6 +17,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle" >
- <solid android:color="?androidprv:attr/colorSurfaceVariant"/>
+ <solid android:color="?androidprv:attr/materialColorOutlineVariant"/>
<corners android:radius="@dimen/bottom_sheet_handle_corner_radius" />
</shape>
diff --git a/res/drawable/widget_internal_focus_bg.xml b/res/drawable/widget_internal_focus_bg.xml
index 4d4bea6..b1f45a4 100644
--- a/res/drawable/widget_internal_focus_bg.xml
+++ b/res/drawable/widget_internal_focus_bg.xml
@@ -23,6 +23,7 @@
<item android:state_selected="true">
<shape android:shape="rectangle">
<stroke android:color="#fff" android:width="2dp" />
+ <corners android:radius="@dimen/focus_outline_radius" />
</shape>
</item>
</selector>
\ No newline at end of file
diff --git a/res/layout/work_apps_edu.xml b/res/layout/work_apps_edu.xml
index eeb7f4f..f557fb6 100644
--- a/res/layout/work_apps_edu.xml
+++ b/res/layout/work_apps_edu.xml
@@ -19,7 +19,7 @@
android:paddingTop="@dimen/work_edu_card_margin"
android:paddingBottom="@dimen/work_edu_card_bottom_margin"
android:gravity="center">
- <RelativeLayout
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
@@ -28,40 +28,33 @@
android:paddingEnd="@dimen/work_card_margin"
android:paddingStart="@dimen/work_card_margin"
android:paddingTop="@dimen/work_card_margin"
+ android:paddingBottom="@dimen/work_card_margin"
android:id="@+id/wrapper">
<TextView
style="@style/PrimaryHeadline"
android:textColor="?android:attr/textColorPrimary"
android:id="@+id/work_apps_paused_title"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/work_card_margin"
- android:layout_marginEnd="@dimen/work_card_margin"
+ android:layout_weight="1"
+ android:paddingEnd="@dimen/work_edu_card_text_end_margin"
android:text="@string/work_profile_edu_work_apps"
android:textDirection="locale"
android:textSize="18sp" />
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="@dimen/padded_rounded_button_height"
- android:orientation="horizontal">
- <FrameLayout
- android:layout_width="@dimen/rounded_button_width"
- android:layout_height="@dimen/rounded_button_width"
- android:layout_alignParentEnd="true"
- android:background="@drawable/rounded_action_button"
- android:padding="@dimen/rounded_button_padding">
- <ImageButton
- android:id="@+id/action_btn"
- android:layout_width="@dimen/x_icon_size"
- android:layout_height="@dimen/x_icon_size"
- android:layout_gravity="center"
- android:padding="@dimen/x_icon_padding"
- android:contentDescription="@string/accessibility_close"
- android:src="@drawable/ic_remove_no_shadow" />
- </FrameLayout>
- </RelativeLayout>
- </RelativeLayout>
-
-
-
-</com.android.launcher3.allapps.WorkEduCard>
\ No newline at end of file
+ <FrameLayout
+ android:layout_width="@dimen/rounded_button_width"
+ android:layout_height="@dimen/rounded_button_width"
+ android:background="@drawable/rounded_action_button"
+ android:padding="@dimen/rounded_button_padding">
+ <ImageButton
+ android:id="@+id/action_btn"
+ android:layout_width="@dimen/x_icon_size"
+ android:layout_height="@dimen/x_icon_size"
+ android:layout_gravity="center"
+ android:contentDescription="@string/accessibility_close"
+ android:padding="@dimen/x_icon_padding"
+ android:background="@android:color/transparent"
+ android:src="@drawable/ic_remove_no_shadow" />
+ </FrameLayout>
+ </LinearLayout>
+</com.android.launcher3.allapps.WorkEduCard>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 53d7d0c..db9631a 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -72,7 +72,7 @@
<color name="folder_background_dark">#1F2020</color>
<color name="folder_preview_light">#7FCFFF</color>
- <color name="folder_preview_dark">#2A2A2A</color>
+ <color name="folder_preview_dark">#1E1F20</color>
<color name="folder_pagination_color_light">#0B57D0</color>
<color name="folder_pagination_color_dark">#A8C7FA</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 62ce256..6d115b2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -155,6 +155,7 @@
<dimen name="work_edu_card_margin">16dp</dimen>
<dimen name="work_edu_card_radius">16dp</dimen>
<dimen name="work_edu_card_bottom_margin">26dp</dimen>
+ <dimen name="work_edu_card_text_end_margin">32dp</dimen>
<dimen name="work_apps_paused_button_stroke">1dp</dimen>
<dimen name="work_card_margin">24dp</dimen>
@@ -368,6 +369,11 @@
<!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
<dimen name="taskbar_size">0dp</dimen>
<dimen name="taskbar_phone_size">@*android:dimen/navigation_bar_frame_height</dimen>
+ <dimen name="taskbar_phone_home_button_size">80dp</dimen>
+ <dimen name="taskbar_phone_content_padding">8dp</dimen>
+ <dimen name="taskbar_phone_rounded_corner_content_margin">
+ @*android:dimen/rounded_corner_content_padding
+ </dimen>
<dimen name="taskbar_stashed_size">0dp</dimen>
<dimen name="qsb_widget_height">0dp</dimen>
<dimen name="qsb_shadow_height">0dp</dimen>
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 1ca7da9..53297f2 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -789,14 +789,16 @@
* width of the hotseat.
*/
private int calculateQsbWidth(int hotseatBorderSpace) {
+ int iconExtraSpacePx = iconSizePx - getIconVisibleSizePx(iconSizePx);
if (isQsbInline) {
int columns = getPanelCount() * inv.numColumns;
return getIconToIconWidthForColumns(columns)
- iconSizePx * numShownHotseatIcons
- - hotseatBorderSpace * numShownHotseatIcons;
+ - hotseatBorderSpace * numShownHotseatIcons
+ - iconExtraSpacePx;
} else {
int columns = inv.hotseatColumnSpan[mTypeIndex];
- return getIconToIconWidthForColumns(columns);
+ return getIconToIconWidthForColumns(columns) - iconExtraSpacePx;
}
}
@@ -1074,11 +1076,8 @@
}
private int getNormalizedIconDrawablePadding(int iconSizePx, int iconDrawablePadding) {
- // TODO(b/235886078): workaround needed because of this bug
- // Icons are 10% larger on XML than their visual size,
- // so remove that extra space to get labels closer to the correct padding
- int iconVisibleSizePx = Math.round(ICON_VISIBLE_AREA_FACTOR * iconSizePx);
- return Math.max(0, iconDrawablePadding - ((iconSizePx - iconVisibleSizePx) / 2));
+ return Math.max(0, iconDrawablePadding
+ - ((iconSizePx - getIconVisibleSizePx(iconSizePx)) / 2));
}
private int getNormalizedIconDrawablePadding() {
@@ -1091,8 +1090,7 @@
// so remove that extra space to get labels closer to the correct padding
int drawablePadding = (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3;
- int iconVisibleSizePx = Math.round(ICON_VISIBLE_AREA_FACTOR * folderChildIconSizePx);
- int iconSizeDiff = folderChildIconSizePx - iconVisibleSizePx;
+ int iconSizeDiff = folderChildIconSizePx - getIconVisibleSizePx(folderChildIconSizePx);
return Math.max(0, drawablePadding - iconSizeDiff / 2);
}
@@ -1788,7 +1786,8 @@
}
} else if (mIsScalableGrid) {
- int sideSpacing = (availableWidthPx - hotseatQsbWidth) / 2;
+ int iconExtraSpacePx = iconSizePx - getIconVisibleSizePx(iconSizePx);
+ int sideSpacing = (availableWidthPx - (hotseatQsbWidth + iconExtraSpacePx)) / 2;
hotseatBarPadding.set(sideSpacing,
0,
sideSpacing,
@@ -1827,13 +1826,24 @@
availableWidthPx - allAppsSpacing,
0 /* borderSpace */,
numShownAllAppsColumns);
- int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * allAppsIconSizePx);
- int iconAlignmentMargin = (cellWidth - iconVisibleSize) / 2;
+ int iconAlignmentMargin = (cellWidth - getIconVisibleSizePx(allAppsIconSizePx)) / 2;
return (Utilities.isRtl(context.getResources()) ? allAppsPadding.right
: allAppsPadding.left) + iconAlignmentMargin;
}
+ /**
+ * TODO(b/235886078): workaround needed because of this bug
+ * Icons are 10% larger on XML than their visual size, so remove that extra space to get
+ * some dimensions correct.
+ *
+ * When this bug is resolved this method will no longer be needed and we would be able to
+ * replace all instances where this method is called with iconSizePx.
+ */
+ private int getIconVisibleSizePx(int iconSizePx) {
+ return Math.round(ICON_VISIBLE_AREA_FACTOR * iconSizePx);
+ }
+
private int getAdditionalQsbSpace() {
return isQsbInline ? hotseatQsbWidth + hotseatBorderSpace : 0;
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e41a8a5..11dc6e2 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -52,8 +52,10 @@
import static com.android.launcher3.LauncherConstants.TraceEvents.ON_NEW_INTENT_EVT;
import static com.android.launcher3.LauncherConstants.TraceEvents.ON_RESUME_EVT;
import static com.android.launcher3.LauncherConstants.TraceEvents.ON_START_EVT;
+import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
@@ -63,6 +65,8 @@
import static com.android.launcher3.LauncherState.NO_SCALE;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_BIND_FAILURE;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_INVALID_LOCATION;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
@@ -162,6 +166,7 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.apppairs.AppPairIcon;
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
import com.android.launcher3.celllayout.CellPosMapper.TwoPanelCellPosMapper;
@@ -2158,9 +2163,15 @@
int newItemsScreenId = -1;
int end = items.size();
View newView = null;
+ LauncherRestoreEventLogger restoreEventLogger = null;
+ Boolean isRestoreFromBackup = (Boolean) LauncherPrefs.get(getApplicationContext())
+ .get(IS_FIRST_LOAD_AFTER_RESTORE);
+ if (isRestoreFromBackup) {
+ restoreEventLogger = LauncherRestoreEventLogger.Companion
+ .newInstance(getApplicationContext());
+ }
for (int i = 0; i < end; i++) {
final ItemInfo item = items.get(i);
-
// Short circuit if we are loading dock items for a configuration which has no dock
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
mHotseat == null) {
@@ -2187,12 +2198,17 @@
(FolderInfo) item);
break;
}
- case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ case ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: {
- view = inflateAppWidget((LauncherAppWidgetInfo) item);
+ view = inflateAppWidget((LauncherAppWidgetInfo) item, restoreEventLogger);
if (view == null) {
continue;
}
+ // Widgets have more checks when inflating, so we have to wait until here
+ // to mark restored, instead of logging in LoaderTask.
+ if (restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestored(item.itemType);
+ }
break;
}
default:
@@ -2216,6 +2232,10 @@
if (FeatureFlags.IS_STUDIO_BUILD) {
throw (new RuntimeException(desc));
} else {
+ if (restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(item.itemType,
+ RESTORE_ERROR_INVALID_LOCATION);
+ }
getModelWriter().deleteItemFromDatabase(item, desc);
continue;
}
@@ -2274,6 +2294,9 @@
} else if (focusFirstItemForAccessibility && viewToFocus != null) {
viewToFocus.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
+ if (restoreEventLogger != null) {
+ restoreEventLogger.reportLauncherRestoreResults();
+ }
workspace.requestLayout();
}
@@ -2281,14 +2304,15 @@
* Add the views for a widget to the workspace.
*/
public void bindAppWidget(LauncherAppWidgetInfo item) {
- View view = inflateAppWidget(item);
+ View view = inflateAppWidget(item, null);
if (view != null) {
mWorkspace.addInScreen(view, item);
mWorkspace.requestLayout();
}
}
- private View inflateAppWidget(LauncherAppWidgetInfo item) {
+ private View inflateAppWidget(LauncherAppWidgetInfo item,
+ @Nullable LauncherRestoreEventLogger restoreEventLogger) {
if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) {
item.providerName = QsbContainerView.getSearchComponentName(this);
if (item.providerName == null) {
@@ -2305,11 +2329,9 @@
}
TraceHelper.INSTANCE.beginSection("BIND_WIDGET_id=" + item.appWidgetId);
-
try {
final LauncherAppWidgetProviderInfo appWidgetInfo;
String removalReason = "";
-
if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
// If the provider is not ready, bind as a pending widget.
appWidgetInfo = null;
@@ -2349,6 +2371,10 @@
"Removing restored widget: id=" + item.appWidgetId
+ " belongs to component " + item.providerName + " user " + item.user
+ ", as the provider is null and " + removalReason);
+ if (restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ ITEM_TYPE_APPWIDGET, RESTORE_ERROR_BIND_FAILURE);
+ }
return null;
}
@@ -2419,6 +2445,10 @@
if (appWidgetInfo == null) {
FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
getModelWriter().deleteWidgetInfo(item, getAppWidgetHolder(), removalReason);
+ if (restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ ITEM_TYPE_APPWIDGET, RESTORE_ERROR_BIND_FAILURE);
+ }
return null;
}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 78056e6..51ba5c6 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -34,6 +34,7 @@
import com.android.launcher3.model.DeviceGridState
import com.android.launcher3.pm.InstallSessionHelper
import com.android.launcher3.provider.RestoreDbTask
+import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY
import com.android.launcher3.states.RotationHelper
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.MainThreadInitializedObject
@@ -417,6 +418,13 @@
InvariantDeviceProfile.TYPE_PHONE,
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
+ @JvmField
+ val IS_FIRST_LOAD_AFTER_RESTORE =
+ backedUpItem(
+ FIRST_LOAD_AFTER_RESTORE_KEY,
+ false,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
@JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
@JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
@JvmField
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index 2945979..fa840ed 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -21,9 +21,11 @@
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.AttributeSet
+import android.util.Log
import android.view.Gravity
import android.widget.FrameLayout
import com.android.launcher3.DeviceProfile
+import com.android.launcher3.model.data.WorkspaceItemInfo
/**
* A FrameLayout marking the area on an [AppPairIcon] where the visual icon will be drawn. One of
@@ -31,6 +33,8 @@
*/
class AppPairIconGraphic @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) {
+ private val TAG = "AppPairIconGraphic"
+
companion object {
// Design specs -- the below ratios are in relation to the size of a standard app icon.
private const val OUTER_PADDING_SCALE = 1 / 30f
@@ -61,8 +65,8 @@
private lateinit var parentIcon: AppPairIcon
private lateinit var appPairBackground: Drawable
- private lateinit var appIcon1: Drawable
- private lateinit var appIcon2: Drawable
+ private var appIcon1: Drawable? = null
+ private var appIcon2: Drawable? = null
fun init(grid: DeviceProfile, icon: AppPairIcon) {
// Calculate device-specific measurements
@@ -79,12 +83,24 @@
appPairBackground = AppPairIconBackground(context, this)
appPairBackground.setBounds(0, 0, backgroundSize.toInt(), backgroundSize.toInt())
+ applyIcons(parentIcon.info.contents)
+ }
+
+ /** Sets up app pair member icons for drawing. */
+ private fun applyIcons(contents: ArrayList<WorkspaceItemInfo>) {
+ // App pair should always contain 2 members; if not 2, return to avoid a crash loop
+ if (contents.size != 2) {
+ Log.w(TAG, "AppPair contents not 2, size: " + contents.size, Throwable())
+ return
+ }
+
appIcon1 = parentIcon.info.contents[0].newIcon(context)
appIcon2 = parentIcon.info.contents[1].newIcon(context)
- appIcon1.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
- appIcon2.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+ appIcon1?.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+ appIcon2?.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
}
+
/** Gets this icon graphic's bounds, with respect to the parent icon's coordinate system. */
fun getIconBounds(outBounds: Rect) {
outBounds.set(0, 0, backgroundSize.toInt(), backgroundSize.toInt())
@@ -118,7 +134,7 @@
} else {
canvas.translate(width / 2f - memberIconSize / 2f, innerPadding)
}
- appIcon1.draw(canvas)
+ appIcon1?.draw(canvas)
canvas.restore()
// Draw second icon
@@ -135,7 +151,7 @@
height - (innerPadding + memberIconSize)
)
}
- appIcon2.draw(canvas)
+ appIcon2?.draw(canvas)
canvas.restore()
}
}
diff --git a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
index 16b1854..bdac05d 100644
--- a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
+++ b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
@@ -14,6 +14,15 @@
companion object {
const val TAG = "LauncherRestoreEventLogger"
+ // Restore Errors
+ const val RESTORE_ERROR_PROFILE_DELETED = "user_profile_deleted"
+ const val RESTORE_ERROR_MISSING_INFO = "missing_information_when_loading"
+ const val RESTORE_ERROR_BIND_FAILURE = "binding_to_view_failed"
+ const val RESTORE_ERROR_INVALID_LOCATION = "invalid_size_or_location"
+ const val RESTORE_ERROR_SHORTCUT_NOT_FOUND = "shortcut_not_found"
+ const val RESTORE_ERROR_APP_NOT_INSTALLED = "app_not_installed"
+ const val RESTORE_ERROR_WIDGETS_DISABLED = "widgets_disabled"
+
fun newInstance(context: Context?): LauncherRestoreEventLogger {
return ResourceBasedOverride.Overrides.getObject(
LauncherRestoreEventLogger::class.java,
@@ -54,6 +63,16 @@
}
/**
+ * Helper to log successfully restoring multiple items from the Favorites table.
+ *
+ * @param favoritesId The id of the item type from [Favorites] that was restored.
+ * @param count number of items that restored.
+ */
+ open fun logFavoritesItemsRestored(favoritesId: Int, count: Int) {
+ // no-op
+ }
+
+ /**
* Helper to log a failure to restore a single item from the Favorites table.
*
* @param favoritesId The id of the item type from [Favorites] that was not restored.
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index f9d282c..aa6d1f2 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -305,7 +305,7 @@
// TODO(Block 17): Clean up flags
// Aconfig migration complete for ENABLE_TASKBAR_PINNING.
- private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(270396583,
+ private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(296231746,
"ENABLE_TASKBAR_PINNING", TEAMFOOD,
"Enables taskbar pinning to allow user to switch between transient and persistent "
+ "taskbar flavors");
@@ -476,10 +476,10 @@
// TODO(Block 33): Clean up flags
public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,
- "ENABLE_ALL_APPS_RV_PREINFLATION", TEAMFOOD,
+ "ENABLE_ALL_APPS_RV_PREINFLATION", ENABLED,
"Enables preinflating all apps icons to avoid scrolling jank.");
public static final BooleanFlag ALL_APPS_GONE_VISIBILITY = getDebugFlag(291651514,
- "ALL_APPS_GONE_VISIBILITY", TEAMFOOD,
+ "ALL_APPS_GONE_VISIBILITY", ENABLED,
"Set all apps container view's hidden visibility to GONE instead of INVISIBLE.");
// TODO(Block 34): Empty block
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 683354b..ec6b94d 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -259,7 +259,7 @@
query += " or " + LauncherSettings.Favorites.SCREEN + " = "
+ Workspace.SECOND_SCREEN_ID;
}
- loadWorkspace(new ArrayList<>(), query, null);
+ loadWorkspace(new ArrayList<>(), query, null, null);
final SparseArray<Size> spanInfo =
getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index d8388c2..6651fa0 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -117,6 +117,9 @@
@UiEvent(doc = "Task launched from overview using SWIPE DOWN")
LAUNCHER_TASK_LAUNCH_SWIPE_DOWN(340),
+ @UiEvent(doc = "App launched by dragging and dropping, probably from taskbar")
+ LAUNCHER_APP_LAUNCH_DRAGDROP(1552),
+
@UiEvent(doc = "TASK dismissed from overview using SWIPE UP")
LAUNCHER_TASK_DISMISS_SWIPE_UP(341),
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index efd5574..af66431 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -262,7 +262,8 @@
String srcTableName, String destTableName) {
int id = copyEntryAndUpdate(helper, entry, srcTableName, destTableName);
- if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+ || entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
for (Set<Integer> itemIds : entry.mFolderItems.values()) {
for (int itemId : itemIds) {
copyEntryAndUpdate(helper, itemId, id, srcTableName, destTableName);
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index a98ec64..8dc2ab3 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -17,9 +17,16 @@
package com.android.launcher3.model;
import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
+import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_APP_NOT_INSTALLED;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_INVALID_LOCATION;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_MISSING_INFO;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_PROFILE_DELETED;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_SHORTCUT_NOT_FOUND;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_WIDGETS_DISABLED;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
@@ -69,6 +76,7 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderGridOrganizer;
@@ -134,6 +142,7 @@
private final AllAppsList mBgAllAppsList;
protected final BgDataModel mBgDataModel;
private final ModelDelegate mModelDelegate;
+ private boolean mIsRestoreFromBackup;
private FirstScreenBroadcast mFirstScreenBroadcast;
@@ -148,7 +157,6 @@
private final IconCache mIconCache;
private final UserManagerState mUserManagerState;
-
protected final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap = new ArrayMap<>();
private Map<ShortcutKey, ShortcutInfo> mShortcutKeyToPinnedShortcuts;
@@ -172,7 +180,6 @@
mBgDataModel = bgModel;
mModelDelegate = modelDelegate;
mLauncherBinder = launcherBinder;
-
mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
mUserManager = mApp.getContext().getSystemService(UserManager.class);
mUserCache = UserCache.getInstance(mApp.getContext());
@@ -221,9 +228,14 @@
TraceHelper.INSTANCE.beginSection(TAG);
LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
+ mIsRestoreFromBackup =
+ (Boolean) LauncherPrefs.get(mApp.getContext()).get(IS_FIRST_LOAD_AFTER_RESTORE);
+ LauncherRestoreEventLogger restoreEventLogger = LauncherRestoreEventLogger
+ .Companion.newInstance(mApp.getContext());
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
+
List<ShortcutInfo> allShortcuts = new ArrayList<>();
- loadWorkspace(allShortcuts, "", memoryLogger);
+ loadWorkspace(allShortcuts, "", memoryLogger, restoreEventLogger);
// Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
// sanitizeData should not be invoked if the workspace is loaded from a db different
@@ -314,8 +326,8 @@
mLauncherBinder.bindWidgets();
logASplit("bindWidgets");
verifyNotStopped();
-
LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext());
+
if (SMARTSPACE_AS_A_WIDGET.get() && prefs.get(SHOULD_SHOW_SMARTSPACE)) {
mLauncherBinder.bindSmartspaceWidget();
// Turn off pref.
@@ -349,6 +361,11 @@
mModelDelegate.modelLoadComplete();
transaction.commit();
memoryLogger.clearLogs();
+ if (mIsRestoreFromBackup) {
+ restoreEventLogger.reportLauncherRestoreResults();
+ mIsRestoreFromBackup = false;
+ LauncherPrefs.get(mApp.getContext()).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false));
+ }
} catch (CancellationException e) {
// Loader stopped, ignore
logASplit("Cancelled");
@@ -367,10 +384,12 @@
protected void loadWorkspace(
List<ShortcutInfo> allDeepShortcuts,
String selection,
- LoaderMemoryLogger memoryLogger) {
+ LoaderMemoryLogger memoryLogger,
+ @Nullable LauncherRestoreEventLogger restoreEventLogger
+ ) {
Trace.beginSection("LoadWorkspace");
try {
- loadWorkspaceImpl(allDeepShortcuts, selection, memoryLogger);
+ loadWorkspaceImpl(allDeepShortcuts, selection, memoryLogger, restoreEventLogger);
} finally {
Trace.endSection();
}
@@ -391,7 +410,8 @@
private void loadWorkspaceImpl(
List<ShortcutInfo> allDeepShortcuts,
String selection,
- @Nullable LoaderMemoryLogger memoryLogger) {
+ @Nullable LoaderMemoryLogger memoryLogger,
+ @Nullable LauncherRestoreEventLogger restoreEventLogger) {
final Context context = mApp.getContext();
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
final boolean isSafeMode = pmHelper.isSafeMode();
@@ -458,8 +478,8 @@
List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos = new ArrayList<>();
while (!mStopped && c.moveToNext()) {
- processWorkspaceItem(c, memoryLogger, installingPkgs, isSdCardReady,
- tempPackageKey, widgetHelper, pmHelper,
+ processWorkspaceItem(c, memoryLogger, restoreEventLogger, installingPkgs,
+ isSdCardReady, tempPackageKey, widgetHelper, pmHelper,
iconRequestInfos, unlockedUsers, isSafeMode, allDeepShortcuts);
}
tryLoadWorkspaceIconsInBulk(iconRequestInfos);
@@ -518,6 +538,7 @@
private void processWorkspaceItem(LoaderCursor c,
LoaderMemoryLogger memoryLogger,
+ @Nullable LauncherRestoreEventLogger restoreEventLogger,
HashMap<PackageUserKey, SessionInfo> installingPkgs,
boolean isSdCardReady,
PackageUserKey tempPackageKey,
@@ -531,7 +552,11 @@
try {
if (c.user == null) {
// User has been deleted, remove the item.
- c.markDeleted("User has been deleted");
+ c.markDeleted("User of this item has been deleted");
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_PROFILE_DELETED);
+ }
return;
}
@@ -542,6 +567,10 @@
Intent intent = c.parseIntent();
if (intent == null) {
c.markDeleted("Invalid or null intent");
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_MISSING_INFO);
+ }
return;
}
@@ -552,6 +581,10 @@
if (TextUtils.isEmpty(targetPkg)) {
c.markDeleted("Shortcuts can't have null package");
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_MISSING_INFO);
+ }
return;
}
@@ -569,6 +602,9 @@
if (mLauncherApps.isActivityEnabled(cn, c.user)) {
// no special handling necessary for this item
c.markRestored();
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestored(c.itemType);
+ }
} else {
// Gracefully try to find a fallback activity.
intent = pmHelper.getAppLaunchIntent(targetPkg, c.user);
@@ -579,7 +615,11 @@
intent.toUri(0)).commit();
cn = intent.getComponent();
} else {
- c.markDeleted("Unable to find a launch target");
+ c.markDeleted("Intent null, unable to find a launch target");
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_MISSING_INFO);
+ }
return;
}
}
@@ -606,6 +646,10 @@
} else {
c.markDeleted("removing app that is not restored and not "
+ "installing. package: " + targetPkg);
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_APP_NOT_INSTALLED);
+ }
return;
}
} else if (pmHelper.isAppOnSdcard(targetPkg, c.user)) {
@@ -623,6 +667,10 @@
} else {
// Do not wait for external media load anymore.
c.markDeleted("Invalid package removed: " + targetPkg);
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_APP_NOT_INSTALLED);
+ }
return;
}
}
@@ -652,8 +700,12 @@
ShortcutInfo pinnedShortcut = mShortcutKeyToPinnedShortcuts.get(key);
if (pinnedShortcut == null) {
// The shortcut is no longer valid.
- c.markDeleted("Pinned shortcut not found for package: "
- + key.getPackageName());
+ c.markDeleted("Pinned shortcut not found from request."
+ + " package=" + key.getPackageName() + ", user=" + c.user);
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_SHORTCUT_NOT_FOUND);
+ }
return;
}
info = new WorkspaceItemInfo(pinnedShortcut, mApp.getContext());
@@ -672,6 +724,9 @@
info = c.loadSimpleWorkspaceItem();
info.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
}
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestored(c.itemType);
+ }
} else { // item type == ITEM_TYPE_SHORTCUT
info = c.loadSimpleWorkspaceItem();
@@ -751,13 +806,19 @@
// no special handling required for restored folders
c.markRestored();
-
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestored(c.itemType);
+ }
c.checkAndAddItem(folderInfo, mBgDataModel, memoryLogger);
break;
case Favorites.ITEM_TYPE_APPWIDGET:
if (WidgetsModel.GO_DISABLE_WIDGETS) {
c.markDeleted("Only legacy shortcuts can have null package");
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_WIDGETS_DISABLED);
+ }
return;
}
// Follow through
@@ -774,6 +835,10 @@
component = QsbContainerView.getSearchComponentName(mApp.getContext());
if (component == null) {
c.markDeleted("Discarding SearchWidget without packagename ");
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_MISSING_INFO);
+ }
return;
}
} else {
@@ -799,6 +864,10 @@
final boolean isProviderReady = isValidProvider(provider);
if (!isSafeMode && !customWidget && wasProviderReady && !isProviderReady) {
c.markDeleted("Deleting widget that isn't installed anymore: " + provider);
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_APP_NOT_INSTALLED);
+ }
} else {
LauncherAppWidgetInfo appWidgetInfo;
if (isProviderReady) {
@@ -841,6 +910,10 @@
|= LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
} else if (!isSafeMode) {
c.markDeleted("Unrestored widget removed: " + component);
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_APP_NOT_INSTALLED);
+ }
return;
}
@@ -862,6 +935,10 @@
if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) {
c.markDeleted("Widget has invalid size: "
+ appWidgetInfo.spanX + "x" + appWidgetInfo.spanY);
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_INVALID_LOCATION);
+ }
return;
}
LauncherAppWidgetProviderInfo widgetProviderInfo =
@@ -875,12 +952,15 @@
+ "x" + appWidgetInfo.spanY + " minSpan="
+ widgetProviderInfo.minSpanX + "x"
+ widgetProviderInfo.minSpanY);
- logWidgetInfo(mApp.getInvariantDeviceProfile(),
- widgetProviderInfo);
+ logWidgetInfo(mApp.getInvariantDeviceProfile(), widgetProviderInfo);
}
if (!c.isOnWorkspaceOrHotseat()) {
c.markDeleted("Widget found where container != CONTAINER_DESKTOP"
+ "nor CONTAINER_HOTSEAT - ignoring!");
+ if (mIsRestoreFromBackup && restoreEventLogger != null) {
+ restoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ c.itemType, RESTORE_ERROR_INVALID_LOCATION);
+ }
return;
}
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index dc180d8..5141db9 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.Intent;
+import android.os.Process;
import androidx.annotation.Nullable;
@@ -26,7 +27,7 @@
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.pm.PackageInstallInfo;
-import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.uioverrides.ApiWrapper;
/**
* Represents an ItemInfo which also holds an icon.
@@ -216,7 +217,8 @@
String targetPackage = getTargetPackage();
return targetPackage != null
- ? new PackageManagerHelper(context).getMarketIntent(targetPackage)
+ ? ApiWrapper.getAppMarketActivityIntent(
+ context, targetPackage, Process.myUserHandle())
: null;
}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 69bba69..f39f806 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -7,6 +7,7 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
+import android.os.Process;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
@@ -22,6 +23,7 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
@@ -237,8 +239,9 @@
@Override
public void onClick(View view) {
- Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
- mItemInfo.getTargetComponent().getPackageName());
+ Intent intent = ApiWrapper.getAppMarketActivityIntent(view.getContext(),
+ mItemInfo.getTargetComponent().getPackageName(),
+ Process.myUserHandle());
mTarget.startActivitySafely(view, intent, mItemInfo);
AbstractFloatingView.closeAllOpenViews(mTarget);
}
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index dc8cd3a..fbe877d 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS;
+import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
@@ -83,13 +84,15 @@
private static final String TAG = "RestoreDbTask";
public static final String RESTORED_DEVICE_TYPE = "restored_task_pending";
+ public static final String FIRST_LOAD_AFTER_RESTORE_KEY = "first_load_after_restore";
private static final String INFO_COLUMN_NAME = "name";
private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value";
public static final String APPWIDGET_OLD_IDS = "appwidget_old_ids";
public static final String APPWIDGET_IDS = "appwidget_ids";
- private static final String[] DB_COLUMNS_TO_LOG = {"profileId", "title", "itemType", "screen",
+ @VisibleForTesting
+ public static final String[] DB_COLUMNS_TO_LOG = {"profileId", "title", "itemType", "screen",
"container", "cellX", "cellY", "spanX", "spanY", "intent", "appWidgetProvider",
"appWidgetId", "restored"};
@@ -340,9 +343,10 @@
* Marks the DB state as pending restoration
*/
public static void setPending(Context context) {
- FileLog.d(TAG, "Restore data received through full backup");
- LauncherPrefs.get(context)
- .putSync(RESTORE_DEVICE.to(new DeviceGridState(context).getDeviceType()));
+ DeviceGridState deviceGridState = new DeviceGridState(context);
+ FileLog.d(TAG, "restore initiated from backup: DeviceGridState=" + deviceGridState);
+ LauncherPrefs.get(context).putSync(RESTORE_DEVICE.to(deviceGridState.getDeviceType()));
+ LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true));
}
@WorkerThread
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index a9c2a2e..839f98c 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -31,6 +31,7 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller.SessionInfo;
+import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -58,8 +59,8 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.Snackbar;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -207,7 +208,8 @@
}
}
// Fallback to using custom market intent.
- Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
+ Intent intent = ApiWrapper.getAppMarketActivityIntent(launcher,
+ packageName, Process.myUserHandle());
launcher.startActivitySafely(v, intent, item);
};
@@ -344,8 +346,8 @@
&& (((ItemInfoWithIcon) item).runtimeStatusFlags
& ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
- intent = new PackageManagerHelper(launcher)
- .getMarketIntent(appInfo.getTargetComponent().getPackageName());
+ intent = ApiWrapper.getAppMarketActivityIntent(launcher,
+ appInfo.getTargetComponent().getPackageName(), Process.myUserHandle());
} else {
intent = item.getIntent();
}
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 51c047c..f7afcb9 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -543,15 +543,19 @@
secondarySnapshotWidth = parentWidth;
secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
- secondarySnapshot.setTranslationY(0);
- primarySnapshot.setTranslationY(secondarySnapshotHeight + spaceAboveSnapshot + dividerBar);
+
+ int translationY = primarySnapshotHeight + spaceAboveSnapshot + dividerBar;
+ primarySnapshot.setTranslationY(spaceAboveSnapshot);
+ secondarySnapshot.setTranslationY(translationY - spaceAboveSnapshot);
+
primarySnapshot.measure(
View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
+ View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY)
+ );
secondarySnapshot.measure(
View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
- View.MeasureSpec.EXACTLY));
+ View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight, View.MeasureSpec.EXACTLY)
+ );
}
@Override
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 04b6710..8301981 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -644,11 +644,12 @@
secondarySnapshotHeight = totalThumbnailHeight;
secondarySnapshotWidth = parentWidth - primarySnapshotWidth - scaledDividerBar;
- int translationX = primarySnapshotWidth + scaledDividerBar;
if (isRtl) {
+ int translationX = secondarySnapshotWidth + scaledDividerBar;
primarySnapshot.setTranslationX(-translationX);
secondarySnapshot.setTranslationX(0);
} else {
+ int translationX = primarySnapshotWidth + scaledDividerBar;
secondarySnapshot.setTranslationX(translationX);
primarySnapshot.setTranslationX(0);
}
@@ -744,7 +745,8 @@
secondaryIconParams.setMarginStart(primaryIconParams.getMarginStart());
if (deviceProfile.isLeftRightSplit) {
if (isRtl) {
- primaryIconView.setTranslationX(-primarySnapshotWidth);
+ int secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth;
+ primaryIconView.setTranslationX(-secondarySnapshotWidth);
} else {
secondaryIconView.setTranslationX(primarySnapshotWidth);
}
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 06526a8..dcbf7d1 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -343,13 +343,15 @@
secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
secondarySnapshot.setTranslationY(0);
primarySnapshot.setTranslationY(secondarySnapshotHeight + spaceAboveSnapshot + dividerBar);
+
primarySnapshot.measure(
View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
+ View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY)
+ );
secondarySnapshot.measure(
View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
- View.MeasureSpec.EXACTLY));
+ View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight, View.MeasureSpec.EXACTLY)
+ );
}
/* ---------- The following are only used by TaskViewTouchHandler. ---------- */
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 91203a7..3f7a128 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -28,8 +28,8 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.graphics.Rect;
-import android.net.Uri;
import android.os.Bundle;
+import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -46,6 +46,7 @@
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.uioverrides.ApiWrapper;
import java.net.URISyntaxException;
import java.util.List;
@@ -137,17 +138,6 @@
return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
}
- public Intent getMarketIntent(String packageName) {
- return new Intent(Intent.ACTION_VIEW)
- .setData(new Uri.Builder()
- .scheme("market")
- .authority("details")
- .appendQueryParameter("id", packageName)
- .build())
- .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
- .authority(mContext.getPackageName()).build());
- }
-
/**
* Creates a new market search intent.
*/
@@ -172,8 +162,8 @@
&& (((ItemInfoWithIcon) info).runtimeStatusFlags
& ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
ItemInfoWithIcon appInfo = (ItemInfoWithIcon) info;
- mContext.startActivity(new PackageManagerHelper(mContext)
- .getMarketIntent(appInfo.getTargetComponent().getPackageName()));
+ mContext.startActivity(ApiWrapper.getAppMarketActivityIntent(mContext,
+ appInfo.getTargetComponent().getPackageName(), Process.myUserHandle()));
return;
}
ComponentName componentName = null;
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 5d069ed..12b47e6 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -41,6 +41,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.CheckLongPressHelper;
+import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -103,6 +104,9 @@
mLongPressHelper = new CheckLongPressHelper(this, this);
setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
setBackgroundResource(R.drawable.widget_internal_focus_bg);
+ if (Flags.enableFocusOutline()) {
+ setDefaultFocusHighlightEnabled(false);
+ }
if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
setOnLightBackground(true);
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index fe5c1fd..b9f9ac5 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -19,9 +19,11 @@
import android.app.ActivityOptions;
import android.app.Person;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -29,6 +31,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.util.UserIconInfo;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -80,6 +83,30 @@
return users;
}
+ /**
+ * Returns the list of the system packages that are installed at user creation.
+ * An empty list denotes that all system packages are installed for that user at creation.
+ */
+ public static List<String> getPreInstalledSystemPackages(Context context, UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ /**
+ * Returns an intent which can be used to start the App Market activity (Installer
+ * Activity).
+ */
+ public static Intent getAppMarketActivityIntent(Context context, String packageName,
+ UserHandle user) {
+ return new Intent(Intent.ACTION_VIEW)
+ .setData(new Uri.Builder()
+ .scheme("market")
+ .authority("details")
+ .appendQueryParameter("id", packageName)
+ .build())
+ .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
+ .authority(context.getPackageName()).build());
+ }
+
private static class NoopDrawable extends ColorDrawable {
@Override
public int getIntrinsicHeight() {
diff --git a/tests/Android.bp b/tests/Android.bp
index 84c3951..dd0ba9e 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -66,14 +66,14 @@
filegroup {
name: "launcher-oop-tests-src",
srcs: [
- "src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java",
- "src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java",
+ "src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java",
+ "src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java",
"src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java",
"src/com/android/launcher3/dragging/TaplDragTest.java",
- "src/com/android/launcher3/dragging/TaplUninstallRemove.java",
+ "src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java",
"src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
"src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
- "src/com/android/launcher3/ui/TaplTestsLauncher3.java",
+ "src/com/android/launcher3/ui/TaplTestsLauncher3Test.java",
"src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java",
"src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java",
"src/com/android/launcher3/util/LauncherLayoutBuilder.java",
@@ -174,3 +174,77 @@
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
}
+
+filegroup {
+ name: "launcher-testing-helpers",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ "src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
+ "tapl/com/android/launcher3/tapl/*.java",
+ "tapl/com/android/launcher3/tapl/*.kt",
+ ],
+ exclude_srcs: [
+ // Test classes
+ "src/**/*Test.java",
+ "src/**/*Test.kt",
+ ],
+}
+
+android_library {
+ name: "Launcher3Lib",
+ srcs: [
+ ":launcher-src",
+ ":launcher-src_shortcuts_overrides",
+ ":launcher-src_ui_overrides",
+ ],
+ static_libs: [
+ "Launcher3CommonDepsLib",
+ ],
+}
+
+android_robolectric_test {
+ enabled: true,
+ name: "Launcher3RoboTests",
+ srcs: [
+ "src/com/android/launcher3/util/*.java",
+ "src/com/android/launcher3/util/*.kt",
+
+ // Test util classes
+ ":launcher-testing-helpers",
+ ":launcher-testing-shared",
+ ],
+ exclude_srcs: [
+ "src/com/android/launcher3/util/CellContentDimensionsTest.kt", // Failing - b/316553889
+
+ // requires modification to work with inline mock maker
+ "src/com/android/launcher3/util/rule/StaticMockitoRule.java",
+
+ // requires kotlin mockito
+ "src/com/android/launcher3/util/LockedUserStateTest.kt",
+ "src/com/android/launcher3/util/DisplayControllerTest.kt",
+ ],
+ java_resource_dirs: ["config"],
+ static_libs: [
+ "flag-junit-base",
+ "com_android_launcher3_flags_lib",
+ "com_android_wm_shell_flags_lib",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.core_core-animation-testing",
+ "androidx.test.ext.junit",
+ "inline-mockito-robolectric-prebuilt",
+ "platform-parametric-runner-lib",
+ "testables",
+ "Launcher3TestResources",
+ "SystemUISharedLib",
+ "launcher-testing-shared",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "truth",
+ ],
+ instrumentation_for: "Launcher3",
+ upstream: true,
+}
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
index 1781673..0f27893 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
@@ -87,7 +87,7 @@
numShownHotseatIcons: 6
hotseatBorderSpace: 100.0px (50.0dp)
isQsbInline: false
- hotseatQsbWidth: 1224.0px (612.0dp)
+ hotseatQsbWidth: 1214.0px (607.0dp)
isTaskbarPresent:false
isTaskbarPresentInApps:true
taskbarHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
index bd9e267..85f7ca1 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
@@ -87,7 +87,7 @@
numShownHotseatIcons: 6
hotseatBorderSpace: 100.0px (50.0dp)
isQsbInline: false
- hotseatQsbWidth: 1224.0px (612.0dp)
+ hotseatQsbWidth: 1214.0px (607.0dp)
isTaskbarPresent:false
isTaskbarPresentInApps:true
taskbarHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
index e983ef7..bd47777 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
@@ -87,7 +87,7 @@
numShownHotseatIcons: 6
hotseatBorderSpace: 116.0px (58.0dp)
isQsbInline: false
- hotseatQsbWidth: 1300.0px (650.0dp)
+ hotseatQsbWidth: 1290.0px (645.0dp)
isTaskbarPresent:false
isTaskbarPresentInApps:true
taskbarHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
index aa92838..902885a 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
@@ -87,7 +87,7 @@
numShownHotseatIcons: 6
hotseatBorderSpace: 116.0px (58.0dp)
isQsbInline: false
- hotseatQsbWidth: 1300.0px (650.0dp)
+ hotseatQsbWidth: 1290.0px (645.0dp)
isTaskbarPresent:false
isTaskbarPresentInApps:true
taskbarHeight: 0.0px (0.0dp)
diff --git a/tests/config/robolectric.properties b/tests/config/robolectric.properties
new file mode 100644
index 0000000..fab7251
--- /dev/null
+++ b/tests/config/robolectric.properties
@@ -0,0 +1 @@
+sdk=NEWEST_SDK
diff --git a/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java b/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
similarity index 96%
rename from tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
rename to tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
index 9f6bbdf..27a2c75 100644
--- a/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
+++ b/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
@@ -33,7 +33,7 @@
* The test runs in Out of process (Oop) and in process.
* Makes sure the basic behaviors of Icons on AllApps are working.
*/
-public class TaplTestsAllAppsIconsWorking extends AbstractLauncherUiTest {
+public class TaplAllAppsIconsWorkingTest extends AbstractLauncherUiTest {
@Before
public void setUp() throws Exception {
diff --git a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
rename to tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
index 4f10287..92ff355 100644
--- a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
+++ b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
@@ -43,7 +43,7 @@
* Test that we can open and close the all apps in multiple situations.
* The test runs in Out of process (Oop) and in process.
*/
-public class TaplOpenCloseAllApps extends AbstractLauncherUiTest {
+public class TaplOpenCloseAllAppsTest extends AbstractLauncherUiTest {
public static final String READ_DEVICE_CONFIG_PERMISSION =
"android.permission.READ_DEVICE_CONFIG";
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
rename to tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index 568fc9f..0b9de0f 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -51,7 +51,7 @@
* Test runs in Out of process (Oop) and In process (Ipc)
* Test the behaviour of uninstalling and removing apps both from AllApps, Workspace and Hotseat.
*/
-public class TaplUninstallRemove extends AbstractLauncherUiTest {
+public class TaplUninstallRemoveTest extends AbstractLauncherUiTest {
@Before
public void setUp() throws Exception {
diff --git a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
index d102397..9912a34 100644
--- a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
@@ -47,7 +47,7 @@
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(177)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1445)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1435)
}
/**
@@ -69,7 +69,7 @@
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(110)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1080)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1070)
}
/**
@@ -90,7 +90,7 @@
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(370)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1468)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1455)
}
/**
@@ -115,7 +115,7 @@
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(668)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1224)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1214)
}
/** This is a case when after setting the hotseat, the QSB width needs to be changed to fit */
@@ -134,7 +134,7 @@
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(640)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1179)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1169)
}
/**
@@ -156,7 +156,7 @@
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(582)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1095)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1085)
}
@Test
@@ -176,7 +176,7 @@
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(177)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(177)
- assertThat(dp.hotseatQsbWidth).isEqualTo(1445)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1435)
}
}
}
diff --git a/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java b/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
new file mode 100644
index 0000000..b6ded97
--- /dev/null
+++ b/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.tapl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class TaplUtilityTest {
+
+ @Test
+ public void testMakeMultilinePattern() {
+ // Original title will match.
+ assertTrue(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play Store").matches());
+ assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+ .matcher("PlayStore").matches());
+
+ // Original title with whitespace added will match.
+ assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+ .matcher("Play\nStore").matches());
+ assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+ .matcher("Play Store").matches());
+ // Original title with whitespace removed will also match.
+ assertTrue(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("PlayStore").matches());
+ // Or whitespace replaced with a different kind of whitespace (both of above conditions).
+ assertTrue(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play\nStore").matches());
+ assertTrue(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play \n Store").matches());
+
+ // Any non-whitespace character added to the title will not match.
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play Store has 7 notifications").matches());
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play Store!").matches());
+ // Title is case-sensitive.
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("play store").matches());
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("play store").matches());
+ // Removing non whitespace characters will not match.
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("").matches());
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play Stor").matches());
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play").matches());
+ }
+}
diff --git a/tests/src/com/android/launcher3/tapl/TaplUtilityTests.java b/tests/src/com/android/launcher3/tapl/TaplUtilityTests.java
deleted file mode 100644
index 15db1d8..0000000
--- a/tests/src/com/android/launcher3/tapl/TaplUtilityTests.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.tapl;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-
-public class TaplUtilityTests {
-
- @Test
- public void testNewStringWithRegex() {
- assertTrue(AppIcon.makeMultilinePattern("Play Store")
- .matcher("Play Store has 7 notifications").matches());
- assertTrue(AppIcon.makeMultilinePattern("Play Store")
- .matcher("Play Store!").matches());
- assertFalse(AppIcon.makeMultilinePattern("Play Store")
- .matcher("play store").matches());
- assertFalse(AppIcon.makeMultilinePattern("Play Store")
- .matcher("").matches());
- assertTrue(AppIcon.makeMultilinePattern("Play Store")
- .matcher("Play \n Store").matches());
- }
-}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
similarity index 95%
rename from tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
rename to tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
index 229ea45..d26a9db 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
@@ -27,7 +27,7 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
+public class TaplTestsLauncher3Test extends AbstractLauncherUiTest {
@Before
public void setUp() throws Exception {
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index f818564..5d41da0 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.util.TestUtil.installDummyAppForUser;
import static org.junit.Assert.assertEquals;
@@ -45,6 +46,7 @@
import org.junit.Ignore;
import org.junit.Test;
+import java.io.IOException;
import java.util.Objects;
import java.util.function.Predicate;
@@ -98,7 +100,17 @@
launcher.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
});
TestUtil.uninstallDummyApp();
- mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+
+ mLauncher.runToState(
+ () -> {
+ try {
+ mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ },
+ NORMAL_STATE_ORDINAL,
+ "executing pm 'remove-user' command");
}
private void waitForWorkTabSetup() {
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index 27fda9b..d75b387 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -47,7 +47,6 @@
@Rule
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
- @PlatinumTest(focusArea = "launcher")
@Test
@PortraitLandscape
@ScreenRecordRule.ScreenRecord // b/289161193
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
index 59c82a7..4edeb42 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.ui.workspace;
-import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
import static com.android.launcher3.util.TestConstants.AppNames.CHROME_APP_NAME;
import static org.junit.Assert.assertEquals;
@@ -23,8 +22,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.platform.test.annotations.PlatinumTest;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.tapl.HomeAppIcon;
@@ -70,7 +67,6 @@
* move between workspaces. After, make sure we can launch an app from the Workspace.
* @throws Exception if we can't set the defaults icons that will appear at the beginning.
*/
- @PlatinumTest(focusArea = "launcher")
@Test
public void testWorkspace() throws Exception {
// Set workspace that includes the chrome Activity app icon on the hotseat.
@@ -123,7 +119,6 @@
* Similar to {@link TaplWorkspaceTest#testWorkspace} but here we also make sure we can delete
* the pages.
*/
- @PlatinumTest(focusArea = "launcher")
@Test
public void testAddAndDeletePageAndFling() {
Workspace workspace = mLauncher.getWorkspace();
diff --git a/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt b/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
index b8d74aa..972b592 100644
--- a/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
+++ b/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
@@ -16,7 +16,7 @@
package com.android.launcher3.util
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.util.rule.TestStabilityRule
import java.util.concurrent.ExecutorService
@@ -24,12 +24,13 @@
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/** Unit test for [ExecutorRunnable] */
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class ExecutorRunnableTest {
private lateinit var underTest: ExecutorRunnable<Int>
@@ -38,6 +39,8 @@
private var isTaskExecuted = false
private var isCallbackExecuted = false
+ @get:Rule(order = 0) val testStabilityRule = TestStabilityRule()
+
@Before
fun setup() {
reset()
@@ -60,8 +63,8 @@
fun run_and_complete() {
awaitAllExecutorCompleted()
- assertTrue(isTaskExecuted)
- assertTrue(isCallbackExecuted)
+ assertTrue("task should be executed", isTaskExecuted)
+ assertTrue("callback should be executed", isCallbackExecuted)
assertEquals(2, result)
}
@@ -73,7 +76,7 @@
underTest.cancel(false)
awaitAllExecutorCompleted()
- assertFalse(isCallbackExecuted)
+ assertFalse("callback should not be executed.", isCallbackExecuted)
assertEquals(0, result)
}
@@ -83,8 +86,8 @@
underTest.cancel(false)
- assertTrue(isTaskExecuted)
- assertTrue(isCallbackExecuted)
+ assertTrue("task should be executed", isTaskExecuted)
+ assertTrue("callback should be executed", isCallbackExecuted)
assertEquals(2, result)
}
diff --git a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
index 61ec669..9d9bd517 100644
--- a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
+++ b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -1,7 +1,27 @@
package com.android.launcher3.util
+import android.content.ContentValues
import com.android.launcher3.LauncherModel
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_ID
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_PROVIDER
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_SOURCE
+import com.android.launcher3.LauncherSettings.Favorites.CELLX
+import com.android.launcher3.LauncherSettings.Favorites.CELLY
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.INTENT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID
+import com.android.launcher3.LauncherSettings.Favorites.RESTORED
+import com.android.launcher3.LauncherSettings.Favorites.SCREEN
+import com.android.launcher3.LauncherSettings.Favorites.SPANX
+import com.android.launcher3.LauncherSettings.Favorites.SPANY
+import com.android.launcher3.LauncherSettings.Favorites.TITLE
+import com.android.launcher3.LauncherSettings.Favorites._ID
import com.android.launcher3.model.BgDataModel
+import com.android.launcher3.model.ModelDbController
object ModelTestExtensions {
/** Clears and reloads Launcher db to cleanup the workspace */
@@ -20,6 +40,7 @@
loadModelSync()
}
+ /** Loads the model in memory synchronously */
fun LauncherModel.loadModelSync() {
val mockCb: BgDataModel.Callbacks = object : BgDataModel.Callbacks {}
TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { addCallbacksAndLoad(mockCb) }
@@ -27,4 +48,54 @@
TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {}
TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { removeCallbacks(mockCb) }
}
+
+ /** Adds and commits a new item to Launcher.db */
+ fun LauncherModel.addItem(
+ title: String = "LauncherTestApp",
+ intent: String =
+ "#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;component=com.google.android.apps.nexuslauncher.tests/com.android.launcher3.testcomponent.BaseTestingActivity;launchFlags=0x10200000;end",
+ type: Int = ITEM_TYPE_APPLICATION,
+ restoreFlags: Int = 0,
+ screen: Int = 0,
+ container: Int = CONTAINER_DESKTOP,
+ x: Int,
+ y: Int,
+ spanX: Int = 1,
+ spanY: Int = 1,
+ id: Int = 0,
+ profileId: Int = 0,
+ tableName: String = Favorites.TABLE_NAME,
+ appWidgetId: Int = -1,
+ appWidgetSource: Int = -1,
+ appWidgetProvider: String? = null
+ ) {
+ loadModelSync()
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ val controller: ModelDbController = modelDbController
+ controller.tryMigrateDB()
+ modelDbController.newTransaction().use { transaction ->
+ val values =
+ ContentValues().apply {
+ values[_ID] = id
+ values[TITLE] = title
+ values[PROFILE_ID] = profileId
+ values[CONTAINER] = container
+ values[SCREEN] = screen
+ values[CELLX] = x
+ values[CELLY] = y
+ values[SPANX] = spanX
+ values[SPANY] = spanY
+ values[ITEM_TYPE] = type
+ values[RESTORED] = restoreFlags
+ values[INTENT] = intent
+ values[APPWIDGET_ID] = appWidgetId
+ values[APPWIDGET_SOURCE] = appWidgetSource
+ values[APPWIDGET_PROVIDER] = appWidgetProvider
+ }
+ // Migrate any previous data so that the DB state is correct
+ controller.insert(tableName, values)
+ transaction.commit()
+ }
+ }
+ }
}
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
index b51045f..909aabd 100644
--- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -83,6 +83,7 @@
public static int getRunFlavor() {
if (sRunFlavor != 0) return sRunFlavor;
+ if (isRobolectricTest()) return PLATFORM_POSTSUBMIT;
final String flavorOverride = InstrumentationRegistry.getArguments().getString("flavor");
@@ -150,4 +151,8 @@
public static boolean isPresubmit() {
return getRunFlavor() == PLATFORM_PRESUBMIT;
}
+
+ public static boolean isRobolectricTest() {
+ return Build.FINGERPRINT.contains("robolectric");
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 85098c8..867a1a8 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -36,9 +36,23 @@
super(launcher, icon);
}
+ /**
+ * Find an app icon with the given name.
+ *
+ * @param appName app icon to look for
+ */
+ static BySelector getAppIconSelector(String appName) {
+ return By.clazz(TextView.class).text(makeMultilinePattern(appName));
+ }
+
+ /**
+ * Find an app icon with the given name.
+ *
+ * @param appName app icon to look for
+ * @param launcher (optional) - only match ui elements from Launcher's package
+ */
static BySelector getAppIconSelector(String appName, LauncherInstrumentation launcher) {
- return By.clazz(TextView.class).desc(makeMultilinePattern(appName))
- .pkg(launcher.getLauncherPackageName());
+ return getAppIconSelector(appName).pkg(launcher.getLauncherPackageName());
}
static BySelector getMenuItemSelector(String text, LauncherInstrumentation launcher) {
@@ -109,13 +123,16 @@
}
/**
- * Create a regular expression pattern that matches strings starting with the app name, where
- * spaces in the app name are replaced with zero or more occurrences of the "\s" character
- * (which represents a whitespace character in regular expressions), followed by any characters
- * after the app name.
+ * Create a regular expression pattern that matches strings containing all of the non-whitespace
+ * characters of the app name, with any amount of whitespace added between characters (e.g.
+ * newline for multiline app labels).
*/
static Pattern makeMultilinePattern(String appName) {
- return Pattern.compile(appName.replaceAll("\\s+", "\\\\s*") + ".*",
- Pattern.DOTALL);
+ // Remove any existing whitespace.
+ appName = appName.replaceAll("\\s", "");
+ // Allow whitespace between characters, e.g. newline for 2 line app label.
+ StringBuilder regexBuldier = new StringBuilder("\\s*");
+ appName.chars().forEach(letter -> regexBuldier.append((char) letter).append("\\s*"));
+ return Pattern.compile(regexBuldier.toString());
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index fe927b3..28e2590 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -16,8 +16,6 @@
package com.android.launcher3.tapl;
-import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
import android.graphics.Point;
@@ -97,12 +95,10 @@
LauncherInstrumentation.log("Launchable.launch before click "
+ mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(
mObject));
- mLauncher.executeAndWaitForLauncherEvent(
+
+ mLauncher.executeAndWaitForLauncherStop(
() -> mLauncher.clickLauncherObject(mObject),
- accessibilityEvent ->
- accessibilityEvent.getEventType() == TYPE_WINDOW_STATE_CHANGED,
- () -> "Unable to click object to launch split",
- "Click launcher object to launch split");
+ "clicking the launchable");
try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, OverviewTask.SPLIT_START_EVENT);
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 6c1a02f..7d3807e 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1530,7 +1530,8 @@
}
}
- void runToState(Runnable command, int expectedState, String actionName) {
+ /** Run an action and wait for the specified Launcher state. */
+ public void runToState(Runnable command, int expectedState, String actionName) {
final List<Integer> actualEvents = new ArrayList<>();
executeAndWaitForLauncherEvent(
command,
@@ -2336,7 +2337,7 @@
int bottomBound = Math.min(
containerBounds.bottom,
getRealDisplaySize().y - getImeInsets().bottom);
- int y = (bottomBound + containerBounds.top) / 2;
+ int y = (bottomBound - containerBounds.top) / 2;
// Do not tap in the status bar.
y = Math.max(y, getWindowInsets().top);
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index f0a8aa2..963bf79 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -39,7 +39,7 @@
/** Find the app from search results with app name. */
public AppIcon findAppIcon(String appName) {
- UiObject2 icon = mLauncher.waitForLauncherObject(By.clazz(TextView.class).text(appName));
+ UiObject2 icon = mLauncher.waitForLauncherObject(AppIcon.getAppIconSelector(appName));
return createAppIcon(icon);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
index 064f80c..d05c112 100644
--- a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
@@ -68,6 +68,7 @@
@Override
protected boolean launcherStopsAfterLaunch() {
+ // false because if taskbar is showing then launcher is already stopped.
return false;
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 105bc3b..6387b05 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
import static com.android.launcher3.tapl.LauncherInstrumentation.log;
+import android.annotation.Nullable;
import android.graphics.Rect;
import androidx.test.uiautomator.By;
@@ -114,7 +115,13 @@
.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ /** Get widget with supplied text. */
public Widget getWidget(String labelText) {
+ return getWidget(labelText, null);
+ }
+
+ /** Get widget with supplied text and app package */
+ public Widget getWidget(String labelText, @Nullable String testAppWidgetPackage) {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"getting widget " + labelText + " in widgets list")) {
@@ -124,7 +131,8 @@
mLauncher.assertTrue("Widgets container didn't become scrollable",
fullWidgetsPicker.wait(Until.scrollable(true), WAIT_TIME_MS));
- final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer();
+ final UiObject2 widgetsContainer =
+ findTestAppWidgetsTableContainer(testAppWidgetPackage);
mLauncher.assertTrue("Can't locate widgets list for the test app: "
+ mLauncher.getLauncherPackageName(),
widgetsContainer != null);
@@ -180,14 +188,22 @@
return searchBar;
}
- /** Finds the widgets list of this test app from the collapsed full widgets picker. */
- private UiObject2 findTestAppWidgetsTableContainer() {
+ /**
+ * Finds the widgets list of this test app or supplied test app package from the collapsed full
+ * widgets picker.
+ */
+ private UiObject2 findTestAppWidgetsTableContainer(@Nullable String testAppWidgetPackage) {
final BySelector headerSelector = By.res(mLauncher.getLauncherPackageName(),
"widgets_list_header");
final BySelector widgetPickerSelector = By.res(mLauncher.getLauncherPackageName(),
"container");
- final BySelector targetAppSelector = By.clazz("android.widget.TextView").text(
- mLauncher.getContext().getPackageName());
+
+ String packageName = mLauncher.getContext().getPackageName();
+ final BySelector targetAppSelector = By
+ .clazz("android.widget.TextView")
+ .text((testAppWidgetPackage == null || testAppWidgetPackage.isEmpty())
+ ? packageName
+ : testAppWidgetPackage);
final BySelector widgetsContainerSelector = By.res(mLauncher.getLauncherPackageName(),
"widgets_table");
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 1b0e59c..ada0a7f 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -192,16 +192,24 @@
}
/**
- * Ensures that workspace is scrollable. If it's not, drags an icon icons from hotseat to the
- * second screen.
+ * Ensures that workspace is scrollable. If it's not, drags a chrome app icon from hotseat
+ * to the second screen.
*/
public void ensureWorkspaceIsScrollable() {
+ ensureWorkspaceIsScrollable("Chrome");
+ }
+
+ /**
+ * Ensures that workspace is scrollable. If it's not, drags an icon of a given app name from
+ * hotseat to the second screen.
+ */
+ public void ensureWorkspaceIsScrollable(String appName) {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
final UiObject2 workspace = verifyActiveContainer();
if (!isWorkspaceScrollable(workspace)) {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"dragging icon to a second page of workspace to make it scrollable")) {
- dragIcon(workspace, getHotseatAppIcon("Chrome"), pagesPerScreen());
+ dragIcon(workspace, getHotseatAppIcon(appName), pagesPerScreen());
verifyActiveContainer();
}
}
@@ -450,7 +458,12 @@
}
/** Returns the index of the current page */
- private static int geCurrentPage(LauncherInstrumentation launcher) {
+ public int getCurrentPage() {
+ return getCurrentPage(mLauncher);
+ }
+
+ /** Returns the index of the current page */
+ private static int getCurrentPage(LauncherInstrumentation launcher) {
return launcher.getTestInfo(TestProtocol.REQUEST_WORKSPACE_CURRENT_PAGE_INDEX).getInt(
TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
@@ -637,7 +650,7 @@
Point currentPosition, int destinationWorkspaceIndex, int y) {
final long downTime = SystemClock.uptimeMillis();
int displayX = launcher.getRealDisplaySize().x;
- int currentPage = Workspace.geCurrentPage(launcher);
+ int currentPage = Workspace.getCurrentPage(launcher);
int counter = 0;
while (currentPage != destinationWorkspaceIndex) {
counter++;
@@ -656,7 +669,7 @@
() -> launcher.movePointer(finalDragStart, screenEdge, DEFAULT_DRAG_STEPS,
true, downTime, downTime, true,
LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER));
- currentPage = Workspace.geCurrentPage(launcher);
+ currentPage = Workspace.getCurrentPage(launcher);
currentPosition = screenEdge;
}
return currentPosition;