Merge "Update live tile during dismiss anim even if disabled at start" 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/OWNERS b/OWNERS
index 38963d0..b8aae78 100644
--- a/OWNERS
+++ b/OWNERS
@@ -12,7 +12,6 @@
jonmiranda@google.com
alexchau@google.com
patmanning@google.com
-tsuharesu@google.com
awickham@google.com
# Launcher workspace eng team
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index af175ce..210cfd0 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -43,6 +43,13 @@
}
flag {
+ name: "enable_focus_outline"
+ namespace: "launcher"
+ description: "Enables focus states outline for launcher."
+ bug: "310953377"
+}
+
+flag {
name: "enable_taskbar_no_recreate"
namespace: "launcher"
description: "Enables taskbar with no recreation from lifecycle changes of TaskbarActivityContext."
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/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index bb2f533..893b1c8 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -86,7 +86,7 @@
<string name="gesture_tutorial_nice" msgid="2936275692616928280">"¡Muy bien!"</string>
<string name="gesture_tutorial_step" msgid="1279786122817620968">"Tutorial <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="allset_title" msgid="5021126669778966707">"¡Ya está!"</string>
- <string name="allset_hint" msgid="459504134589971527">"Desliza el dedo hacia arriba para ir a la pantalla de inicio"</string>
+ <string name="allset_hint" msgid="459504134589971527">"Desliza hacia arriba para ir a la pantalla de inicio"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Toca el botón de inicio para ir a la pantalla de inicio"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"Ya puedes empezar a usar tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="default_device_name" msgid="6660656727127422487">"dispositivo"</string>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index c8da620..91e4ad5 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -109,7 +109,7 @@
<string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"برای استفاده همزمان از ۲ برنامه، یک برنامه را به کناری بکشید"</string>
<string name="taskbar_edu_stashing" msgid="5645461372669217294">"برای نمایش «نوار وظیفه»، انگشتتان را آهسته بهبالا بکشید"</string>
<string name="taskbar_edu_suggestions" msgid="8215044496435527982">"براساس روالهایتان، پیشنهاد برنامه دریافت کنید"</string>
- <string name="taskbar_edu_pinning" msgid="6708550858580071558">"برای سنجاق کردن «نوار وظیفه»، جداکننده را چند ثانیه فشار دهید"</string>
+ <string name="taskbar_edu_pinning" msgid="6708550858580071558">"جداکننده را چند ثانیه فشار دهید تا «نوار وظیفه» سنجاق شود"</string>
<string name="taskbar_edu_features" msgid="3320337287472848162">"با «نوار وظیفه» میتوانید کارهای بیشتر انجام دهید"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"بستن"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"تمام"</string>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index df6cad4..789ef13 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -109,7 +109,7 @@
<string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Faites glisser une appli sur le côté pour en utiliser 2 à la fois"</string>
<string name="taskbar_edu_stashing" msgid="5645461372669217294">"Balayez lentement vers haut pour afficher barre des tâches"</string>
<string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Obtenez des suggestions d\'applis basées sur vos habitudes"</string>
- <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Appui de manière prolongée sur le séparateur pour épingler la barre des tâches"</string>
+ <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Appui prolongé sur le séparateur pour épingler la barre des tâches"</string>
<string name="taskbar_edu_features" msgid="3320337287472848162">"Faites-en plus avec la barre des tâches"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Fermer"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"OK"</string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 2a1f39f..232c441 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -322,6 +322,7 @@
<!-- 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/res/values/override.xml b/quickstep/res/values/override.xml
index df32626..29779a7 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -35,4 +35,6 @@
<string name="assist_state_manager_class" translatable="false"></string>
+ <string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string>
+
</resources>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 4898761..f25b652 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -64,8 +64,6 @@
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
-import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME;
-import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME_FALLBACK;
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
@@ -117,6 +115,7 @@
import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -1652,7 +1651,8 @@
// is initialized.
if (launcherIsForceInvisibleOrOpening) {
addCujInstrumentation(anim, playFallBackAnimation
- ? CUJ_APP_CLOSE_TO_HOME_FALLBACK : CUJ_APP_CLOSE_TO_HOME);
+ ? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK
+ : Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -1768,19 +1768,18 @@
if (launchingFromWidget) {
composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets,
wallpaperTargets, nonAppTargets, launcherClosing);
- addCujInstrumentation(
- anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_WIDGET);
+ addCujInstrumentation(anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET);
skipFirstFrame = true;
} else if (launchingFromRecents) {
composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
launcherClosing);
addCujInstrumentation(
- anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS);
+ anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS);
skipFirstFrame = true;
} else {
composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
launcherClosing);
- addCujInstrumentation(anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_ICON);
+ addCujInstrumentation(anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON);
skipFirstFrame = false;
}
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 43716ab..575ad9e 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -16,13 +16,19 @@
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;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
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;
import android.view.WindowInsetsController;
import android.view.WindowManager;
@@ -32,6 +38,7 @@
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.widget.BaseWidgetSheet;
+import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
@@ -39,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 -> {});
@@ -81,6 +96,63 @@
return mDragLayer;
}
+ @Override
+ public View.OnClickListener getItemOnClickListener() {
+ return v -> {
+ final AppWidgetProviderInfo info =
+ (v instanceof WidgetCell) ? ((WidgetCell) v).getWidgetItem().widgetInfo : null;
+ if (info == null || info.provider == null) {
+ return;
+ }
+
+ setResult(RESULT_OK, new Intent()
+ .putExtra(Intent.EXTRA_COMPONENT_NAME, info.provider)
+ .putExtra(Intent.EXTRA_USER, info.getProfile()));
+
+ finish();
+ };
+ }
+
+ @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..17c39af 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;
@@ -132,7 +133,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);
@@ -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()) {
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/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index b4754c6..159a6ef 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -204,6 +204,11 @@
}
@Override
+ public void onStateTransitionCompletedAfterSwipeToHome(LauncherState state) {
+ mTaskbarLauncherStateController.onStateTransitionCompletedAfterSwipeToHome(state);
+ }
+
+ @Override
public void refreshResumedState() {
onLauncherVisibilityChanged(mLauncher.hasBeenResumed());
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index bed4c37..709d3ba 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -25,8 +25,6 @@
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
-import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
-import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
@@ -239,7 +237,7 @@
Point p = !mContext.isUserSetupComplete()
? new Point(0, mControllers.taskbarActivityContext.getSetupWindowHeight())
: DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
- TaskbarManager.isPhoneMode(deviceProfile));
+ mContext.isPhoneMode());
mNavButtonsView.getLayoutParams().height = p.y;
mIsImeRenderingNavButtons =
@@ -305,7 +303,7 @@
initButtons(mNavButtonContainer, mEndContextualContainer,
mControllers.navButtonController);
updateButtonLayoutSpacing();
- updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneButtonNavMode(mContext));
+ updateStateForFlag(FLAG_SMALL_SCREEN, mContext.isPhoneButtonNavMode());
mPropertyHolders.add(new StatePropertyHolder(
mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
@@ -388,7 +386,7 @@
int navButtonSize = mContext.getResources().getDimensionPixelSize(
R.dimen.taskbar_nav_buttons_size);
boolean isRtl = Utilities.isRtl(mContext.getResources());
- if (!isPhoneMode(mContext.getDeviceProfile())) {
+ if (!mContext.isPhoneMode()) {
mPropertyHolders.add(new StatePropertyHolder(
mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
|| (flags & FLAG_KEYGUARD_VISIBLE) != 0,
@@ -632,7 +630,7 @@
* Sets the translationY of the nav buttons based on the current device state.
*/
public void updateNavButtonTranslationY() {
- if (isPhoneButtonNavMode(mContext)) {
+ if (mContext.isPhoneButtonNavMode()) {
return;
}
final float normalTranslationY = mTaskbarNavButtonTranslationY.value;
@@ -751,9 +749,8 @@
dp, mNavButtonsView, mImeSwitcherButton,
mControllers.rotationButtonController.getRotationButton(),
mA11yButton, res, isInKidsMode, isInSetup, isThreeButtonNav,
- TaskbarManager.isPhoneMode(dp),
- mWindowManagerProxy.getRotation(mContext));
- navButtonLayoutter.layoutButtons(dp, isA11yButtonPersistent());
+ mContext.isPhoneMode(), mWindowManagerProxy.getRotation(mContext));
+ navButtonLayoutter.layoutButtons(mContext, isA11yButtonPersistent());
updateNavButtonColor();
return;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index c4255bf..ad2dc23 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -107,8 +107,8 @@
mControllers = controllers;
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Resources resources = mActivity.getResources();
- if (isPhoneGestureNavMode(mActivity.getDeviceProfile())) {
- mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_size);
+ if (mActivity.isPhoneGestureNavMode()) {
+ mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_phone_size);
mStashedHandleWidth =
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
} else {
@@ -120,7 +120,7 @@
mStashedHandleView.getLayoutParams().height = mTaskbarSize + taskbarBottomMargin;
mTaskbarStashedHandleAlpha.get(ALPHA_INDEX_STASHED).setValue(
- isPhoneGestureNavMode(deviceProfile) ? 1 : 0);
+ mActivity.isPhoneGestureNavMode() ? 1 : 0);
mTaskbarStashedHandleHintScale.updateValue(1f);
final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
@@ -148,7 +148,7 @@
view.setPivotY(stashedCenterY);
});
initRegionSampler();
- if (isPhoneGestureNavMode(deviceProfile)) {
+ if (mActivity.isPhoneGestureNavMode()) {
onIsStashedChanged(true);
}
}
@@ -184,10 +184,6 @@
mRegionSamplingHelper = null;
}
- private boolean isPhoneGestureNavMode(DeviceProfile deviceProfile) {
- return TaskbarManager.isPhoneMode(deviceProfile) && !mActivity.isThreeButtonNav();
- }
-
public MultiPropertyFactory<View> getStashedHandleAlpha() {
return mTaskbarStashedHandleAlpha;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 38ee4ac..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
@@ -214,7 +220,7 @@
Context c = getApplicationContext();
mWindowManager = c.getSystemService(WindowManager.class);
- boolean phoneMode = TaskbarManager.isPhoneMode(mDeviceProfile);
+ boolean phoneMode = isPhoneMode();
mLeftCorner = phoneMode
? null
: display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
@@ -388,6 +394,28 @@
}
/**
+ * @return {@code true} if the device profile isn't a large screen profile and we are using a
+ * single window for taskbar and navbar.
+ */
+ public boolean isPhoneMode() {
+ return ENABLE_TASKBAR_NAVBAR_UNIFICATION && mDeviceProfile.isPhone;
+ }
+
+ /**
+ * @return {@code true} if {@link #isPhoneMode()} is true and we're using 3 button-nav
+ */
+ public boolean isPhoneButtonNavMode() {
+ return isPhoneMode() && isThreeButtonNav();
+ }
+
+ /**
+ * @return {@code true} if {@link #isPhoneMode()} is true and we're using gesture nav
+ */
+ public boolean isPhoneGestureNavMode() {
+ return isPhoneMode() && !isThreeButtonNav();
+ }
+
+ /**
* Show Taskbar upon receiving broadcast
*/
public void showTaskbarFromBroadcast() {
@@ -464,9 +492,7 @@
windowLayoutParams.privateFlags =
WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
windowLayoutParams.accessibilityTitle = getString(
- TaskbarManager.isPhoneMode(mDeviceProfile)
- ? R.string.taskbar_phone_a11y_title
- : R.string.taskbar_a11y_title);
+ isPhoneMode() ? R.string.taskbar_phone_a11y_title : R.string.taskbar_a11y_title);
return windowLayoutParams;
}
@@ -480,8 +506,7 @@
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
WindowManager.LayoutParams windowLayoutParams =
createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE);
- boolean isPhoneNavMode = TaskbarManager.isPhoneButtonNavMode(this);
- if (!isPhoneNavMode) {
+ if (!isPhoneButtonNavMode()) {
return windowLayoutParams;
}
@@ -885,7 +910,7 @@
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && mDeviceProfile.isPhone) {
return isThreeButtonNav() ?
- resources.getDimensionPixelSize(R.dimen.taskbar_size) :
+ resources.getDimensionPixelSize(R.dimen.taskbar_phone_size) :
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
}
@@ -1037,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) {
@@ -1125,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(
@@ -1148,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/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index d6016f1..30f8d56 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -23,7 +23,6 @@
import android.graphics.Path
import android.graphics.RectF
import com.android.app.animation.Interpolators
-import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapRange
@@ -95,10 +94,10 @@
setCornerRoundness(DEFAULT_ROUNDNESS)
}
- fun updateStashedHandleWidth(dp: DeviceProfile, res: Resources) {
+ fun updateStashedHandleWidth(context: TaskbarActivityContext, res: Resources) {
stashedHandleWidth =
res.getDimensionPixelSize(
- if (TaskbarManager.isPhoneMode(dp)) R.dimen.taskbar_stashed_small_screen
+ if (context.isPhoneMode) R.dimen.taskbar_stashed_small_screen
else R.dimen.taskbar_stashed_handle_width
)
}
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/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index a24cf4b..f9fc983 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -106,7 +106,7 @@
public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
mControllerCallbacks = callbacks;
- mBackgroundRenderer.updateStashedHandleWidth(mActivity.getDeviceProfile(), getResources());
+ mBackgroundRenderer.updateStashedHandleWidth(mActivity, getResources());
recreateControllers();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 73e32ab..3823c5a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -276,11 +276,10 @@
*/
public int getTaskbarBackgroundHeight() {
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- if (TaskbarManager.isPhoneMode(deviceProfile)) {
+ if (mActivity.isPhoneMode()) {
Resources resources = mActivity.getResources();
- Point taskbarDimensions =
- DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
- TaskbarManager.isPhoneMode(deviceProfile));
+ Point taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile,
+ resources, true /* isPhoneMode */);
return taskbarDimensions.y == -1 ?
deviceProfile.getDisplayInfo().currentSize.y :
taskbarDimensions.y;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index 6d1b558..4776f0c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -32,7 +32,6 @@
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinningEdu
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
-import com.android.launcher3.taskbar.TaskbarManager.isPhoneMode
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
import com.android.quickstep.util.LottieAnimationColorUtils
@@ -62,7 +61,7 @@
LoggableTaskbarController {
private val isTooltipEnabled: Boolean
- get() = !Utilities.isRunningInTestHarness() && !isPhoneMode(activityContext.deviceProfile)
+ get() = !Utilities.isRunningInTestHarness() && !activityContext.isPhoneMode
private val isOpen: Boolean
get() = tooltip?.isOpen ?: false
val isBeforeTooltipFeaturesStep: Boolean
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 9a37bcb..a850680 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -17,7 +17,6 @@
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
-import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
@@ -205,13 +204,6 @@
public void onStateTransitionComplete(LauncherState finalState) {
mLauncherState = finalState;
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
- // TODO(b/279514548) Cleans up bad state that can occur when user interacts with
- // taskbar on top of transparent activity.
- if (!FeatureFlags.enableHomeTransitionListener()
- && finalState == LauncherState.NORMAL
- && mLauncher.hasBeenResumed()) {
- updateStateForFlag(FLAG_VISIBLE, true);
- }
applyState();
boolean disallowLongClick =
FeatureFlags.enableSplitContextually()
@@ -223,6 +215,21 @@
}
};
+ /**
+ * Callback for when launcher state transition completes after user swipes to home.
+ * @param finalState The final state of the transition.
+ */
+ public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
+ // TODO(b/279514548) Cleans up bad state that can occur when user interacts with
+ // taskbar on top of transparent activity.
+ if (!FeatureFlags.enableHomeTransitionListener()
+ && (finalState == LauncherState.NORMAL)
+ && mLauncher.hasBeenResumed()) {
+ updateStateForFlag(FLAG_VISIBLE, true);
+ applyState();
+ }
+ }
+
/** Initializes the controller instance, and applies the initial state immediately. */
public void init(TaskbarControllers controllers, QuickstepLauncher launcher,
int sysuiStateFlags) {
@@ -726,7 +733,7 @@
}
mIconAlphaForHome.setValue(alpha);
boolean hotseatVisible = alpha == 0
- || isPhoneMode(mLauncher.getDeviceProfile())
+ || mControllers.taskbarActivityContext.isPhoneMode()
|| (!mControllers.uiController.isHotseatIconOnTopWhenAligned()
&& mIconAlignment.value > 0);
/*
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index bbac116..4f6e298 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -71,7 +71,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;
@@ -329,7 +328,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;
}
@@ -492,23 +494,7 @@
}
}
- /**
- * @return {@code true} if provided device profile isn't a large screen profile
- * and we are using a single window for taskbar and navbar.
- */
- public static boolean isPhoneMode(DeviceProfile deviceProfile) {
- return ENABLE_TASKBAR_NAVBAR_UNIFICATION && deviceProfile.isPhone;
- }
-
- /**
- * @return {@code true} if {@link #isPhoneMode(DeviceProfile)} is true and we're using
- * 3 button-nav
- */
- public static boolean isPhoneButtonNavMode(TaskbarActivityContext context) {
- return isPhoneMode(context.getDeviceProfile()) && context.isThreeButtonNav();
- }
-
- private boolean isTaskbarPresent(DeviceProfile deviceProfile) {
+ private static boolean isTaskbarPresent(DeviceProfile deviceProfile) {
return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
index 1224b3f..d09f74c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -86,4 +86,8 @@
public void setTaskbarWasPinned(boolean taskbarWasPinned) {
mTaskbarWasPinned = taskbarWasPinned;
}
+
+ // To track if taskbar was stashed / unstashed between configuration changes (which recreates
+ // the task bar).
+ public Boolean taskbarWasStashedAuto = true;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index c74ddcb..2f7f6f3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -21,6 +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;
@@ -56,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;
@@ -255,8 +259,9 @@
mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
- if (isPhoneMode()) {
- mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_size);
+ if (mActivity.isPhoneMode()) {
+ mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
+ R.dimen.taskbar_phone_size);
mStashedHeight = mActivity.getResources().getDimensionPixelSize(
R.dimen.taskbar_stashed_size);
} else {
@@ -306,17 +311,22 @@
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
- updateStateForFlag(FLAG_STASHED_IN_APP_AUTO,
- isTransientTaskbar && !mTaskbarSharedState.getTaskbarWasPinned());
+ boolean isStashedInAppAuto =
+ isTransientTaskbar && !mTaskbarSharedState.getTaskbarWasPinned();
+ if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
+ isStashedInAppAuto = isStashedInAppAuto && mTaskbarSharedState.taskbarWasStashedAuto;
+ }
+ updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, isStashedInAppAuto);
updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
updateStateForFlag(FLAG_IN_SETUP, isInSetup);
- updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, isPhoneMode()
+ updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, mActivity.isPhoneMode()
&& !mActivity.isThreeButtonNav());
// For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell
// us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
updateStateForFlag(FLAG_IN_APP, true);
applyState(/* duration = */ 0);
- if (mTaskbarSharedState.getTaskbarWasPinned()) {
+ if (mTaskbarSharedState.getTaskbarWasPinned()
+ || !mTaskbarSharedState.taskbarWasStashedAuto) {
tryStartTaskbarTimeout();
}
notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
@@ -379,13 +389,6 @@
return (hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing());
}
- /**
- * @return {@code true} if we're not on a large screen AND using gesture nav
- */
- private boolean isPhoneMode() {
- return TaskbarManager.isPhoneMode(mActivity.getDeviceProfile());
- }
-
private boolean hasAnyFlag(int flagMask) {
return hasAnyFlag(mState, flagMask);
}
@@ -421,7 +424,7 @@
* @see android.view.WindowInsets.Type#systemBars()
*/
public int getContentHeightToReportToApps() {
- if ((isPhoneMode() && !mActivity.isThreeButtonNav())
+ if ((mActivity.isPhoneMode() && !mActivity.isThreeButtonNav())
|| DisplayController.isTransientTaskbar(mActivity)) {
return getStashedHeight();
}
@@ -491,6 +494,7 @@
}
if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO) != stash) {
+ mTaskbarSharedState.taskbarWasStashedAuto = stash;
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
applyState();
}
@@ -571,7 +575,7 @@
mAnimator = new AnimatorSet();
addJankMonitorListener(mAnimator, /* appearing= */ !mIsStashed);
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
- final float stashTranslation = isPhoneMode() || isTransientTaskbar
+ final float stashTranslation = mActivity.isPhoneMode() || isTransientTaskbar
? 0
: (mUnstashedHeight - mStashedHeight);
@@ -643,7 +647,7 @@
firstHalfAnimatorSet.playTogether(
mIconAlphaForStash.animateToValue(0),
- mIconScaleForStash.animateToValue(isPhoneMode() ?
+ mIconScaleForStash.animateToValue(mActivity.isPhoneMode() ?
0 : STASHED_TASKBAR_SCALE)
);
secondHalfAnimatorSet.playTogether(
@@ -929,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) &&
- !(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 a29a25c..7edf0d3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -30,6 +30,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -218,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;
@@ -237,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
@@ -330,6 +331,14 @@
}
/**
+ * Callback for when launcher state transition completes after user swipes to home.
+ * @param finalState The final state of the transition.
+ */
+ public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
+ // Overridden
+ }
+
+ /**
* Refreshes the resumed state of this ui controller.
*/
public void refreshResumedState() {}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 2ab0066..74517a8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -123,7 +123,7 @@
mIconLayoutBounds = mActivityContext.getTransientTaskbarBounds();
Resources resources = getResources();
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivityContext)
- && !TaskbarManager.isPhoneMode(mActivityContext.getDeviceProfile());
+ && !mActivityContext.isPhoneMode();
mIsRtl = Utilities.isRtl(resources);
mTransientTaskbarMinWidth = resources.getDimension(R.dimen.transient_taskbar_min_width);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 14ab471..33fb395 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -29,8 +29,6 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
-import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
-import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM;
@@ -191,8 +189,8 @@
public void init(TaskbarControllers controllers) {
mControllers = controllers;
mTaskbarView.init(new TaskbarViewCallbacks());
- mTaskbarView.getLayoutParams().height = isPhoneMode(mActivity.getDeviceProfile())
- ? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_size)
+ mTaskbarView.getLayoutParams().height = mActivity.isPhoneMode()
+ ? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_phone_size)
: mActivity.getDeviceProfile().taskbarHeight;
mTaskbarIconScaleForStash.updateValue(1f);
@@ -219,7 +217,7 @@
// This gets modified in NavbarButtonsViewController, but the initial value it reads
// may be incorrect since it's state gets destroyed on taskbar recreate, so reset here
mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN)
- .animateToValue(isPhoneButtonNavMode(mActivity) ? 0 : 1).start();
+ .animateToValue(mActivity.isPhoneButtonNavMode() ? 0 : 1).start();
}
if (enableTaskbarPinning()) {
mTaskbarView.addOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
@@ -598,7 +596,7 @@
* 1 => fully aligned
*/
public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
- if (isPhoneMode(launcherDp)) {
+ if (mActivity.isPhoneMode()) {
mIconAlignControllerLazy = null;
return;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 5ce2a7a..964d329 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -222,7 +222,7 @@
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !mAppsView.shouldContainerScroll(ev)
|| getTopOpenViewWithType(
- mActivityContext, TYPE_ACCESSIBLE & ~TYPE_TASKBAR_OVERLAYS) != null;
+ mActivityContext, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null;
}
return super.onControllerInterceptTouchEvent(ev);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index c482911..ec9f4e5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -130,6 +130,8 @@
super(context, attrs, defStyleAttr, defStyleRes);
TaskbarActivityContext activityContext = ActivityContext.lookupContext(context);
+ setAlpha(0);
+ setVisibility(INVISIBLE);
mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
mIconSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index 3f51958..65b77ac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -24,8 +24,8 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
import com.android.systemui.shared.rotation.RotationButton
@@ -48,7 +48,7 @@
a11yButton
) {
- override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+ override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
val iconSize: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_ICON_SIZE_KIDS)
val buttonWidth: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS)
val buttonHeight: Int =
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 6b05e9a..22f0131 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -24,6 +24,7 @@
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.Companion
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
@@ -162,6 +163,6 @@
/** Lays out and provides access to the home, recents, and back buttons for various mischief */
interface NavButtonLayoutter {
- fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean)
+ fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
index 5a7bc49..3817f91 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -20,7 +20,7 @@
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.systemui.shared.rotation.RotationButton
/** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
@@ -43,7 +43,7 @@
a11yButton
) {
- override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+ override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 382e298..7583cc1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -23,9 +23,8 @@
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.core.view.children
-import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
-import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.util.DimensionUtils
import com.android.systemui.shared.rotation.RotationButton
@@ -48,11 +47,11 @@
a11yButton
) {
- override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+ 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(dp, resources,
- TaskbarManager.isPhoneMode(dp))
+ val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile,
+ resources, context.isPhoneMode)
navButtonContainer.removeAllViews()
navButtonContainer.orientation = LinearLayout.VERTICAL
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index e1ffa4d..4388ce6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -22,9 +22,8 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
-import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.util.DimensionUtils
import com.android.systemui.shared.rotation.RotationButton
@@ -47,11 +46,11 @@
a11yButton
) {
- override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+ override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
// TODO(b/230395757): Polish pending, this is just to make it usable
val taskbarDimensions =
- DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
- TaskbarManager.isPhoneMode(dp))
+ DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile, resources,
+ context.isPhoneMode)
val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
// Ensure order of buttons is correct
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index abdd32c..1ac0060 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -22,8 +22,8 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.systemui.shared.rotation.RotationButton
class SetupNavLayoutter(
@@ -45,7 +45,7 @@
a11yButton
) {
- override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+ override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
// Since setup wizard only has back button enabled, it looks strange to be
// end-aligned, so start-align instead.
val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index f5a4c64..5465b6b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -22,8 +22,8 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.systemui.shared.rotation.RotationButton
/**
@@ -48,9 +48,11 @@
a11yButton
) {
- override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+ override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
// Add spacing after the end of the last nav button
- var navMarginEnd = resources.getDimension(dp.inv.inlineNavButtonsEndSpacing).toInt()
+ var navMarginEnd = resources
+ .getDimension(context.deviceProfile.inv.inlineNavButtonsEndSpacing)
+ .toInt()
val contextualWidth = endContextualContainer.width
// If contextual buttons are showing, we check if the end margin is enough for the
// contextual button to be showing - if not, move the nav buttons over a smidge
@@ -91,7 +93,7 @@
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
- if (!dp.isGestureMode) {
+ if (!context.deviceProfile.isGestureMode) {
val contextualMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(endContextualContainer, 0, Gravity.END)
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 14e258b..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
@@ -707,6 +707,13 @@
}
@Override
+ public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
+ if (mTaskbarUIController != null) {
+ mTaskbarUIController.onStateTransitionCompletedAfterSwipeToHome(finalState);
+ }
+ }
+
+ @Override
protected void onResume() {
super.onResume();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
index 301fbe4..c1a85fa 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
@@ -28,6 +28,7 @@
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
+import static com.android.launcher3.LauncherPrefs.PRIVATE_SPACE_APPS;
import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_HIGHLIGHT_KEY;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
@@ -67,6 +68,7 @@
import androidx.preference.SwitchPreference;
import com.android.launcher3.ConstantItem;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
@@ -115,6 +117,9 @@
addAllAppsFromOverviewCatergory();
}
addCustomLpnhCategory();
+ if (Flags.enablePrivateSpace()) {
+ addCustomPrivateAppsCategory();
+ }
}
private void filterPreferences(String query, PreferenceGroup pg) {
@@ -365,6 +370,12 @@
}
}
+ private void addCustomPrivateAppsCategory() {
+ PreferenceCategory category = newCategory("Apps in Private Space Config");
+ category.addPreference(createSeekBarPreference(
+ "Number of Apps to put in private region", 0, 100, 1, PRIVATE_SPACE_APPS));
+ }
+
/**
* Create a preference with text and a seek bar. Should be added to a PreferenceCategory.
*
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index f6cd30a..82a9c05 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -45,6 +45,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TouchController;
@@ -194,7 +195,20 @@
recentsView.switchToScreenshot(null,
() -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
if (mStartState.overviewUi) {
- new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState),
+ Runnable onReachedHome = () -> {
+ StateManager.StateListener<LauncherState> listener =
+ new StateManager.StateListener<>() {
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ mLauncher.onStateTransitionCompletedAfterSwipeToHome(
+ finalState);
+ mLauncher.getStateManager().removeStateListener(this);
+ }
+ };
+ mLauncher.getStateManager().addStateListener(listener);
+ onSwipeInteractionCompleted(mEndState);
+ };
+ new OverviewToHomeAnim(mLauncher, onReachedHome,
FeatureFlags.enableSplitContextually()
? mCancelSplitRunnable
: null)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 968faf0..6d3b60a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides.touchcontrollers;
import static android.view.MotionEvent.ACTION_DOWN;
+
import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
import static com.android.app.animation.Interpolators.DECELERATE_3;
import static com.android.app.animation.Interpolators.LINEAR;
@@ -65,6 +66,7 @@
import android.view.MotionEvent;
import android.view.animation.Interpolator;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -190,8 +192,7 @@
public void onDragStart(boolean start) {
mMotionPauseDetector.clear();
if (start) {
- InteractionJankMonitorWrapper.begin(mRecentsView,
- InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+ InteractionJankMonitorWrapper.begin(mRecentsView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
mStartState = mLauncher.getStateManager().getState();
@@ -327,7 +328,7 @@
if (mMotionPauseDetector.isPaused() && noFling) {
// Going to Overview.
cancelAnimations();
- InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
StateAnimationConfig config = new StateAnimationConfig();
config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
@@ -445,7 +446,7 @@
RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
RecentsView.SCROLL_VIBRATION_FALLBACK);
} else {
- InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
}
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
@@ -469,7 +470,7 @@
: LAUNCHER_UNKNOWN_SWIPEDOWN));
if (targetState == QUICK_SWITCH_FROM_HOME) {
- InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
}
mLauncher.getStateManager().goToState(targetState, false, forEndCallback(this::clearState));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 8cbf239..d94cd89 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -15,8 +15,7 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
-import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
-import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
+import static com.android.launcher3.AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT;
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -25,6 +24,7 @@
import android.view.MotionEvent;
import com.android.app.animation.Interpolators;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
@@ -84,7 +84,7 @@
return false;
}
}
- if (getTopOpenViewWithType(mLauncher, TYPE_ACCESSIBLE | TYPE_ALL_APPS_EDU) != null) {
+ if (getTopOpenViewWithType(mLauncher, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) {
return false;
}
return true;
@@ -186,18 +186,15 @@
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
InteractionJankMonitorWrapper.begin(
- mLauncher.getRootView(), InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ mLauncher.getRootView(), Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS);
InteractionJankMonitorWrapper.begin(
- mLauncher.getRootView(),
- InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
+ mLauncher.getRootView(), Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
- InteractionJankMonitorWrapper.cancel(
- InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
- InteractionJankMonitorWrapper.cancel(
- InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE);
break;
}
return super.onControllerInterceptTouchEvent(ev);
@@ -208,11 +205,10 @@
protected void onReinitToState(LauncherState newToState) {
super.onReinitToState(newToState);
if (newToState != ALL_APPS) {
- InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS);
}
if (newToState != NORMAL) {
- InteractionJankMonitorWrapper.cancel(
- InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE);
}
}
@@ -220,18 +216,16 @@
protected void onReachedFinalState(LauncherState toState) {
super.onReachedFinalState(toState);
if (toState == ALL_APPS) {
- InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS);
} else if (toState == NORMAL) {
- InteractionJankMonitorWrapper.end(
- InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE);
}
}
@Override
protected void clearState() {
super.clearState();
- InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
- InteractionJankMonitorWrapper.cancel(
- InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 3d94857..19bfe06 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
-import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
+import static com.android.launcher3.AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
@@ -112,7 +112,8 @@
// If we are already animating from a previous state, we can intercept.
return true;
}
- if (AbstractFloatingView.getTopOpenViewWithType(mActivity, TYPE_ACCESSIBLE) != null) {
+ if (AbstractFloatingView.getTopOpenViewWithType(
+ mActivity, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) {
return false;
}
return isRecentsInteractive();
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index d7ff59e..42b18bd 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -96,6 +96,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import com.android.internal.jank.Cuj;
import com.android.internal.util.LatencyTracker;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
@@ -1024,12 +1025,12 @@
}
mHandled = true;
+ InteractionJankMonitorWrapper.begin(mRecentsView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH,
+ 2000 /* ms timeout */);
InteractionJankMonitorWrapper.begin(mRecentsView,
- InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timeout */);
+ Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
InteractionJankMonitorWrapper.begin(mRecentsView,
- InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
- InteractionJankMonitorWrapper.begin(mRecentsView,
- InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
+ Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
rv.post(() -> rv.getViewTreeObserver().removeOnDrawListener(this));
}
@@ -1145,16 +1146,13 @@
View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget);
if (endTarget != NEW_TASK) {
- InteractionJankMonitorWrapper.cancel(
- InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
}
if (endTarget != HOME) {
- InteractionJankMonitorWrapper.cancel(
- InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
}
if (endTarget != RECENTS) {
- InteractionJankMonitorWrapper.cancel(
- InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
}
switch (endTarget) {
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 9e58160..0f88aac 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -23,6 +23,8 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
+import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -52,6 +54,7 @@
import com.android.internal.view.AppearanceRegion;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -284,7 +287,7 @@
mBackInProgress = true;
RemoteAnimationTarget appTarget = backEvent.getDepartingAnimationTarget();
- if (appTarget == null) {
+ if (appTarget == null || appTarget.leash == null || !appTarget.leash.isValid()) {
return;
}
@@ -294,8 +297,12 @@
mBackTarget = appTarget;
mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
- // TODO(b/218916755): Offset start rectangle in multiwindow mode.
mStartRect.set(appTarget.windowConfiguration.getMaxBounds());
+ if (mLauncher.getDeviceProfile().isTaskbarPresent && enableTaskbarPinning()
+ && LauncherPrefs.get(mLauncher).get(TASKBAR_PINNING)) {
+ int insetBottom = mStartRect.bottom - appTarget.contentInsets.bottom;
+ mStartRect.set(mStartRect.left, mStartRect.top, mStartRect.right, insetBottom);
+ }
mCurrentRect.set(mStartRect);
addScrimLayer();
mTransaction.apply();
@@ -449,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
new file mode 100644
index 0000000..fc450f0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
@@ -0,0 +1,148 @@
+package com.android.quickstep
+
+import android.app.backup.BackupManager
+import android.app.backup.BackupRestoreEventLogger
+import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType
+import android.app.backup.BackupRestoreEventLogger.BackupRestoreError
+import android.content.Context
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
+
+/**
+ * Concrete implementation for wrapper to log Restore event metrics for both success and failure to
+ * restore Launcher workspace from a backup. This implementation accesses SystemApis so is only
+ * available to QuickStep/NexusLauncher.
+ */
+class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEventLogger() {
+ companion object {
+ const val TAG = "LauncherRestoreEventLoggerImpl"
+
+ // Generic type for any possible workspace items, when specific type is not known.
+ @BackupRestoreDataType private const val DATA_TYPE_LAUNCHER_ITEM = "launcher_item"
+ // Specific workspace item types, based off of Favorites Table.
+ @BackupRestoreDataType private const val DATA_TYPE_APPLICATION = "application"
+ @BackupRestoreDataType private const val DATA_TYPE_FOLDER = "folder"
+ @BackupRestoreDataType private const val DATA_TYPE_APPWIDGET = "widget"
+ @BackupRestoreDataType private const val DATA_TYPE_CUSTOM_APPWIDGET = "custom_widget"
+ @BackupRestoreDataType private const val DATA_TYPE_DEEP_SHORTCUT = "deep_shortcut"
+ @BackupRestoreDataType private const val DATA_TYPE_APP_PAIR = "app_pair"
+ }
+
+ private val restoreEventLogger: BackupRestoreEventLogger =
+ BackupManager(context).delayedRestoreLogger
+
+ /**
+ * For logging when multiple items of a given data type failed to restore.
+ *
+ * @param dataType The data type that was not restored.
+ * @param count the number of data items that were not restored.
+ * @param error error type for why the data was not restored.
+ */
+ override fun logLauncherItemsRestoreFailed(
+ @BackupRestoreDataType dataType: String,
+ count: Int,
+ @BackupRestoreError error: String?
+ ) {
+ if (Flags.enableLauncherBrMetrics()) {
+ restoreEventLogger.logItemsRestoreFailed(dataType, count, error)
+ }
+ }
+
+ /**
+ * For logging when multiple items of a given data type were successfully restored.
+ *
+ * @param dataType The data type that was restored.
+ * @param count the number of data items restored.
+ */
+ override fun logLauncherItemsRestored(@BackupRestoreDataType dataType: String, count: Int) {
+ if (Flags.enableLauncherBrMetrics()) {
+ restoreEventLogger.logItemsRestored(dataType, count)
+ }
+ }
+
+ /**
+ * Helper to log successfully restoring a single item from the Favorites table.
+ *
+ * @param favoritesId The id of the item type from [Favorites] that was restored.
+ */
+ override fun logSingleFavoritesItemRestored(favoritesId: Int) {
+ if (Flags.enableLauncherBrMetrics()) {
+ restoreEventLogger.logItemsRestored(favoritesIdToDataType(favoritesId), 1)
+ }
+ }
+
+ /**
+ * 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.
+ * @param error error type for why the data was not restored.
+ */
+ override fun logSingleFavoritesItemRestoreFailed(
+ favoritesId: Int,
+ @BackupRestoreError error: String?
+ ) {
+ if (Flags.enableLauncherBrMetrics()) {
+ restoreEventLogger.logItemsRestoreFailed(favoritesIdToDataType(favoritesId), 1, error)
+ }
+ }
+
+ /**
+ * Helper to log a failure to restore items from the Favorites table.
+ *
+ * @param favoritesId The id of the item type from [Favorites] that was not restored.
+ * @param count number of items that failed to restore.
+ * @param error error type for why the data was not restored.
+ */
+ override fun logFavoritesItemsRestoreFailed(
+ favoritesId: Int,
+ count: Int,
+ @BackupRestoreError error: String?
+ ) {
+ if (Flags.enableLauncherBrMetrics()) {
+ restoreEventLogger.logItemsRestoreFailed(
+ favoritesIdToDataType(favoritesId),
+ count,
+ error
+ )
+ }
+ }
+
+ /**
+ * Uses the current [restoreEventLogger] to report its results to the [backupManager]. Use when
+ * done restoring items for Launcher.
+ */
+ override fun reportLauncherRestoreResults() {
+ if (Flags.enableLauncherBrMetrics()) {
+ BackupManager(context).reportDelayedRestoreResult(restoreEventLogger)
+ }
+ }
+
+ /**
+ * Helper method to convert item types from [Favorites] to B&R data types for logging. Also to
+ * avoid direct usage of @BackupRestoreDataType which is protected under @SystemApi.
+ */
+ @BackupRestoreDataType
+ private fun favoritesIdToDataType(favoritesId: Int): String =
+ when (favoritesId) {
+ Favorites.ITEM_TYPE_APPLICATION -> DATA_TYPE_APPLICATION
+ Favorites.ITEM_TYPE_FOLDER -> DATA_TYPE_FOLDER
+ Favorites.ITEM_TYPE_APPWIDGET -> DATA_TYPE_APPWIDGET
+ Favorites.ITEM_TYPE_CUSTOM_APPWIDGET -> DATA_TYPE_CUSTOM_APPWIDGET
+ Favorites.ITEM_TYPE_DEEP_SHORTCUT -> DATA_TYPE_DEEP_SHORTCUT
+ Favorites.ITEM_TYPE_APP_PAIR -> DATA_TYPE_APP_PAIR
+ else -> DATA_TYPE_LAUNCHER_ITEM
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 31fe791..b2429ad 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -31,6 +31,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -262,7 +263,7 @@
if (activity != null) {
InteractionJankMonitorWrapper.begin(
activity.getRootView(),
- InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+ Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
}
GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE,
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/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 341e18c..06a442b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -25,14 +25,13 @@
import android.os.RemoteException;
import android.util.Log;
import android.view.IRecentsAnimationController;
-import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManagerGlobal;
import android.window.PictureInPictureSurfaceTransaction;
-import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
+import com.android.internal.jank.Cuj;
import com.android.internal.os.IResultReceiver;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
@@ -183,10 +182,9 @@
});
}
});
- InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
- InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
- InteractionJankMonitorWrapper.end(
- InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
};
if (forceFinish) {
finishCb.run();
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 11c5ab4..4e84f4a 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -60,6 +60,7 @@
import androidx.annotation.Nullable;
import com.android.app.animation.Interpolators;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimatedFloat;
@@ -395,8 +396,7 @@
@Override
public void onAnimationSuccess(Animator animator) {
if (isQuickSwitch) {
- InteractionJankMonitorWrapper.end(
- InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
}
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index bd4625b..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;
@@ -974,8 +981,8 @@
TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
if (tac != null && !(base instanceof AssistantInputConsumer)) {
// Present always on large screen or on small screen w/ flag
- DeviceProfile dp = tac.getDeviceProfile();
- boolean useTaskbarConsumer = dp.isTaskbarPresent && !TaskbarManager.isPhoneMode(dp)
+ boolean useTaskbarConsumer = tac.getDeviceProfile().isTaskbarPresent
+ && !tac.isPhoneMode()
&& !tac.isInStashedLauncherState();
if (canStartSystemGesture && useTaskbarConsumer) {
reasonString.append(NEWLINE_PREFIX)
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 69c15a5..c91ee81 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -25,6 +25,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Insets;
@@ -86,6 +87,8 @@
private boolean mIsFoldable;
private boolean mOnAttachedToWindowPendingCreate;
+ @Nullable private Runnable mOnAttachedOnGlobalLayoutCallback = null;
+
public static TutorialFragment newInstance(
TutorialType tutorialType, boolean gestureComplete, boolean fromTutorialMenu) {
TutorialFragment fragment = getFragmentForTutorialType(tutorialType, fromTutorialMenu);
@@ -349,13 +352,27 @@
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
- changeController(mTutorialType);
+ runOnAttached(() -> changeController(mTutorialType));
mRootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
}
+ private void runOnAttached(Runnable callback) {
+ mOnAttachedOnGlobalLayoutCallback = callback;
+ if (getContext() != null) {
+ onAttached();
+ }
+ }
+
+ private void onAttached() {
+ if (mOnAttachedOnGlobalLayoutCallback != null) {
+ mOnAttachedOnGlobalLayoutCallback.run();
+ mOnAttachedOnGlobalLayoutCallback = null;
+ }
+ }
+
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (mTutorialController != null && !isGestureComplete()) {
@@ -378,6 +395,12 @@
}
@Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ onAttached();
+ }
+
+ @Override
void onAttachedToWindow() {
if (mEdgeBackGestureHandler == null) {
mOnAttachedToWindowPendingCreate = true;
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index bce2e82..cf9fc74 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -42,6 +42,7 @@
import androidx.annotation.WorkerThread;
import androidx.slice.SliceItem;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
import com.android.launcher3.logger.LauncherAtom;
@@ -401,11 +402,10 @@
case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN:
InteractionJankMonitorWrapper.begin(
view,
- InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL);
+ Cuj.CUJ_LAUNCHER_ALL_APPS_SCROLL);
break;
case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END:
- InteractionJankMonitorWrapper.end(
- InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_ALL_APPS_SCROLL);
break;
default:
break;
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 ade8074..ad9f5ea 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -23,6 +23,7 @@
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.graphics.Bitmap
import android.graphics.Rect
import android.graphics.RectF
@@ -31,9 +32,11 @@
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.View
-import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
+import android.window.WindowContainerToken
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators
import com.android.launcher3.DeviceProfile
@@ -46,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
@@ -387,14 +391,15 @@
"trying to launch an app pair icon, but encountered an unexpected null"
}
- composeIconSplitLaunchAnimator(
- launchingIconView,
- initialTaskId,
- secondTaskId,
- info,
- t,
- finishCallback
- )
+ // 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
check(info != null && t != null) {
@@ -461,12 +466,27 @@
/**
* When the user taps an app pair icon to launch split, this will play the tasks' launch
* animation from the position of the icon.
+ *
+ * To find the root shell leash that we want to fade in, we do the following:
+ * The Changes we receive in transitionInfo are structured like this
+ *
+ * Root (grandparent)
+ * |
+ * |--> Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW)
+ * | |
+ * | --> App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW)
+ * |--> Divider
+ * |--> Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW)
+ * |
+ * --> App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW)
+ *
+ * We want to animate the Root (grandparent) so that it affects both apps and the divider.
+ * To do this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the
+ * left-side ones, for simplicity) and traverse the tree until we find the grandparent.
*/
@VisibleForTesting
fun composeIconSplitLaunchAnimator(
launchingIconView: AppPairIcon,
- initialTaskId: Int,
- secondTaskId: Int,
transitionInfo: TransitionInfo,
t: Transaction,
finishCallback: Runnable
@@ -481,46 +501,47 @@
progressUpdater.setDuration(timings.getDuration().toLong())
progressUpdater.interpolator = Interpolators.LINEAR
- // Find the root shell leash that we want to fade in (parent of both app windows and
- // the divider). For simplicity, we search using the initialTaskId.
- var rootShellLayer: SurfaceControl? = null
- var dividerPos = 0
+ var rootCandidate: Change? = null
for (change in transitionInfo.changes) {
val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
- val taskId = taskInfo.taskId
- val mode = change.mode
- if (taskId == initialTaskId || taskId == secondTaskId) {
- check(
- mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT
- ) {
- "Expected task to be showing, but it is $mode"
+ // TODO (b/316490565): Replace this logic when SplitBounds is available to
+ // startAnimation() and we can know the precise taskIds of launching tasks.
+ // Find a change that has WINDOWING_MODE_MULTI_WINDOW.
+ if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
+ (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)) {
+ // Check if it is a left/top app.
+ val isLeftTopApp =
+ (dp.isLeftRightSplit && change.endAbsBounds.left == 0) ||
+ (!dp.isLeftRightSplit && change.endAbsBounds.top == 0)
+ if (isLeftTopApp) {
+ // Found one!
+ rootCandidate = change
+ break
}
}
-
- if (taskId == initialTaskId) {
- var splitRoot1 = change
- val parentToken = change.parent
- if (parentToken != null) {
- splitRoot1 = transitionInfo.getChange(parentToken) ?: change
- }
-
- val topLevelToken = splitRoot1.parent
- if (topLevelToken != null) {
- rootShellLayer = transitionInfo.getChange(topLevelToken)?.leash
- }
-
- dividerPos =
- if (dp.isLeftRightSplit) change.endAbsBounds.right
- else change.endAbsBounds.bottom
- }
}
- check(rootShellLayer != null) {
- "Could not find a TransitionInfo.Change matching the initialTaskId"
+ // If we could not find a proper root candidate, something went wrong.
+ check(rootCandidate != null) { "Could not find a split root candidate" }
+
+ // Find the place where our left/top app window meets the divider (used for the
+ // launcher side animation)
+ val dividerPos =
+ if (dp.isLeftRightSplit) rootCandidate.endAbsBounds.right
+ else rootCandidate.endAbsBounds.bottom
+
+ // Recurse up the tree until parent is null, then we've found our root.
+ var parentToken: WindowContainerToken? = rootCandidate.parent
+ while (parentToken != null) {
+ rootCandidate = transitionInfo.getChange(parentToken) ?: break
+ parentToken = rootCandidate.parent
}
+ // Make sure nothing weird happened, like getChange() returning null.
+ check(rootCandidate != null) { "Failed to find a root leash" }
+
// Shell animation: the apps are revealed toward end of the launch animation
progressUpdater.addUpdateListener { valueAnimator: ValueAnimator ->
val progress =
@@ -532,7 +553,7 @@
)
// Set the alpha of the shell layer (2 apps + divider)
- t.setAlpha(rootShellLayer, progress)
+ t.setAlpha(rootCandidate.leash, progress)
t.apply()
}
@@ -651,9 +672,7 @@
// Find the target tasks' root tasks since those are the split stages that need to
// be animated (the tasks themselves are children and thus inherit animation).
if (taskId == initialTaskId || taskId == secondTaskId) {
- check(
- mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT
- ) {
+ check(mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
"Expected task to be showing, but it is $mode"
}
}
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/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index e705285..28efc97 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -34,6 +34,7 @@
import android.os.UserHandle;
import android.view.View;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
@@ -174,8 +175,7 @@
public void onAnimationEnd(Animator animation) {
if (!mIsCancelled) {
mController.launchSplitTasks(aBoolean -> cleanUp());
- InteractionJankMonitorWrapper.end(
- InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index fe6ce46..a36b32c 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -16,8 +16,6 @@
package com.android.quickstep.util;
-import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_PIP;
-
import android.animation.Animator;
import android.animation.RectEvaluator;
import android.content.ComponentName;
@@ -35,6 +33,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.icons.IconProvider;
import com.android.quickstep.TaskAnimationManager;
@@ -174,19 +173,19 @@
addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
- InteractionJankMonitorWrapper.begin(view, CUJ_APP_CLOSE_TO_PIP);
+ InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
super.onAnimationStart(animation);
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
- InteractionJankMonitorWrapper.cancel(CUJ_APP_CLOSE_TO_PIP);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
}
@Override
public void onAnimationSuccess(Animator animator) {
- InteractionJankMonitorWrapper.end(CUJ_APP_CLOSE_TO_PIP);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 5d8e53e..baaa062 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -427,9 +427,16 @@
// conflict with layers that WM core positions (ie. the input consumers). For shell
// transitions, the animation leashes are reparented to an animation container so we
// can bump layers as needed.
- builder.setLayer(mDrawsBelowRecents
- ? Integer.MIN_VALUE + app.prefixOrderIndex
- : ENABLE_SHELL_TRANSITIONS ? Integer.MAX_VALUE : 0);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ builder.setLayer(mDrawsBelowRecents
+ ? Integer.MIN_VALUE + app.prefixOrderIndex
+ // 1000 is an arbitrary number to give room for multiple layers.
+ : Integer.MAX_VALUE - 1000 + app.prefixOrderIndex);
+ } else {
+ builder.setLayer(mDrawsBelowRecents
+ ? Integer.MIN_VALUE + app.prefixOrderIndex
+ : 0);
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/util/unfold/LauncherJankMonitorTransitionProgressListener.kt b/quickstep/src/com/android/quickstep/util/unfold/LauncherJankMonitorTransitionProgressListener.kt
index 4f89c7e..b4ca35e 100644
--- a/quickstep/src/com/android/quickstep/util/unfold/LauncherJankMonitorTransitionProgressListener.kt
+++ b/quickstep/src/com/android/quickstep/util/unfold/LauncherJankMonitorTransitionProgressListener.kt
@@ -16,6 +16,7 @@
package com.android.quickstep.util.unfold
import android.view.View
+import com.android.internal.jank.Cuj
import com.android.systemui.shared.system.InteractionJankMonitorWrapper
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import java.util.function.Supplier
@@ -28,11 +29,11 @@
override fun onTransitionStarted() {
InteractionJankMonitorWrapper.begin(
attachedViewProvider.get(),
- InteractionJankMonitorWrapper.CUJ_LAUNCHER_UNFOLD_ANIM
+ Cuj.CUJ_LAUNCHER_UNFOLD_ANIM
)
}
override fun onTransitionFinished() {
- InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_LAUNCHER_UNFOLD_ANIM)
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_UNFOLD_ANIM)
}
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index dd201af..2ae64ff 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -17,6 +17,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -246,12 +247,11 @@
RunnableList endCallback = new RunnableList();
RecentsView recentsView = getRecentsView();
// Callbacks run from remote animation when recents animation not currently running
- InteractionJankMonitorWrapper.begin(this,
- InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Enter form GroupedTaskView");
+ InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER,
+ "Enter form GroupedTaskView");
launchTaskInternal(success -> {
endCallback.executeAllAndDestroy();
- InteractionJankMonitorWrapper.end(
- InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
}, false /* freezeTaskList */, true /*launchingExistingTaskview*/);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 960fe6c..ebb6ba8 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -133,6 +133,7 @@
import androidx.annotation.UiThread;
import androidx.core.graphics.ColorUtils;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener;
import com.android.launcher3.DeviceProfile;
@@ -1444,8 +1445,7 @@
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
}
if (mOverviewStateEnabled) { // only when in overview
- InteractionJankMonitorWrapper.begin(/* view= */ this,
- InteractionJankMonitorWrapper.CUJ_RECENTS_SCROLLING);
+ InteractionJankMonitorWrapper.begin(/* view= */ this, Cuj.CUJ_RECENTS_SCROLLING);
}
}
@@ -1460,7 +1460,7 @@
if (getNextPage() > 0) {
setSwipeDownShouldLaunchApp(true);
}
- InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_RECENTS_SCROLLING);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_RECENTS_SCROLLING);
}
@Override
@@ -3314,8 +3314,8 @@
timings.getInstructionsUnfoldEndOffset()));
mSplitSelectStateController.setSplitInstructionsView(splitInstructionsView);
- InteractionJankMonitorWrapper.begin(this,
- InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "First tile selected");
+ InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER,
+ "First tile selected");
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -3331,8 +3331,7 @@
});
anim.addEndListener(success -> {
if (success) {
- InteractionJankMonitorWrapper.end(
- InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
} else {
// If transition to split select was interrupted, clean up to prevent glitches
if (FeatureFlags.enableSplitContextually()) {
@@ -3340,8 +3339,7 @@
} else {
resetFromSplitSelectionState();
}
- InteractionJankMonitorWrapper.cancel(
- InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_SPLIT_SCREEN_ENTER);
}
updateCurrentTaskActionsVisibility();
@@ -4837,8 +4835,7 @@
} else {
resetFromSplitSelectionState();
}
- InteractionJankMonitorWrapper.end(
- InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
});
});
@@ -4848,8 +4845,8 @@
mSplitSelectStateController.getSecondTaskId());
}
- InteractionJankMonitorWrapper.begin(this,
- InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected");
+ InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER,
+ "Second tile selected");
// Fade out all other views underneath placeholders
ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index b42f055..66a880b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -1035,9 +1035,6 @@
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- if (!recentsView.showAsGrid()) {
- return;
- }
recentsView.runActionOnRemoteHandles(
(Consumer<RemoteTargetHandle>) remoteTargetHandle ->
remoteTargetHandle
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 eced5a9..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;
@@ -131,8 +131,7 @@
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
getLauncherCommand(getLauncherInMyProcess()));
// b/143488140
- mDevice.pressHome();
- mDevice.waitForIdle();
+ pressHomeAndWaitForOverviewClose();
}
}
};
@@ -144,7 +143,7 @@
.around(new TestStabilityRule())
.around(new NavigationModeSwitchRule(mLauncher))
.around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
- .around(viewCaptureRule)
+ // .around(viewCaptureRule) b/315482167
.around(new TestIsolationRule(mLauncher, false))
.around(setLauncherCommand);
@@ -218,10 +217,21 @@
}
private BaseOverview pressHomeAndGoToOverview() {
- mDevice.pressHome();
+ pressHomeAndWaitForOverviewClose();
return mLauncher.getLaunchedAppState().switchToOverview();
}
+ private void pressHomeAndWaitForOverviewClose() {
+ mDevice.pressHome();
+ waitForRecentsActivityStop();
+ }
+
+ private void waitForRecentsActivityStop() {
+ Wait.atMost("Recents activity didn't stop",
+ () -> getFromRecents(recents -> !recents.isStarted()),
+ DEFAULT_UI_TIMEOUT, mLauncher);
+ }
+
// b/143488140
//@NavigationModeSwitch
@Test
@@ -270,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/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 6cbe171..25adb62 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -358,14 +358,11 @@
// Debug if we need to goHome to prevent wrong previous state b/315525621
mLauncher.goHome();
assumeFalse(FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get());
- mLauncher.getWorkspace().switchToAllApps();
- mLauncher.pressBack();
- mLauncher.getWorkspace();
+ mLauncher.getWorkspace().switchToAllApps().pressBackToWorkspace();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
startAppFast(CALCULATOR_APP_PACKAGE);
- mLauncher.pressBack();
- mLauncher.getWorkspace();
+ mLauncher.getLaunchedAppState().pressBackToWorkspace();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
}
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 86018b1..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,9 +251,10 @@
@Test
fun playsAppropriateSplitLaunchAnimation_playsIconLaunchCorrectly() {
val spySplitAnimationController = spy(splitAnimationController)
+ whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper)
doNothing()
.whenever(spySplitAnimationController)
- .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any(), any())
+ .composeIconSplitLaunchAnimator(any(), any(), any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
null /* launchingTaskView */,
@@ -267,7 +272,34 @@
)
verify(spySplitAnimationController)
- .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any(), any())
+ .composeIconSplitLaunchAnimator(any(), any(), any(), any())
+ }
+
+ @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
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/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/folder_icon.xml b/res/layout/folder_icon.xml
index 4093744..6af346e 100644
--- a/res/layout/folder_icon.xml
+++ b/res/layout/folder_icon.xml
@@ -19,7 +19,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:focusable="true" >
+ android:focusable="true"
+ android:defaultFocusHighlightEnabled="false">
<com.android.launcher3.views.DoubleShadowBubbleTextView
style="@style/BaseIcon.Workspace"
android:id="@+id/folder_icon_name"
diff --git a/res/layout/widgets_two_pane_sheet_foldable.xml b/res/layout/widgets_two_pane_sheet_foldable.xml
deleted file mode 100644
index 93c0c70..0000000
--- a/res/layout/widgets_two_pane_sheet_foldable.xml
+++ /dev/null
@@ -1,131 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.launcher3.widget.picker.WidgetsTwoPaneSheet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:orientation="vertical"
- android:theme="?attr/widgetsTheme">
-
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:focusable="true"
- android:importantForAccessibility="no">
-
- <View
- android:id="@+id/collapse_handle"
- android:gravity="center_horizontal"
- android:layout_width="@dimen/bottom_sheet_handle_width"
- android:layout_height="@dimen/bottom_sheet_handle_height"
- android:layout_marginTop="@dimen/bottom_sheet_handle_margin"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- android:background="@drawable/widget_picker_collapse_handle"/>
-
- <TextView
- android:id="@+id/title"
- android:gravity="center_horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="24dp"
- android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
- android:text="@string/widget_button_text"
- app:layout_constraintTop_toBottomOf="@id/collapse_handle"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- android:textColor="?attr/widgetPickerTitleColor"
- android:textSize="24sp" />
-
- <FrameLayout
- android:id="@+id/recycler_view_container"
- android:layout_width="0dp"
- android:layout_height="0dp"
- app:layout_constraintTop_toBottomOf="@id/title"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintWidth_percent="0.33"
- app:layout_constraintWidth_min="254dp"
- app:layout_constraintWidth_max="395dp">
- <TextView
- android:id="@+id/fast_scroller_popup"
- style="@style/FastScrollerPopup"
- android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
-
- <!-- Fast scroller popup -->
- <com.android.launcher3.views.RecyclerViewFastScroller
- android:id="@+id/fast_scroller"
- android:layout_width="@dimen/fastscroll_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/fastscroll_end_margin" />
-
- <com.android.launcher3.widget.picker.WidgetsRecyclerView
- android:id="@+id/search_widgets_list_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipToPadding="false"
- android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
- android:visibility="gone" />
- </FrameLayout>
-
- <FrameLayout
- android:id="@+id/right_pane_container"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_marginEnd="@dimen/widget_list_horizontal_margin_two_pane"
- android:paddingTop="@dimen/widget_list_horizontal_margin_two_pane"
- app:layout_constraintTop_toBottomOf="@id/title"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@id/recycler_view_container"
- app:layout_constraintEnd_toEndOf="parent">
- <TextView
- android:id="@+id/no_widgets_text"
- style="@style/PrimaryHeadline"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:textSize="18sp"
- android:visibility="gone"
- tools:text="No widgets available" />
- <ScrollView
- android:id="@+id/right_pane_scroll_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fillViewport="true">
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:clipToOutline="true"
- android:paddingBottom="36dp"
- android:background="@drawable/widgets_surface_background"
- android:id="@+id/right_pane">
- <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
- android:id="@+id/recommended_widget_table"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingHorizontal=
- "@dimen/widget_list_horizontal_margin_two_pane"
- android:visibility="gone" />
- </LinearLayout>
- </ScrollView>
- </FrameLayout>
- </androidx.constraintlayout.widget.ConstraintLayout>
-</com.android.launcher3.widget.picker.WidgetsTwoPaneSheet>
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-da/strings.xml b/res/values-da/strings.xml
index 9d5a55f..415d1f3 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -173,10 +173,10 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Genoptag"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Mislykket: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"Privat rum"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"Privat område"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privat"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Indstillinger for privat rum"</string>
- <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lås/oplås det private rum"</string>
- <string name="ps_container_transition" msgid="8667331812048014412">"Ændringer af tilstanden for det private rum"</string>
+ <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lås/oplås det private område"</string>
+ <string name="ps_container_transition" msgid="8667331812048014412">"Ændringer af tilstanden for det private område"</string>
<string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Overløb"</string>
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 6c3b54c..db9631a 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -25,6 +25,7 @@
<color name="uninstall_target_hover_tint">#FFF0592B</color>
<color name="focused_background">#80c6c5c5</color>
+ <color name="focus_outline_color">@color/material_color_on_secondary_container</color>
<color name="default_shadow_color_no_alpha">#FF000000</color>
@@ -71,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/config.xml b/res/values/config.xml
index 154312a..2980635 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -86,6 +86,7 @@
<string name="widget_holder_factory_class" translatable="false"></string>
<string name="taskbar_search_session_controller_class" translatable="false"></string>
<string name="taskbar_model_callbacks_factory_class" translatable="false"></string>
+ <string name="launcher_restore_event_logger_class" translatable="false"></string>
<!-- View ID to use for QSB widget -->
<item type="id" name="qsb_widget" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 242c439..0ebcbf3 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -56,7 +56,7 @@
<!-- App Widget resize frame -->
<dimen name="widget_handle_margin">13dp</dimen>
<dimen name="resize_frame_background_padding">24dp</dimen>
- <dimen name="resize_frame_margin">22dp</dimen>
+ <dimen name="resize_frame_margin">23dp</dimen>
<dimen name="resize_frame_invalid_drag_across_two_panel_opacity_margin">24dp</dimen>
<!-- App widget reconfigure button -->
@@ -125,6 +125,9 @@
<dimen name="all_apps_tip_bottom_margin">8dp</dimen>
<dimen name="all_apps_height_extra">6dp</dimen>
<dimen name="all_apps_paged_view_top_padding">40dp</dimen>
+ <dimen name="all_apps_recycler_view_decorator_padding">1dp</dimen>
+ <dimen name="all_apps_recycler_view_decorator_group_radius">28dp</dimen>
+ <dimen name="all_apps_recycler_view_decorator_result_radius">4dp</dimen>
<dimen name="all_apps_icon_drawable_padding">8dp</dimen>
<dimen name="all_apps_predicted_icon_vertical_padding">8dp</dimen>
@@ -152,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>
@@ -364,6 +368,7 @@
<!-- 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_stashed_size">0dp</dimen>
<dimen name="qsb_widget_height">0dp</dimen>
<dimen name="qsb_shadow_height">0dp</dimen>
@@ -432,6 +437,9 @@
<dimen name="split_instructions_bottom_margin_phone_portrait">60dp</dimen>
<dimen name="split_instructions_start_margin_cancel">8dp</dimen>
+ <dimen name="focus_outline_radius">16dp</dimen>
+ <dimen name="focus_outline_stroke_width">3dp</dimen>
+
<!-- Workspace grid visualization parameters -->
<dimen name="grid_visualization_rounding_radius">16dp</dimen>
<dimen name="grid_visualization_horizontal_cell_spacing">6dp</dimen>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index d78afd3..f72c556 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -135,6 +135,10 @@
public static final int TYPE_TASKBAR_OVERLAYS =
TYPE_TASKBAR_ALL_APPS | TYPE_TASKBAR_EDUCATION_DIALOG;
+ // Floating views that a TouchController should not try to intercept touches from.
+ public static final int TYPE_TOUCH_CONTROLLER_NO_INTERCEPT = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE
+ & ~TYPE_LISTENER & ~TYPE_TASKBAR_OVERLAYS;
+
public static final int TYPE_ALL_EXCEPT_ON_BOARD_POPUP = TYPE_ALL & ~TYPE_ON_BOARD_POPUP
& ~TYPE_PIN_IME_POPUP;
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index 641fd83..429978e 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -9,9 +9,13 @@
import android.content.Intent;
import android.util.Log;
+import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.widget.LauncherWidgetHolder;
+import java.util.Arrays;
+
public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
private static final String TAG = "AppWidgetsRestoredReceiver";
@@ -20,8 +24,11 @@
public void onReceive(final Context context, Intent intent) {
if (AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED.equals(intent.getAction())) {
int hostId = intent.getIntExtra(AppWidgetManager.EXTRA_HOST_ID, 0);
- Log.d(TAG, "Widget ID map received for host:" + hostId);
+ Log.d(TAG, "onReceive: Widget ID map received for host:" + hostId);
if (hostId != LauncherWidgetHolder.APPWIDGET_HOST_ID) {
+ Log.w(TAG, "onReceive: hostId does not match Launcher."
+ + " Expected: " + LauncherWidgetHolder.APPWIDGET_HOST_ID
+ + ", Actual: " + hostId);
return;
}
@@ -31,8 +38,18 @@
LauncherPrefs.get(context).putSync(
OLD_APP_WIDGET_IDS.to(IntArray.wrap(oldIds).toConcatString()),
APP_WIDGET_IDS.to(IntArray.wrap(newIds).toConcatString()));
+ FileLog.d(TAG, "onReceive: Valid Widget IDs received."
+ + " old IDs=" + Arrays.toString(oldIds)
+ + ", new IDs=" + Arrays.toString(newIds));
+ if (!RestoreDbTask.isPending(context)) {
+ FileLog.w(TAG, "onReceive: Restored App Widget Ids received but Launcher"
+ + " restore is not pending. New widget Ids might not get restored.");
+ }
} else {
- Log.e(TAG, "Invalid host restored received");
+ Log.e(TAG, "onReceive: Invalid widget ids received for Launcher"
+ + ", skipping restore of widget ids."
+ + " newIds=" + Arrays.toString(newIds)
+ + ", oldIds=" + Arrays.toString(oldIds));
}
}
}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e2e528c..91da7e6 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -175,6 +175,7 @@
@ViewDebug.ExportedProperty(category = "launcher")
private DotInfo mDotInfo;
private DotRenderer mDotRenderer;
+ private Locale mCurrentLocale;
@ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
protected DotRenderer.DrawParams mDotParams;
private Animator mDotScaleAnim;
@@ -250,6 +251,7 @@
mDotParams = new DotRenderer.DrawParams();
+ mCurrentLocale = context.getResources().getConfiguration().locale;
setEllipsize(TruncateAt.END);
setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
setTextAlpha(1f);
@@ -411,10 +413,12 @@
* Only if actual text can be displayed in two line, the {@code true} value will be effective.
*/
protected boolean shouldUseTwoLine() {
- return ((FeatureFlags.enableTwolineAllapps())
- && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW))
- || (FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()
- && mDisplay == DISPLAY_SEARCH_RESULT);
+ return (FeatureFlags.enableTwolineAllapps() && isCurrentLanguageEnglish())
+ && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW);
+ }
+
+ protected boolean isCurrentLanguageEnglish() {
+ return mCurrentLocale.equals(Locale.US);
}
@UiThread
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index b6e8ec3..53297f2 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -356,7 +356,7 @@
final Resources res = context.getResources();
mMetrics = res.getDisplayMetrics();
- mIconSizeSteps = mIsResponsiveGrid ? new IconSizeSteps(res) : null;
+ mIconSizeSteps = new IconSizeSteps(res);
// Determine sizes.
widthPx = windowBounds.bounds.width();
@@ -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);
}
@@ -1486,6 +1484,17 @@
folderCellWidthPx = roundPxValueFromFloat(folderCellWidthPx * scale);
folderCellHeightPx = roundPxValueFromFloat(folderCellHeightPx * scale);
}
+ // Recalculating padding and cell height
+ folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
+
+ CellContentDimensions cellContentDimensions = new CellContentDimensions(
+ folderChildIconSizePx,
+ folderChildDrawablePaddingPx,
+ folderChildTextSizePx);
+ cellContentDimensions.resizeToFitCellHeight(folderCellHeightPx, mIconSizeSteps);
+ folderChildIconSizePx = cellContentDimensions.getIconSizePx();
+ folderChildDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx();
+ folderChildTextSizePx = cellContentDimensions.getIconTextSizePx();
folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale);
folderCellLayoutBorderSpacePx = new Point(
@@ -1493,10 +1502,7 @@
roundPxValueFromFloat(folderCellLayoutBorderSpacePx.y * scale)
);
folderFooterHeightPx = roundPxValueFromFloat(folderFooterHeightPx * scale);
-
folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx.x;
-
- folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
} else {
int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
* scale);
@@ -1780,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,
@@ -1819,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/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index dfbbcaa..5721ed3 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -298,11 +298,15 @@
* Reinitialize the current grid after a restore, where some grids might now be disabled.
*/
public void reinitializeAfterRestore(Context context) {
- FileLog.d(TAG, "Reinitializing grid after restore");
String currentGridName = getCurrentGridName(context);
String currentDbFile = dbFile;
String newGridName = initGrid(context, currentGridName);
String newDbFile = dbFile;
+ FileLog.d(TAG, "Reinitializing grid after restore."
+ + " currentGridName=" + currentGridName
+ + ", currentDbFile=" + currentDbFile
+ + ", newGridName=" + newGridName
+ + ", newDbFile=" + newDbFile);
if (!newDbFile.equals(currentDbFile)) {
FileLog.d(TAG, "Restored grid is disabled : " + currentGridName
+ ", migrating to: " + newGridName
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5adfd43..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;
}
@@ -2905,6 +2935,14 @@
// Overridden; move this into ActivityContext if necessary for Taskbar
}
+ /**
+ * Callback for when launcher state transition completes after user swipes to home.
+ * @param finalState The final state of the transition.
+ */
+ public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
+ // Overridden
+ }
+
@Override
public void returnToHomescreen() {
super.returnToHomescreen();
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index a05b0f5..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
@@ -364,6 +365,13 @@
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
+ val PRIVATE_SPACE_APPS =
+ nonRestorableItem(
+ "pref_private_space_apps",
+ 0,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
+ @JvmField
val THEMED_ICONS =
backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
@JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
@@ -410,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/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index e5a223a..7f1d216 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -407,7 +407,7 @@
// If exiting search, revert predictive back scale on all apps
mAllAppsTransitionController.animateAllAppsToNoScale();
}
- mSearchTransitionController.animateToSearchState(goingToSearch, durationMs,
+ mSearchTransitionController.animateToState(goingToSearch, durationMs,
/* onEndRunnable = */ () -> {
mIsSearching = goingToSearch;
updateSearchResultsVisibility();
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index b0f13ef..36a44cc 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -36,7 +36,11 @@
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.util.Consumer;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.DeviceProfile;
@@ -57,6 +61,7 @@
protected static final String TAG = "AllAppsRecyclerView";
private static final boolean DEBUG = false;
private static final boolean DEBUG_LATENCY = Utilities.isPropertyEnabled(SEARCH_LOGGING);
+ private Consumer<View> mChildAttachedConsumer;
protected final int mNumAppsPerRow;
private final AllAppsFastScrollHelper mFastScrollHelper;
@@ -282,6 +287,22 @@
}
}
+ /**
+ * This will be called just before a new child is attached to the window. Passing in null will
+ * remove the consumer.
+ */
+ protected void setChildAttachedConsumer(@Nullable Consumer<View> childAttachedConsumer) {
+ mChildAttachedConsumer = childAttachedConsumer;
+ }
+
+ @Override
+ public void onChildAttachedToWindow(@NonNull View child) {
+ if (mChildAttachedConsumer != null) {
+ mChildAttachedConsumer.accept(child);
+ }
+ super.onChildAttachedToWindow(child);
+ }
+
@Override
public int getScrollBarTop() {
return ActivityContext.lookupContext(getContext()).getAppsView().isSearchSupported()
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 328516e..1782791 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -15,6 +15,10 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_LEFT;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_RIGHT;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
+
import android.content.Context;
import androidx.annotation.Nullable;
@@ -318,6 +322,10 @@
case PrivateProfileManager.STATE_ENABLED:
// Add PS Apps only in Enabled State.
addAppsWithSections(mPrivateApps, position);
+ if (mActivityContext.getAppsView() != null) {
+ mActivityContext.getAppsView().getActiveRecyclerView()
+ .scrollToBottomWithMotion();
+ }
break;
}
}
@@ -325,8 +333,34 @@
private void addAppsWithSections(List<AppInfo> appList, int startPosition) {
String lastSectionName = null;
+ boolean hasPrivateApps = false;
+ if (mPrivateProviderManager != null) {
+ hasPrivateApps = appList.stream().
+ allMatch(mPrivateProviderManager.getItemInfoMatcher());
+ }
+ int privateAppCount = 0;
+ int numberOfColumns = mActivityContext.getDeviceProfile().numShownAllAppsColumns;
+ int numberOfAppRows = (int) Math.ceil((double) appList.size() / numberOfColumns);
for (AppInfo info : appList) {
- mAdapterItems.add(AdapterItem.asApp(info));
+ // Apply decorator to private apps.
+ if (hasPrivateApps) {
+ int roundRegion = ROUND_NOTHING;
+ if ((privateAppCount / numberOfColumns) == numberOfAppRows - 1) {
+ if ((privateAppCount % numberOfColumns) == 0) {
+ // App is the first column
+ roundRegion = ROUND_BOTTOM_LEFT;
+ } else if ((privateAppCount % numberOfColumns) == numberOfColumns-1) {
+ roundRegion = ROUND_BOTTOM_RIGHT;
+ }
+ }
+ mAdapterItems.add(AdapterItem.asAppWithDecorationInfo(info,
+ new SectionDecorationInfo(mActivityContext.getApplicationContext(),
+ roundRegion,
+ true /* decorateTogether */)));
+ privateAppCount += 1;
+ } else {
+ mAdapterItems.add(AdapterItem.asApp(info));
+ }
String sectionName = info.sectionName;
// Create a new section if the section names do not match
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 5e26ea5..5eeb259 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -15,6 +15,12 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_LEFT;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_RIGHT;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_TOP_LEFT;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_TOP_RIGHT;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
+
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
@@ -25,6 +31,7 @@
import android.widget.RelativeLayout;
import android.widget.TextView;
+import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BubbleTextView;
@@ -92,7 +99,8 @@
public int rowAppIndex;
// The associated ItemInfoWithIcon for the item
public AppInfo itemInfo = null;
-
+ // Private App Decorator
+ public SectionDecorationInfo decorationInfo = null;
public AdapterItem(int viewType) {
this.viewType = viewType;
}
@@ -106,6 +114,13 @@
return item;
}
+ public static AdapterItem asAppWithDecorationInfo(AppInfo appInfo,
+ SectionDecorationInfo decorationInfo) {
+ AdapterItem item = asApp(appInfo);
+ item.decorationInfo = decorationInfo;
+ return item;
+ }
+
protected boolean isCountedForAccessibility() {
return viewType == VIEW_TYPE_ICON;
}
@@ -125,9 +140,17 @@
return itemInfo == null && other.itemInfo == null;
}
- /** Sets the alpha of the decorator for this item. Returns true if successful. */
- public boolean setDecorationFillAlpha(int alpha) {
- return false;
+ @Nullable
+ public SectionDecorationInfo getDecorationInfo() {
+ return decorationInfo;
+ }
+
+ /** Sets the alpha of the decorator for this item. */
+ protected void setDecorationFillAlpha(int alpha) {
+ if (decorationInfo == null || decorationInfo.getDecorationHandler() == null) {
+ return;
+ }
+ decorationInfo.getDecorationHandler().setFillAlpha(alpha);
}
}
@@ -249,6 +272,15 @@
assert mPrivateSpaceHeaderViewController != null;
assert psHeaderLayout != null;
mPrivateSpaceHeaderViewController.addPrivateSpaceHeaderViewElements(psHeaderLayout);
+ AdapterItem adapterItem = mApps.getAdapterItems().get(position);
+ int roundRegions = ROUND_TOP_LEFT | ROUND_TOP_RIGHT;
+ if (mPrivateSpaceHeaderViewController.getPrivateProfileManager().getCurrentState()
+ == STATE_DISABLED) {
+ roundRegions |= (ROUND_BOTTOM_LEFT | ROUND_BOTTOM_RIGHT);
+ }
+ adapterItem.decorationInfo =
+ new SectionDecorationInfo(mActivityContext, roundRegions,
+ false /* decorateTogether */);
break;
case VIEW_TYPE_ALL_APPS_DIVIDER:
case VIEW_TYPE_WORK_DISABLED_CARD:
diff --git a/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java b/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java
index f4ed754..8712b84 100644
--- a/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java
@@ -16,97 +16,55 @@
package com.android.launcher3.allapps;
-import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ICON;
-import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
-
-import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.RectF;
import android.view.View;
-import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.launcher3.R;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.views.ActivityContext;
+import java.util.HashMap;
/**
* Decorator which changes the background color for Private Space Icon Rows in AllAppsContainer.
*/
public class PrivateAppsSectionDecorator extends RecyclerView.ItemDecoration {
- private final Path mTmpPath = new Path();
- private final RectF mTmpRect = new RectF();
- private final Context mContext;
+ private static final String PRIVATE_APP_SECTION = "private_apps";
private final AlphabeticalAppsList<?> mAppsList;
- private final UserCache mUserCache;
- private final Paint mPaint;
- private final int mCornerRadius;
- public PrivateAppsSectionDecorator(Context context, AlphabeticalAppsList<?> appsList) {
- mContext = context;
+ public PrivateAppsSectionDecorator(AlphabeticalAppsList<?> appsList) {
mAppsList = appsList;
- mUserCache = UserCache.getInstance(context);
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setColor(ContextCompat.getColor(context,
- R.color.material_color_surface_container_high));
- mCornerRadius = context.getResources().getDimensionPixelSize(
- R.dimen.ps_container_corner_radius);
}
/** Decorates Private Space Header and Icon Rows to give the shape of a container. */
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- mTmpPath.reset();
- mTmpRect.setEmpty();
- int numCol = ActivityContext.lookupContext(mContext).getDeviceProfile()
- .numShownAllAppsColumns;
+ HashMap<String, SectionDecorationHandler.UnionDecorationHandler> deferredDecorations =
+ new HashMap<>();
for (int i = 0; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
BaseAllAppsAdapter.AdapterItem adapterItem = mAppsList.getAdapterItems().get(position);
- // Rectangle that covers the bottom half of the PS Header View when Space is unlocked.
- if (adapterItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
- // We flatten the bottom corners of the rectangle, so that it merges with
- // the private space app row decorator.
- mTmpRect.set(
- view.getLeft(),
- view.getTop() + (float) (view.getBottom() - view.getTop()) / 2,
- view.getRight(),
- view.getBottom());
- mTmpPath.addRect(mTmpRect, Path.Direction.CW);
- c.drawPath(mTmpPath, mPaint);
- } else if (adapterItem.viewType == VIEW_TYPE_ICON
- && mUserCache.getUserInfo(adapterItem.itemInfo.user).isPrivate()
- // No decoration for any private space app icon other than those at first row.
- && adapterItem.rowAppIndex == 0) {
- c.drawPath(getPrivateAppRowPath(parent, view, position, numCol), mPaint);
+ SectionDecorationInfo info = adapterItem.decorationInfo;
+ if (info == null) {
+ continue;
+ }
+ SectionDecorationHandler decorationHandler = info.getDecorationHandler();
+ if (info.shouldDecorateItemsTogether()) {
+ SectionDecorationHandler.UnionDecorationHandler unionHandler =
+ deferredDecorations.getOrDefault(
+ PRIVATE_APP_SECTION,
+ new SectionDecorationHandler.UnionDecorationHandler(
+ decorationHandler, parent.getPaddingLeft(),
+ parent.getPaddingRight()));
+ unionHandler.addChild(decorationHandler, view, true /* applyBackground */);
+ deferredDecorations.put(PRIVATE_APP_SECTION, unionHandler);
+ } else {
+ decorationHandler.onFocusDraw(c, view);
}
}
- }
-
- /** Returns the path to be decorated for Private Space App Row */
- private Path getPrivateAppRowPath(RecyclerView parent, View iconView, int adapterPosition,
- int numCol) {
- // We always decorate the entire app row here.
- // As the iconView just represents the first icon of the row, we get the right margin of
- // our decorator using the parent view.
- mTmpRect.set(iconView.getLeft(),
- iconView.getTop(),
- parent.getRight() - parent.getPaddingRight(),
- iconView.getBottom());
- // Decorates last app row with rounded bottom corners.
- if (adapterPosition + numCol >= mAppsList.getAdapterItems().size()) {
- float[] mCornersBot = new float[]{0, 0, 0, 0, mCornerRadius, mCornerRadius,
- mCornerRadius, mCornerRadius};
- mTmpPath.addRoundRect(mTmpRect, mCornersBot, Path.Direction.CW);
- } else {
- // Decorate other rows as a plain rectangle
- mTmpPath.addRect(mTmpRect, Path.Direction.CW);
+ for (SectionDecorationHandler.UnionDecorationHandler decorationHandler
+ : deferredDecorations.values()) {
+ decorationHandler.onGroupDecorate(c);
}
- return mTmpPath;
}
}
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 334d5c1..693681b 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -30,6 +30,7 @@
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.Flags;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.Preconditions;
@@ -47,6 +48,7 @@
private static final String SAFETY_CENTER_INTENT = Intent.ACTION_SAFETY_CENTER;
private static final String PS_SETTINGS_FRAGMENT_KEY = ":settings:fragment_args_key";
private static final String PS_SETTINGS_FRAGMENT_VALUE = "AndroidPrivateSpace_personal";
+ private static final int ANIMATION_DURATION = 2000;
private final ActivityAllAppsContainerView<?> mAllApps;
private final Predicate<UserHandle> mPrivateProfileMatcher;
private PrivateAppsSectionDecorator mPrivateAppsSectionDecorator;
@@ -125,7 +127,6 @@
// Create a new decorator instance if not already available.
if (mPrivateAppsSectionDecorator == null) {
mPrivateAppsSectionDecorator = new PrivateAppsSectionDecorator(
- mAllApps.mActivityContext,
mainAdapterHolder.mAppsList);
}
for (int i = 0; i < mainAdapterHolder.mRecyclerView.getItemDecorationCount(); i++) {
@@ -137,6 +138,13 @@
}
// Add Private Space Decorator to the Recycler view.
mainAdapterHolder.mRecyclerView.addItemDecoration(mPrivateAppsSectionDecorator);
+ if (Flags.privateSpaceAnimation() && mAllApps.getActiveRecyclerView()
+ == mainAdapterHolder.mRecyclerView) {
+ RecyclerViewAnimationController recyclerViewAnimationController =
+ new RecyclerViewAnimationController(mAllApps);
+ recyclerViewAnimationController.animateToState(true /* expand */,
+ ANIMATION_DURATION, () -> {});
+ }
} else {
// Remove Private Space Decorator from the Recycler view.
if (mPrivateAppsSectionDecorator != null) {
diff --git a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
index e0ca947..568ce32 100644
--- a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
+++ b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
@@ -105,4 +105,8 @@
transitionImage.setVisibility(View.GONE);
}
}
+
+ PrivateProfileManager getPrivateProfileManager() {
+ return mPrivateProfileManager;
+ }
}
diff --git a/src/com/android/launcher3/allapps/RecyclerViewAnimationController.java b/src/com/android/launcher3/allapps/RecyclerViewAnimationController.java
new file mode 100644
index 0000000..6209393
--- /dev/null
+++ b/src/com/android/launcher3/allapps/RecyclerViewAnimationController.java
@@ -0,0 +1,309 @@
+/*
+ * 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.allapps;
+
+import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
+
+import static com.android.app.animation.Interpolators.DECELERATE_1_7;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.clampToProgress;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
+
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.model.data.ItemInfo;
+
+import java.util.List;
+
+public class RecyclerViewAnimationController {
+
+ private static final String LOG_TAG = "AnimationCtrl";
+
+ /**
+ * These values represent points on the [0, 1] animation progress spectrum. They are used to
+ * animate items in the {@link SearchRecyclerView} and private space container in
+ * {@link AllAppsRecyclerView}.
+ */
+ protected static final float TOP_CONTENT_FADE_PROGRESS_START = 0.133f;
+ protected static final float CONTENT_FADE_PROGRESS_DURATION = 0.083f;
+ protected static final float TOP_BACKGROUND_FADE_PROGRESS_START = 0.633f;
+ protected static final float BACKGROUND_FADE_PROGRESS_DURATION = 0.15f;
+ // Progress before next item starts fading.
+ protected static final float CONTENT_STAGGER = 0.01f;
+
+ protected static final FloatProperty<RecyclerViewAnimationController> PROGRESS =
+ new FloatProperty<RecyclerViewAnimationController>("expansionProgress") {
+ @Override
+ public Float get(RecyclerViewAnimationController controller) {
+ return controller.getAnimationProgress();
+ }
+
+ @Override
+ public void setValue(RecyclerViewAnimationController controller, float progress) {
+ controller.setAnimationProgress(progress);
+ }
+ };
+
+ protected final ActivityAllAppsContainerView<?> mAllAppsContainerView;
+ protected ObjectAnimator mAnimator = null;
+ private float mAnimatorProgress = 1f;
+
+ public RecyclerViewAnimationController(ActivityAllAppsContainerView<?> allAppsContainerView) {
+ mAllAppsContainerView = allAppsContainerView;
+ }
+
+ /**
+ * Updates the children views of the current recyclerView based on the current animation
+ * progress.
+ *
+ * @return the total height of animating views (may exclude at most one row of app icons
+ * depending on which recyclerView is being acted upon).
+ */
+ protected int onProgressUpdated(float expansionProgress) {
+ int numItemsAnimated = 0;
+ int totalHeight = 0;
+ int appRowHeight = 0;
+ boolean appRowComplete = false;
+ Integer top = null;
+ AllAppsRecyclerView allAppsRecyclerView = getRecyclerView();
+
+ for (int i = 0; i < allAppsRecyclerView.getChildCount(); i++) {
+ View currentView = allAppsRecyclerView.getChildAt(i);
+ if (currentView == null) {
+ continue;
+ }
+ if (top == null) {
+ top = currentView.getTop();
+ }
+ int adapterPosition = allAppsRecyclerView.getChildAdapterPosition(currentView);
+ List<BaseAllAppsAdapter.AdapterItem> allAppsAdapters = allAppsRecyclerView.getApps()
+ .getAdapterItems();
+ if (adapterPosition < 0 || adapterPosition >= allAppsAdapters.size()) {
+ continue;
+ }
+ BaseAllAppsAdapter.AdapterItem adapterItemAtPosition =
+ allAppsAdapters.get(adapterPosition);
+ int spanIndex = getSpanIndex(allAppsRecyclerView, adapterPosition);
+ appRowComplete |= appRowHeight > 0 && spanIndex == 0;
+
+ float backgroundAlpha = 1f;
+ boolean hasDecorationInfo = adapterItemAtPosition.getDecorationInfo() != null;
+ boolean shouldAnimate = shouldAnimate(currentView, hasDecorationInfo, appRowComplete);
+
+ if (shouldAnimate) {
+ if (spanIndex > 0) {
+ // Animate this item with the previous item on the same row.
+ numItemsAnimated--;
+ }
+ // Adjust background (or decorator) alpha based on start progress and stagger.
+ backgroundAlpha = getAdjustedBackgroundAlpha(numItemsAnimated);
+ }
+
+ Drawable background = currentView.getBackground();
+ if (background != null && currentView instanceof ViewGroup currentViewGroup) {
+ currentView.setAlpha(1f);
+ // Apply content alpha to each child, since the view needs to be fully opaque for
+ // the background to show properly.
+ for (int j = 0; j < currentViewGroup.getChildCount(); j++) {
+ setViewAdjustedContentAlpha(currentViewGroup.getChildAt(j), numItemsAnimated,
+ shouldAnimate);
+ }
+
+ // Apply background alpha to the background drawable directly.
+ background.setAlpha((int) (255 * backgroundAlpha));
+ } else {
+ // Adjust content alpha based on start progress and stagger.
+ setViewAdjustedContentAlpha(currentView, numItemsAnimated, shouldAnimate);
+
+ // Apply background alpha to decorator if possible.
+ setAdjustedAdapterItemDecorationBackgroundAlpha(
+ allAppsRecyclerView.getApps().getAdapterItems().get(adapterPosition),
+ numItemsAnimated);
+
+ // Apply background alpha to view's background (e.g. for Search Edu card).
+ if (background != null) {
+ background.setAlpha((int) (255 * backgroundAlpha));
+ }
+ }
+
+ float scaleY = 1;
+ if (shouldAnimate) {
+ scaleY = 1 - getAnimationProgress();
+ // Update number of search results that has been animated.
+ numItemsAnimated++;
+ }
+ int scaledHeight = (int) (currentView.getHeight() * scaleY);
+ currentView.setScaleY(scaleY);
+
+ // For rows with multiple elements, only count the height once and translate elements to
+ // the same y position.
+ int y = top + totalHeight;
+ if (spanIndex > 0) {
+ // Continuation of an existing row; move this item into the row.
+ y -= scaledHeight;
+ } else {
+ // Start of a new row contributes to total height.
+ totalHeight += scaledHeight;
+ if (!shouldAnimate) {
+ appRowHeight = scaledHeight;
+ }
+ }
+ currentView.setY(y);
+ }
+ return totalHeight - appRowHeight;
+ }
+
+ protected void animateToState(boolean expand, long duration, Runnable onEndRunnable) {
+ float targetProgress = expand ? 0 : 1;
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ mAnimator = ObjectAnimator.ofFloat(this, PROGRESS, targetProgress);
+
+ TimeInterpolator timeInterpolator = getInterpolator();
+ if (timeInterpolator == INSTANT) {
+ duration = 0;
+ }
+
+ mAnimator.addListener(forEndCallback(() -> mAnimator = null));
+ mAnimator.setDuration(duration).setInterpolator(timeInterpolator);
+ mAnimator.addListener(forSuccessCallback(onEndRunnable));
+ mAnimator.start();
+ getRecyclerView().setChildAttachedConsumer(this::onChildAttached);
+ }
+
+ /** Called just before a child is attached to the RecyclerView. */
+ private void onChildAttached(View child) {
+ // Avoid allocating hardware layers for alpha changes.
+ child.forceHasOverlappingRendering(false);
+ child.setPivotY(0);
+ if (getAnimationProgress() > 0 && getAnimationProgress() < 1) {
+ // Before the child is rendered, apply the animation including it to avoid flicker.
+ onProgressUpdated(getAnimationProgress());
+ } else {
+ // Apply default states without processing the full layout.
+ child.setAlpha(1);
+ child.setScaleY(1);
+ child.setTranslationY(0);
+ int adapterPosition = getRecyclerView().getChildAdapterPosition(child);
+ List<BaseAllAppsAdapter.AdapterItem> allAppsAdapters =
+ getRecyclerView().getApps().getAdapterItems();
+ if (adapterPosition >= 0 && adapterPosition < allAppsAdapters.size()) {
+ allAppsAdapters.get(adapterPosition).setDecorationFillAlpha(255);
+ }
+ if (child instanceof ViewGroup childGroup) {
+ for (int i = 0; i < childGroup.getChildCount(); i++) {
+ childGroup.getChildAt(i).setAlpha(1f);
+ }
+ }
+ if (child.getBackground() != null) {
+ child.getBackground().setAlpha(255);
+ }
+ }
+ }
+
+ /** @return the column that the view at this position is found (0 assumed if indeterminate). */
+ protected int getSpanIndex(AllAppsRecyclerView appsRecyclerView, int adapterPosition) {
+ if (adapterPosition == NO_POSITION) {
+ Log.w(LOG_TAG, "Can't determine span index - child not found in adapter");
+ return 0;
+ }
+ if (!(appsRecyclerView.getAdapter() instanceof AllAppsGridAdapter<?>)) {
+ Log.e(LOG_TAG, "Search RV doesn't have an AllAppsGridAdapter?");
+ // This case shouldn't happen, but for debug devices we will continue to create a more
+ // visible crash.
+ if (!Utilities.IS_DEBUG_DEVICE) {
+ return 0;
+ }
+ }
+ AllAppsGridAdapter<?> adapter = (AllAppsGridAdapter<?>) appsRecyclerView.getAdapter();
+ return adapter.getSpanIndex(adapterPosition);
+ }
+
+ protected TimeInterpolator getInterpolator() {
+ return DECELERATE_1_7;
+ }
+
+ protected AllAppsRecyclerView getRecyclerView() {
+ return mAllAppsContainerView.mAH.get(ActivityAllAppsContainerView.AdapterHolder.MAIN)
+ .mRecyclerView;
+ }
+
+ /** Returns true if a transition animation is currently in progress. */
+ protected boolean isRunning() {
+ return mAnimator != null;
+ }
+
+ /** Should only animate if the view is an app icon and if it has a decoration info. */
+ protected boolean shouldAnimate(View view, boolean hasDecorationInfo,
+ boolean firstAppRowComplete) {
+ return isAppIcon(view) && hasDecorationInfo;
+ }
+
+ private float getAdjustedContentAlpha(int itemsAnimated) {
+ float startContentFadeProgress = Math.max(0,
+ TOP_CONTENT_FADE_PROGRESS_START - CONTENT_STAGGER * itemsAnimated);
+ float endContentFadeProgress = Math.min(1,
+ startContentFadeProgress + CONTENT_FADE_PROGRESS_DURATION);
+ return 1 - clampToProgress(mAnimatorProgress,
+ startContentFadeProgress, endContentFadeProgress);
+ }
+
+ private float getAdjustedBackgroundAlpha(int itemsAnimated) {
+ float startBackgroundFadeProgress = Math.max(0,
+ TOP_BACKGROUND_FADE_PROGRESS_START - CONTENT_STAGGER * itemsAnimated);
+ float endBackgroundFadeProgress = Math.min(1,
+ startBackgroundFadeProgress + BACKGROUND_FADE_PROGRESS_DURATION);
+ return 1 - clampToProgress(mAnimatorProgress,
+ startBackgroundFadeProgress, endBackgroundFadeProgress);
+ }
+
+ private void setViewAdjustedContentAlpha(View view, int numberOfItemsAnimated,
+ boolean shouldAnimate) {
+ view.setAlpha(shouldAnimate ? getAdjustedContentAlpha(numberOfItemsAnimated) : 1f);
+ }
+
+ private void setAdjustedAdapterItemDecorationBackgroundAlpha(
+ BaseAllAppsAdapter.AdapterItem adapterItem, int numberOfItemsAnimated) {
+ adapterItem.setDecorationFillAlpha((int)
+ (255 * getAdjustedBackgroundAlpha(numberOfItemsAnimated)));
+ }
+
+ private float getAnimationProgress() {
+ return mAnimatorProgress;
+ }
+
+ private void setAnimationProgress(float expansionProgress) {
+ mAnimatorProgress = expansionProgress;
+ onProgressUpdated(expansionProgress);
+ }
+
+ protected boolean isAppIcon(View item) {
+ return item instanceof BubbleTextView && item.getTag() instanceof ItemInfo
+ && ((ItemInfo) item.getTag()).itemType == ITEM_TYPE_APPLICATION;
+ }
+}
diff --git a/src/com/android/launcher3/allapps/SearchRecyclerView.java b/src/com/android/launcher3/allapps/SearchRecyclerView.java
index 9d1dfc0..68f9f11 100644
--- a/src/com/android/launcher3/allapps/SearchRecyclerView.java
+++ b/src/com/android/launcher3/allapps/SearchRecyclerView.java
@@ -27,8 +27,6 @@
/** A RecyclerView for AllApps Search results. */
public class SearchRecyclerView extends AllAppsRecyclerView {
- private Consumer<View> mChildAttachedConsumer;
-
public SearchRecyclerView(Context context) {
this(context, null);
}
@@ -46,11 +44,6 @@
super(context, attrs, defStyleAttr, defStyleRes);
}
- /** This will be called just before a new child is attached to the window. */
- public void setChildAttachedConsumer(Consumer<View> childAttachedConsumer) {
- mChildAttachedConsumer = childAttachedConsumer;
- }
-
@Override
protected void updatePoolSize() {
RecycledViewPool pool = getRecycledViewPool();
@@ -67,12 +60,4 @@
public RecyclerViewFastScroller getScrollbar() {
return null;
}
-
- @Override
- public void onChildAttachedToWindow(@NonNull View child) {
- if (mChildAttachedConsumer != null) {
- mChildAttachedConsumer.accept(child);
- }
- super.onChildAttachedToWindow(child);
- }
}
diff --git a/src/com/android/launcher3/allapps/SearchTransitionController.java b/src/com/android/launcher3/allapps/SearchTransitionController.java
index eb1bc0a..d5c3b57 100644
--- a/src/com/android/launcher3/allapps/SearchTransitionController.java
+++ b/src/com/android/launcher3/allapps/SearchTransitionController.java
@@ -18,34 +18,21 @@
import static android.view.View.VISIBLE;
-import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
-
import static com.android.app.animation.Interpolators.DECELERATE_1_7;
import static com.android.app.animation.Interpolators.INSTANT;
import static com.android.app.animation.Interpolators.clampToProgress;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.graphics.drawable.Drawable;
-import android.util.FloatProperty;
-import android.util.Log;
import android.view.View;
-import android.view.ViewGroup;
import android.view.animation.Interpolator;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.data.ItemInfo;
/** Coordinates the transition between Search and A-Z in All Apps. */
-public class SearchTransitionController {
-
- private static final String LOG_TAG = "SearchTransitionCtrl";
+public class SearchTransitionController extends RecyclerViewAnimationController {
// Interpolator when the user taps the QSB while already in All Apps.
private static final Interpolator INTERPOLATOR_WITHIN_ALL_APPS = DECELERATE_1_7;
@@ -53,42 +40,10 @@
// happening simultaneously.
private static final Interpolator INTERPOLATOR_TRANSITIONING_TO_ALL_APPS = INSTANT;
- /**
- * These values represent points on the [0, 1] animation progress spectrum. They are used to
- * animate items in the {@link SearchRecyclerView}.
- */
- private static final float TOP_CONTENT_FADE_PROGRESS_START = 0.133f;
- private static final float CONTENT_FADE_PROGRESS_DURATION = 0.083f;
- private static final float TOP_BACKGROUND_FADE_PROGRESS_START = 0.633f;
- private static final float BACKGROUND_FADE_PROGRESS_DURATION = 0.15f;
- private static final float CONTENT_STAGGER = 0.01f; // Progress before next item starts fading.
-
- private static final FloatProperty<SearchTransitionController> SEARCH_TO_AZ_PROGRESS =
- new FloatProperty<SearchTransitionController>("searchToAzProgress") {
- @Override
- public Float get(SearchTransitionController controller) {
- return controller.getSearchToAzProgress();
- }
-
- @Override
- public void setValue(SearchTransitionController controller, float progress) {
- controller.setSearchToAzProgress(progress);
- }
- };
-
- private final ActivityAllAppsContainerView<?> mAllAppsContainerView;
-
- private ObjectAnimator mSearchToAzAnimator = null;
- private float mSearchToAzProgress = 1f;
private boolean mSkipNextAnimationWithinAllApps;
public SearchTransitionController(ActivityAllAppsContainerView<?> allAppsContainerView) {
- mAllAppsContainerView = allAppsContainerView;
- }
-
- /** Returns true if a transition animation is currently in progress. */
- public boolean isRunning() {
- return mSearchToAzAnimator != null;
+ super(allAppsContainerView);
}
/**
@@ -101,51 +56,31 @@
* @param onEndRunnable will be called when the animation finishes, unless another animation is
* scheduled in the meantime
*/
- public void animateToSearchState(boolean goingToSearch, long duration, Runnable onEndRunnable) {
- float targetProgress = goingToSearch ? 0 : 1;
-
- if (mSearchToAzAnimator != null) {
- mSearchToAzAnimator.cancel();
- }
-
- mSearchToAzAnimator = ObjectAnimator.ofFloat(this, SEARCH_TO_AZ_PROGRESS, targetProgress);
- boolean inAllApps = mAllAppsContainerView.isInAllApps();
- TimeInterpolator timeInterpolator =
- inAllApps ? INTERPOLATOR_WITHIN_ALL_APPS : INTERPOLATOR_TRANSITIONING_TO_ALL_APPS;
- if (mSkipNextAnimationWithinAllApps) {
- timeInterpolator = INSTANT;
- mSkipNextAnimationWithinAllApps = false;
- }
- if (timeInterpolator == INSTANT) {
- duration = 0; // Don't want to animate when coming from QSB.
- }
- mSearchToAzAnimator.setDuration(duration).setInterpolator(timeInterpolator);
- mSearchToAzAnimator.addListener(forEndCallback(() -> mSearchToAzAnimator = null));
+ @Override
+ protected void animateToState(boolean goingToSearch, long duration, Runnable onEndRunnable) {
+ super.animateToState(goingToSearch, duration, onEndRunnable);
if (!goingToSearch) {
- mSearchToAzAnimator.addListener(forSuccessCallback(() -> {
+ mAnimator.addListener(forSuccessCallback(() -> {
mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(false);
mAllAppsContainerView.getFloatingHeaderView().reset(false /* animate */);
mAllAppsContainerView.getAppsRecyclerViewContainer().setTranslationY(0);
}));
}
- mSearchToAzAnimator.addListener(forSuccessCallback(onEndRunnable));
-
mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(true);
mAllAppsContainerView.getFloatingHeaderView().setVisibility(VISIBLE);
mAllAppsContainerView.getFloatingHeaderView().maybeSetTabVisibility(VISIBLE);
mAllAppsContainerView.getAppsRecyclerViewContainer().setVisibility(VISIBLE);
- getSearchRecyclerView().setVisibility(VISIBLE);
- getSearchRecyclerView().setChildAttachedConsumer(this::onSearchChildAttached);
- mSearchToAzAnimator.start();
+ getRecyclerView().setVisibility(VISIBLE);
}
- private SearchRecyclerView getSearchRecyclerView() {
+ @Override
+ protected SearchRecyclerView getRecyclerView() {
return mAllAppsContainerView.getSearchRecyclerView();
}
- private void setSearchToAzProgress(float searchToAzProgress) {
- mSearchToAzProgress = searchToAzProgress;
- int searchHeight = updateSearchRecyclerViewProgress();
+ @Override
+ protected int onProgressUpdated(float searchToAzProgress) {
+ int searchHeight = super.onProgressUpdated(searchToAzProgress);
FloatingHeaderView headerView = mAllAppsContainerView.getFloatingHeaderView();
@@ -171,179 +106,27 @@
appsContainer.setTranslationY(appsTranslationY);
// Fade apps out with tabs (in 20% of the total animation).
appsContainer.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f));
+ return searchHeight;
}
/**
- * Updates the children views of SearchRecyclerView based on the current animation progress.
- *
- * @return the total height of animating views (excluding at most one row of app icons).
+ * Should only animate if the view is not an app icon or if the app row is complete.
*/
- private int updateSearchRecyclerViewProgress() {
- int numSearchResultsAnimated = 0;
- int totalHeight = 0;
- int appRowHeight = 0;
- boolean appRowComplete = false;
- Integer top = null;
- SearchRecyclerView searchRecyclerView = getSearchRecyclerView();
-
- for (int i = 0; i < searchRecyclerView.getChildCount(); i++) {
- View searchResultView = searchRecyclerView.getChildAt(i);
- if (searchResultView == null) {
- continue;
- }
-
- if (top == null) {
- top = searchResultView.getTop();
- }
-
- int adapterPosition = searchRecyclerView.getChildAdapterPosition(searchResultView);
- int spanIndex = getSpanIndex(searchRecyclerView, adapterPosition);
- appRowComplete |= appRowHeight > 0 && spanIndex == 0;
- // We don't animate the first (currently only) app row we see, as that is assumed to be
- // predicted/prefix-matched apps.
- boolean shouldAnimate = !isAppIcon(searchResultView) || appRowComplete;
-
- float contentAlpha = 1f;
- float backgroundAlpha = 1f;
- if (shouldAnimate) {
- if (spanIndex > 0) {
- // Animate this item with the previous item on the same row.
- numSearchResultsAnimated--;
- }
-
- // Adjust content alpha based on start progress and stagger.
- float startContentFadeProgress = Math.max(0,
- TOP_CONTENT_FADE_PROGRESS_START
- - CONTENT_STAGGER * numSearchResultsAnimated);
- float endContentFadeProgress = Math.min(1,
- startContentFadeProgress + CONTENT_FADE_PROGRESS_DURATION);
- contentAlpha = 1 - clampToProgress(mSearchToAzProgress,
- startContentFadeProgress, endContentFadeProgress);
-
- // Adjust background (or decorator) alpha based on start progress and stagger.
- float startBackgroundFadeProgress = Math.max(0,
- TOP_BACKGROUND_FADE_PROGRESS_START
- - CONTENT_STAGGER * numSearchResultsAnimated);
- float endBackgroundFadeProgress = Math.min(1,
- startBackgroundFadeProgress + BACKGROUND_FADE_PROGRESS_DURATION);
- backgroundAlpha = 1 - clampToProgress(mSearchToAzProgress,
- startBackgroundFadeProgress, endBackgroundFadeProgress);
-
- numSearchResultsAnimated++;
- }
-
- Drawable background = searchResultView.getBackground();
- if (background != null
- && searchResultView instanceof ViewGroup
- && FeatureFlags.ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES.get()) {
- searchResultView.setAlpha(1f);
-
- // Apply content alpha to each child, since the view needs to be fully opaque for
- // the background to show properly.
- ViewGroup searchResultViewGroup = (ViewGroup) searchResultView;
- for (int j = 0; j < searchResultViewGroup.getChildCount(); j++) {
- searchResultViewGroup.getChildAt(j).setAlpha(contentAlpha);
- }
-
- // Apply background alpha to the background drawable directly.
- background.setAlpha((int) (255 * backgroundAlpha));
- } else {
- searchResultView.setAlpha(contentAlpha);
-
- // Apply background alpha to decorator if possible.
- if (adapterPosition != NO_POSITION) {
- searchRecyclerView.getApps().getAdapterItems().get(adapterPosition)
- .setDecorationFillAlpha((int) (255 * backgroundAlpha));
- }
-
- // Apply background alpha to view's background (e.g. for Search Edu card).
- if (background != null) {
- background.setAlpha((int) (255 * backgroundAlpha));
- }
- }
-
- float scaleY = 1;
- if (shouldAnimate) {
- scaleY = 1 - mSearchToAzProgress;
- }
- int scaledHeight = (int) (searchResultView.getHeight() * scaleY);
- searchResultView.setScaleY(scaleY);
-
- // For rows with multiple elements, only count the height once and translate elements to
- // the same y position.
- int y = top + totalHeight;
- if (spanIndex > 0) {
- // Continuation of an existing row; move this item into the row.
- y -= scaledHeight;
- } else {
- // Start of a new row contributes to total height.
- totalHeight += scaledHeight;
- if (!shouldAnimate) {
- appRowHeight = scaledHeight;
- }
- }
- searchResultView.setY(y);
- }
-
- return totalHeight - appRowHeight;
+ @Override
+ protected boolean shouldAnimate(View view, boolean hasDecorationInfo, boolean appRowComplete) {
+ return !isAppIcon(view) || appRowComplete;
}
- /** @return the column that the view at this position is found (0 assumed if indeterminate). */
- private int getSpanIndex(SearchRecyclerView searchRecyclerView, int adapterPosition) {
- if (adapterPosition == NO_POSITION) {
- Log.w(LOG_TAG, "Can't determine span index - child not found in adapter");
- return 0;
+ @Override
+ protected TimeInterpolator getInterpolator() {
+ TimeInterpolator timeInterpolator =
+ mAllAppsContainerView.isInAllApps()
+ ? INTERPOLATOR_WITHIN_ALL_APPS : INTERPOLATOR_TRANSITIONING_TO_ALL_APPS;
+ if (mSkipNextAnimationWithinAllApps) {
+ timeInterpolator = INSTANT;
+ mSkipNextAnimationWithinAllApps = false;
}
- if (!(searchRecyclerView.getAdapter() instanceof AllAppsGridAdapter<?>)) {
- Log.e(LOG_TAG, "Search RV doesn't have an AllAppsGridAdapter?");
- // This case shouldn't happen, but for debug devices we will continue to create a more
- // visible crash.
- if (!Utilities.IS_DEBUG_DEVICE) {
- return 0;
- }
- }
- AllAppsGridAdapter<?> adapter = (AllAppsGridAdapter<?>) searchRecyclerView.getAdapter();
- return adapter.getSpanIndex(adapterPosition);
- }
-
- private boolean isAppIcon(View item) {
- return item instanceof BubbleTextView && item.getTag() instanceof ItemInfo
- && ((ItemInfo) item.getTag()).itemType == ITEM_TYPE_APPLICATION;
- }
-
- /** Called just before a child is attached to the SearchRecyclerView. */
- private void onSearchChildAttached(View child) {
- // Avoid allocating hardware layers for alpha changes.
- child.forceHasOverlappingRendering(false);
- child.setPivotY(0);
- if (mSearchToAzProgress > 0) {
- // Before the child is rendered, apply the animation including it to avoid flicker.
- updateSearchRecyclerViewProgress();
- } else {
- // Apply default states without processing the full layout.
- child.setAlpha(1);
- child.setScaleY(1);
- child.setTranslationY(0);
- int adapterPosition = getSearchRecyclerView().getChildAdapterPosition(child);
- if (adapterPosition != NO_POSITION) {
- getSearchRecyclerView().getApps().getAdapterItems().get(adapterPosition)
- .setDecorationFillAlpha(255);
- }
- if (child instanceof ViewGroup
- && FeatureFlags.ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES.get()) {
- ViewGroup childGroup = (ViewGroup) child;
- for (int i = 0; i < childGroup.getChildCount(); i++) {
- childGroup.getChildAt(i).setAlpha(1f);
- }
- }
- if (child.getBackground() != null) {
- child.getBackground().setAlpha(255);
- }
- }
- }
-
- private float getSearchToAzProgress() {
- return mSearchToAzProgress;
+ return timeInterpolator;
}
/**
diff --git a/src/com/android/launcher3/allapps/SectionDecorationHandler.java b/src/com/android/launcher3/allapps/SectionDecorationHandler.java
new file mode 100644
index 0000000..f79b82c
--- /dev/null
+++ b/src/com/android/launcher3/allapps/SectionDecorationHandler.java
@@ -0,0 +1,206 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.InsetDrawable;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+public class SectionDecorationHandler {
+
+ protected final Path mTmpPath = new Path();
+ protected final RectF mTmpRect = new RectF();
+
+ protected final int mCornerGroupRadius;
+ protected final int mCornerResultRadius;
+ protected final RectF mBounds = new RectF();
+ protected final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ protected final int mFocusAlpha = 255; // main focused item alpha
+ protected int mFillColor; // grouping color
+ protected int mFocusColor; // main focused item color
+ protected float mFillSpacing;
+ protected int mInlineRadius;
+ protected Context mContext;
+ protected float[] mCorners;
+ protected int mFillAlpha;
+ protected boolean mIsTopLeftRound;
+ protected boolean mIsTopRightRound;
+ protected boolean mIsBottomLeftRound;
+ protected boolean mIsBottomRightRound;
+ protected boolean mIsBottomRound;
+ protected boolean mIsTopRound;
+
+ public SectionDecorationHandler(Context context, int fillAlpha, boolean isTopLeftRound,
+ boolean isTopRightRound, boolean isBottomLeftRound,
+ boolean isBottomRightRound) {
+
+ mContext = context;
+ mFillAlpha = fillAlpha;
+ mFocusColor = ContextCompat.getColor(context,
+ R.color.material_color_surface_bright); // UX recommended
+ mFillColor = ContextCompat.getColor(context,
+ R.color.material_color_surface_container_high); // UX recommended
+
+ mIsTopLeftRound = isTopLeftRound;
+ mIsTopRightRound = isTopRightRound;
+ mIsBottomLeftRound = isBottomLeftRound;
+ mIsBottomRightRound = isBottomRightRound;
+ mIsBottomRound = mIsBottomLeftRound && mIsBottomRightRound;
+ mIsTopRound = mIsTopLeftRound && mIsTopRightRound;
+
+ mCornerGroupRadius = context.getResources().getDimensionPixelSize(
+ R.dimen.all_apps_recycler_view_decorator_group_radius);
+ mCornerResultRadius = context.getResources().getDimensionPixelSize(
+ R.dimen.all_apps_recycler_view_decorator_result_radius);
+
+ mInlineRadius = 0;
+ mFillSpacing = 0;
+ initCorners();
+ }
+
+ protected void initCorners() {
+ mCorners = new float[]{
+ mIsTopLeftRound ? mCornerGroupRadius : 0,
+ mIsTopLeftRound ? mCornerGroupRadius : 0, // Top left radius in px
+ mIsTopRightRound ? mCornerGroupRadius : 0,
+ mIsTopRightRound ? mCornerGroupRadius : 0, // Top right radius in px
+ mIsBottomRightRound ? mCornerGroupRadius : 0,
+ mIsBottomRightRound ? mCornerGroupRadius : 0, // Bottom right
+ mIsBottomLeftRound ? mCornerGroupRadius : 0,
+ mIsBottomLeftRound ? mCornerGroupRadius : 0 // Bottom left
+ };
+ }
+
+ protected void setFillAlpha(int fillAlpha) {
+ mFillAlpha = fillAlpha;
+ mPaint.setAlpha(mFillAlpha);
+ }
+
+ protected void onFocusDraw(Canvas canvas, @Nullable View view) {
+ if (view == null) {
+ return;
+ }
+ mPaint.setColor(mFillColor);
+ mPaint.setAlpha(mFillAlpha);
+ int scaledHeight = (int) (view.getHeight() * view.getScaleY());
+ mBounds.set(view.getLeft(), view.getY(), view.getRight(), view.getY() + scaledHeight);
+ onDraw(canvas);
+ }
+
+ protected void onDraw(Canvas canvas) {
+ mTmpPath.reset();
+ mTmpRect.set(mBounds.left + mFillSpacing,
+ mBounds.top + mFillSpacing,
+ mBounds.right - mFillSpacing,
+ mBounds.bottom - mFillSpacing);
+ mTmpPath.addRoundRect(mTmpRect, mCorners, Path.Direction.CW);
+ canvas.drawPath(mTmpPath, mPaint);
+ }
+
+ /** Sets the right background drawable to the view based on the give decoration info. */
+ public void applyBackground(View view, Context context,
+ @Nullable SectionDecorationInfo decorationInfo, boolean isHighlighted) {
+ int inset = context.getResources().getDimensionPixelSize(
+ R.dimen.all_apps_recycler_view_decorator_padding);
+ float radiusBottom = (decorationInfo == null || decorationInfo.isBottomRound()) ?
+ mCornerGroupRadius : mCornerResultRadius;
+ float radiusTop =
+ (decorationInfo == null || decorationInfo.isTopRound()) ?
+ mCornerGroupRadius : mCornerResultRadius;
+ int color = isHighlighted ? mFocusColor : mFillColor;
+
+ GradientDrawable shape = new GradientDrawable();
+ shape.setShape(GradientDrawable.RECTANGLE);
+ shape.setCornerRadii(new float[] {
+ radiusTop, radiusTop, // top-left
+ radiusTop, radiusTop, // top-right
+ radiusBottom, radiusBottom, // bottom-right
+ radiusBottom, radiusBottom // bottom-left
+ });
+ shape.setColor(color);
+
+ // Setting the background resets the padding, so we cache it and reset it afterwards.
+ int paddingLeft = view.getPaddingLeft();
+ int paddingTop = view.getPaddingTop();
+ int paddingRight = view.getPaddingRight();
+ int paddingBottom = view.getPaddingBottom();
+
+ view.setBackground(new InsetDrawable(shape, inset));
+
+ view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
+ }
+
+ /**
+ * Section decorator that combines views and draws a single block decoration
+ */
+ public static class UnionDecorationHandler extends SectionDecorationHandler {
+
+ private final int mPaddingLeft;
+ private final int mPaddingRight;
+
+ public UnionDecorationHandler(
+ SectionDecorationHandler decorationHandler,
+ int paddingLeft, int paddingRight) {
+ super(decorationHandler.mContext, decorationHandler.mFillAlpha,
+ decorationHandler.mIsTopLeftRound, decorationHandler.mIsTopRightRound,
+ decorationHandler.mIsBottomLeftRound, decorationHandler.mIsBottomRightRound);
+ mPaddingLeft = paddingLeft;
+ mPaddingRight = paddingRight;
+ }
+
+ /**
+ * Expands decoration bounds to include child {@link PrivateAppsSectionDecorator}
+ */
+ public void addChild(SectionDecorationHandler child, View view, boolean applyBackground) {
+ int scaledHeight = (int) (view.getHeight() * view.getScaleY());
+ mBounds.union(view.getLeft(), view.getY(),
+ view.getRight(), view.getY() + scaledHeight);
+ if (applyBackground) {
+ applyBackground(view, mContext, null, false);
+ }
+ mIsBottomRound |= child.mIsBottomRound;
+ mIsBottomLeftRound |= child.mIsBottomLeftRound;
+ mIsBottomRightRound |= child.mIsBottomRightRound;
+ mIsTopRound |= child.mIsTopRound;
+ mIsTopLeftRound |= child.mIsTopLeftRound;
+ mIsTopRightRound |= child.mIsTopRightRound;
+ }
+
+ /**
+ * Draws group decoration to canvas
+ */
+ public void onGroupDecorate(Canvas canvas) {
+ initCorners();
+ mBounds.left = mPaddingLeft;
+ mBounds.right = canvas.getWidth() - mPaddingRight;
+ mPaint.setColor(mFillColor);
+ mPaint.setAlpha(mFillAlpha);
+ onDraw(canvas);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/allapps/SectionDecorationInfo.java b/src/com/android/launcher3/allapps/SectionDecorationInfo.java
new file mode 100644
index 0000000..1fed2b6
--- /dev/null
+++ b/src/com/android/launcher3/allapps/SectionDecorationInfo.java
@@ -0,0 +1,77 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+
+public class SectionDecorationInfo {
+
+ public static final int ROUND_NOTHING = 1 << 1;
+ public static final int ROUND_TOP_LEFT = 1 << 2;
+ public static final int ROUND_TOP_RIGHT = 1 << 3;
+ public static final int ROUND_BOTTOM_LEFT = 1 << 4;
+ public static final int ROUND_BOTTOM_RIGHT = 1 << 5;
+ public static final int DECORATOR_ALPHA = 255;
+
+ protected boolean mShouldDecorateItemsTogether;
+ private SectionDecorationHandler mDecorationHandler;
+ protected boolean mIsTopRound;
+ protected boolean mIsBottomRound;
+
+ public SectionDecorationInfo(Context context, int roundRegions, boolean decorateTogether) {
+ mDecorationHandler =
+ new SectionDecorationHandler(context, DECORATOR_ALPHA,
+ isFlagEnabled(roundRegions, ROUND_TOP_LEFT),
+ isFlagEnabled(roundRegions, ROUND_TOP_RIGHT),
+ isFlagEnabled(roundRegions, ROUND_BOTTOM_LEFT),
+ isFlagEnabled(roundRegions, ROUND_BOTTOM_RIGHT));
+ mShouldDecorateItemsTogether = decorateTogether;
+ mIsTopRound = isFlagEnabled(roundRegions, ROUND_TOP_LEFT) &&
+ isFlagEnabled(roundRegions, ROUND_TOP_RIGHT);
+ mIsBottomRound = isFlagEnabled(roundRegions, ROUND_BOTTOM_LEFT) &&
+ isFlagEnabled(roundRegions, ROUND_BOTTOM_RIGHT);
+ }
+
+ public SectionDecorationInfo(Context context, @NonNull Bundle target,
+ String targetLayoutType, @NonNull Bundle prevTarget, @NonNull Bundle nextTarget) {}
+
+ public SectionDecorationHandler getDecorationHandler() {
+ return mDecorationHandler;
+ }
+
+ private boolean isFlagEnabled(int canonicalFlag, int comparison) {
+ return (canonicalFlag & comparison) != 0;
+ }
+
+ /**
+ * Returns whether multiple {@link SectionDecorationInfo}s with the same sectionId should
+ * be grouped together.
+ */
+ public boolean shouldDecorateItemsTogether() {
+ return mShouldDecorateItemsTogether;
+ }
+
+ public boolean isTopRound() {
+ return mIsTopRound;
+ }
+
+ public boolean isBottomRound() {
+ return mIsBottomRound;
+ }
+}
diff --git a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
new file mode 100644
index 0000000..bdac05d
--- /dev/null
+++ b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
@@ -0,0 +1,103 @@
+package com.android.launcher3.backuprestore
+
+import android.content.Context
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceBasedOverride
+
+/**
+ * Wrapper for logging Restore event metrics for both success and failure to restore the Launcher
+ * workspace from a backup.
+ */
+open class LauncherRestoreEventLogger : ResourceBasedOverride {
+
+ 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,
+ context,
+ R.string.launcher_restore_event_logger_class
+ )
+ }
+ }
+
+ /**
+ * For logging when multiple items of a given data type failed to restore.
+ *
+ * @param dataType The data type that was not restored.
+ * @param count the number of data items that were not restored.
+ * @param error error type for why the data was not restored.
+ */
+ open fun logLauncherItemsRestoreFailed(dataType: String, count: Int, error: String?) {
+ // no-op
+ }
+
+ /**
+ * For logging when multiple items of a given data type were successfully restored.
+ *
+ * @param dataType The data type that was restored.
+ * @param count the number of data items restored.
+ */
+ open fun logLauncherItemsRestored(dataType: String, count: Int) {
+ // no-op
+ }
+
+ /**
+ * Helper to log successfully restoring a single item from the Favorites table.
+ *
+ * @param favoritesId The id of the item type from [Favorites] that was restored.
+ */
+ open fun logSingleFavoritesItemRestored(favoritesId: Int) {
+ // no-op
+ }
+
+ /**
+ * 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.
+ * @param error error type for why the data was not restored.
+ */
+ open fun logSingleFavoritesItemRestoreFailed(favoritesId: Int, error: String?) {
+ // no-op
+ }
+
+ /**
+ * Helper to log a failure to restore items from the Favorites table.
+ *
+ * @param favoritesId The id of the item type from [Favorites] that was not restored.
+ * @param count number of items that failed to restore.
+ * @param error error type for why the data was not restored.
+ */
+ open fun logFavoritesItemsRestoreFailed(favoritesId: Int, count: Int, error: String?) {
+ // no-op
+ }
+
+ /**
+ * Uses the current [restoreEventLogger] to report its results to the [backupManager]. Use when
+ * done restoring items for Launcher.
+ */
+ open fun reportLauncherRestoreResults() {
+ // no-op
+ }
+}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 2f62840..f9d282c 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -169,10 +169,6 @@
// TODO(Block 8): Clean up flags
// TODO(Block 9): Clean up flags
- public static final BooleanFlag UNFOLDED_WIDGET_PICKER = getDebugFlag(301918659,
- "UNFOLDED_WIDGET_PICKER", DISABLED, "Enable new widget picker that takes "
- + "advantage of the unfolded foldable format");
-
public static final BooleanFlag MULTI_SELECT_EDIT_MODE = getDebugFlag(270709220,
"MULTI_SELECT_EDIT_MODE", DISABLED, "Enable new multi-select edit mode "
+ "for home screen");
@@ -201,10 +197,6 @@
"ENABLE_PARAMETRIZE_REORDER", DISABLED,
"Enables generating the reorder using a set of parameters");
- public static final BooleanFlag ENABLE_NO_LONG_PRESS_DRAG = getDebugFlag(299748096,
- "ENABLE_NO_LONG_PRESS_DRAG", ENABLED,
- "Don't trigger the drag if we are still under long press");
-
// TODO(Block 12): Clean up flags
public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(270396680,
"ENABLE_MULTI_INSTANCE", DISABLED,
@@ -308,7 +300,7 @@
"Long press of nav handle is instantly triggered if deep press is detected.");
public static final IntFlag LPNH_HAPTIC_HINT_DELAY =
- getIntFlag(309972570, "LPNH_HAPTIC_HINT_ITERATIONS", 0,
+ getIntFlag(309972570, "LPNH_HAPTIC_HINT_DELAY", 0,
"Delay before haptic hint starts.");
// TODO(Block 17): Clean up flags
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 777f4d5..aa5329b 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -17,7 +17,6 @@
package com.android.launcher3.dragndrop;
import static com.android.launcher3.Utilities.ATLEAST_Q;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_NO_LONG_PRESS_DRAG;
import android.graphics.Point;
import android.graphics.Rect;
@@ -464,7 +463,7 @@
private DropTarget checkTouchMove(final int x, final int y) {
// If we are in predrag, don't trigger any other event until we get out of it
- if (ENABLE_NO_LONG_PRESS_DRAG.get() && mIsInPreDrag) {
+ if (mIsInPreDrag) {
return mLastDropTarget;
}
DropTarget dropTarget = findDropTarget(x, y);
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 3330448..7dcc8a8 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -35,6 +35,7 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -197,7 +198,7 @@
mUiHandler = new Handler(Looper.getMainLooper());
mContext = context;
mIdp = idp;
- mDp = idp.getDeviceProfile(context).toBuilder(context).setViewScaleProvider(
+ mDp = getDeviceProfileForPreview(context).toBuilder(context).setViewScaleProvider(
this::getAppWidgetScale).build();
if (context instanceof PreviewContext) {
Context tempContext = ((PreviewContext) context).getBaseContext();
@@ -260,6 +261,21 @@
}
/**
+ * Returns the device profile based on resource configuration for previewing various display
+ * sizes
+ */
+ private DeviceProfile getDeviceProfileForPreview(Context context) {
+ float density = context.getResources().getDisplayMetrics().density;
+ Configuration config = context.getResources().getConfiguration();
+
+ return mIdp.getBestMatch(
+ config.screenWidthDp * density,
+ config.screenHeightDp * density,
+ WindowManagerProxy.INSTANCE.get(context).getRotation(context)
+ );
+ }
+
+ /**
* Returns the insets of the screen closest to the display given by the context
*/
private Rect getInsets(Context context) {
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/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index 83003ff..1a7d797 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -20,6 +20,7 @@
import android.view.View;
import android.view.View.OnFocusChangeListener;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
/**
@@ -29,7 +30,8 @@
implements OnFocusChangeListener {
public FocusIndicatorHelper(View container) {
- super(container, container.getResources().getColor(R.color.focused_background));
+ super(container, container.getResources().getColor(Flags.enableFocusOutline()
+ ? R.color.focus_outline_color : R.color.focused_background));
}
@Override
@@ -53,7 +55,18 @@
@Override
public void viewToRect(View v, Rect outRect) {
- outRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
+ if (Flags.enableFocusOutline()) {
+ // Ensure the left and top would not be negative and drawn outside of canvas
+ outRect.set(Math.max(0, v.getLeft()), Math.max(0, v.getTop()), v.getRight(),
+ v.getBottom());
+ // Stroke is drawn with half outside and half inside the view. Inset by half
+ // stroke width to move the whole stroke inside the view and avoid other views
+ // occluding it
+ int halfStrokeWidth = (int) mPaint.getStrokeWidth() / 2;
+ outRect.inset(halfStrokeWidth, halfStrokeWidth);
+ } else {
+ outRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
+ }
}
}
}
diff --git a/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
index 2dc8d81..8eb5c7d 100644
--- a/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
@@ -29,6 +29,7 @@
import android.util.FloatProperty;
import android.view.View;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
/**
@@ -97,13 +98,22 @@
mContainer = container;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mMaxAlpha = Color.alpha(color);
mPaint.setColor(0xFF000000 | color);
+ if (Flags.enableFocusOutline()) {
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(container.getResources().getDimensionPixelSize(
+ R.dimen.focus_outline_stroke_width));
+ mRadius = container.getResources().getDimensionPixelSize(
+ R.dimen.focus_outline_radius);
+ } else {
+ mPaint.setStyle(Paint.Style.FILL);
+ mRadius = container.getResources().getDimensionPixelSize(
+ R.dimen.grid_visualization_rounding_radius);
+ }
+ mMaxAlpha = Color.alpha(color);
setAlpha(0);
mShift = 0;
- mRadius = container.getResources().getDimensionPixelSize(
- R.dimen.grid_visualization_rounding_radius);
}
protected void setAlpha(float alpha) {
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 6ea3e8a..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();
@@ -411,7 +431,7 @@
mSessionHelper.getActiveSessions();
installingPkgs.forEach(mApp.getIconCache()::updateSessionCache);
FileLog.d(TAG, "loadWorkspace: Packages with active install sessions: "
- + installingPkgs.values());
+ + installingPkgs.keySet().stream().map(info -> info.mPackageName).toList());
final PackageUserKey tempPackageKey = new PackageUserKey(null, null);
mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
@@ -440,10 +460,15 @@
mShortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
shortcut);
}
+ if (pinnedShortcuts.isEmpty()) {
+ FileLog.d(TAG, "No pinned shortcuts found for user " + user);
+ }
} else {
// Shortcut manager can fail due to some race condition when the
// lock state changes too frequently. For the purpose of the loading
// shortcuts, consider the user is still locked.
+ FileLog.d(TAG, "Shortcut request failed for user "
+ + user + ", user may still be locked.");
userUnlocked = false;
}
}
@@ -453,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);
@@ -513,6 +538,7 @@
private void processWorkspaceItem(LoaderCursor c,
LoaderMemoryLogger memoryLogger,
+ @Nullable LauncherRestoreEventLogger restoreEventLogger,
HashMap<PackageUserKey, SessionInfo> installingPkgs,
boolean isSdCardReady,
PackageUserKey tempPackageKey,
@@ -526,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;
}
@@ -537,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;
}
@@ -547,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;
}
@@ -564,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);
@@ -574,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;
}
}
@@ -590,17 +635,21 @@
// Package is not yet available but might be
// installed later.
FileLog.d(TAG, "package not yet restored: " + targetPkg);
-
tempPackageKey.update(targetPkg, c.user);
if (c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED)) {
// Restore has started once.
} else if (installingPkgs.containsKey(tempPackageKey)) {
// App restore has started. Update the flag
c.restoreFlag |= WorkspaceItemInfo.FLAG_RESTORE_STARTED;
- c.updater().put(Favorites.RESTORED,
- c.restoreFlag).commit();
+ FileLog.d(TAG, "restore started for installing app: " + targetPkg);
+ c.updater().put(Favorites.RESTORED, c.restoreFlag).commit();
} else {
- c.markDeleted("Unrestored app removed: " + targetPkg);
+ 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)) {
@@ -611,13 +660,17 @@
} else if (!isSdCardReady) {
// SdCard is not ready yet. Package might get available,
// once it is ready.
- Log.d(TAG, "Missing pkg, will check later: " + targetPkg);
+ Log.d(TAG, "Missing package, will check later: " + targetPkg);
mPendingPackages.add(new PackageUserKey(targetPkg, c.user));
// Add the icon on the workspace anyway.
allowMissingTarget = true;
} 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;
}
}
@@ -647,7 +700,12 @@
ShortcutInfo pinnedShortcut = mShortcutKeyToPinnedShortcuts.get(key);
if (pinnedShortcut == null) {
// The shortcut is no longer valid.
- c.markDeleted("Pinned shortcut not found");
+ 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());
@@ -666,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();
@@ -745,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
@@ -768,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 {
@@ -793,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) {
@@ -817,7 +892,7 @@
} else {
Log.v(TAG, "Widget restore pending id=" + c.id
+ " appWidgetId=" + appWidgetId
- + " status =" + c.restoreFlag);
+ + " status=" + c.restoreFlag);
appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, component);
appWidgetInfo.restoreStatus = c.restoreFlag;
@@ -835,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;
}
@@ -856,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 =
@@ -869,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/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 139efc3..d2b7161 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -62,6 +62,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
@@ -262,7 +263,7 @@
*/
public void tryMigrateDB() {
if (!migrateGridIfNeeded()) {
- Log.d(TAG, "Migration failed: resetting launcher database");
+ FileLog.d(TAG, "Migration failed: resetting launcher database");
createEmptyDB();
LauncherPrefs.get(mContext).putSync(
getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true));
@@ -282,15 +283,17 @@
createDbIfNotExists();
if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
// If we have already create a new DB, ignore migration
+ Log.d(TAG, "migrateGridIfNeeded: new DB already created, skipping migration");
return false;
}
InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
if (!GridSizeMigrationUtil.needsToMigrate(mContext, idp)) {
+ Log.d(TAG, "migrateGridIfNeeded: no grid migration needed");
return true;
}
String targetDbName = new DeviceGridState(idp).getDbFile();
if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) {
- Log.e(TAG, "migrateGridIfNeeded - target db is same as current: " + targetDbName);
+ Log.e(TAG, "migrateGridIfNeeded: target db is same as current: " + targetDbName);
return false;
}
DatabaseHelper oldHelper = mOpenHelper;
@@ -299,6 +302,9 @@
try {
return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, idp, mOpenHelper,
oldHelper.getWritableDatabase());
+ } catch (Exception e) {
+ FileLog.e(TAG, "Failed to migrate grid", e);
+ return false;
} finally {
if (mOpenHelper != oldHelper) {
oldHelper.close();
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 dbd13b3..20b2971 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,15 +84,16 @@
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",
- "container", "cellX", "cellY", "spanX", "spanY", "intent"};
+ "container", "cellX", "cellY", "spanX", "spanY", "intent", "appWidgetProvider",
+ "appWidgetId", "restored"};
/**
* Tries to restore the backup DB if needed
@@ -141,16 +143,17 @@
* 4. If restored from a single display backup, remove gaps between screenIds
* 5. Override shortcuts that need to be replaced.
*
- * @return number of items deleted.
+ * @return number of items deleted
*/
@VisibleForTesting
protected int sanitizeDB(Context context, ModelDbController controller, SQLiteDatabase db,
BackupManager backupManager) throws Exception {
- FileLog.d(TAG, "Old Launcher Database before sanitizing:");
+ logFavoritesTable(db, "Old Launcher Database before sanitizing:", null, null);
// Primary user ids
long myProfileId = controller.getSerialNumberForUser(myUserHandle());
long oldProfileId = getDefaultProfileId(db);
- FileLog.d(TAG, "sanitizeDB: myProfileId=" + myProfileId + " oldProfileId=" + oldProfileId);
+ FileLog.d(TAG, "sanitizeDB: myProfileId= " + myProfileId
+ + ", oldProfileId= " + oldProfileId);
LongSparseArray<Long> oldManagedProfileIds = getManagedProfileIds(db, oldProfileId);
LongSparseArray<Long> profileMapping = new LongSparseArray<>(oldManagedProfileIds.size()
+ 1);
@@ -182,7 +185,7 @@
final String[] args = new String[profileIds.length];
Arrays.fill(args, "?");
final String where = "profileId NOT IN (" + TextUtils.join(", ", Arrays.asList(args)) + ")";
- logUnrestoredItems(db, where, profileIds);
+ logFavoritesTable(db, "items to delete from unrestored profiles:", where, profileIds);
int itemsDeletedCount = db.delete(Favorites.TABLE_NAME, where, profileIds);
FileLog.d(TAG, itemsDeletedCount + " total items from unrestored user(s) were deleted");
@@ -242,47 +245,6 @@
}
/**
- * Queries and logs the items we will delete from unrestored profiles in the launcher db.
- * This is to understand why items might be missing during the restore process for Launcher.
- * @param database the Launcher db to query from.
- * @param where the SELECT statement to query items that will be deleted.
- * @param profileIds the profile ID's the user will be migrating to.
- */
- private void logUnrestoredItems(SQLiteDatabase database, String where, String[] profileIds) {
- try (Cursor itemsToDelete = database.query(
- /* table */ Favorites.TABLE_NAME,
- /* columns */ DB_COLUMNS_TO_LOG,
- /* selection */ where,
- /* selection args */ profileIds,
- /* groupBy */ null,
- /* having */ null,
- /* orderBy */ null
- )) {
- if (itemsToDelete.moveToFirst()) {
- String[] columnNames = itemsToDelete.getColumnNames();
- StringBuilder stringBuilder = new StringBuilder(
- "items to be deleted from the Favorites Table during restore:\n"
- );
- do {
- for (String columnName : columnNames) {
- stringBuilder.append(columnName)
- .append("=")
- .append(itemsToDelete.getString(
- itemsToDelete.getColumnIndex(columnName)))
- .append(" ");
- }
- stringBuilder.append("\n");
- } while (itemsToDelete.moveToNext());
- FileLog.d(TAG, stringBuilder.toString());
- } else {
- FileLog.d(TAG, "logDeletedItems: No items found to delete");
- }
- } catch (Exception e) {
- FileLog.e(TAG, "logDeletedItems: Error reading from database", e);
- }
- }
-
- /**
* Remove gaps between screenIds to make sure no empty pages are left in between.
*
* e.g. [0, 3, 4, 6, 7] -> [0, 1, 2, 3, 4]
@@ -380,9 +342,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
@@ -396,7 +359,7 @@
IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
host);
} else {
- FileLog.d(TAG, "No app widget ids to restore.");
+ FileLog.d(TAG, "No app widget ids were received from backup to restore.");
}
lp.remove(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS);
@@ -409,16 +372,16 @@
private void restoreAppWidgetIds(Context context, ModelDbController controller,
int[] oldWidgetIds, int[] newWidgetIds, @NonNull AppWidgetHost host) {
if (WidgetsModel.GO_DISABLE_WIDGETS) {
- Log.e(TAG, "Skipping widget ID remap as widgets not supported");
+ FileLog.e(TAG, "Skipping widget ID remap as widgets not supported");
host.deleteHost();
return;
}
if (!RestoreDbTask.isPending(context)) {
// Someone has already gone through our DB once, probably LoaderTask. Skip any further
// modifications of the DB.
- Log.e(TAG, "Skipping widget ID remap as DB already in use");
+ FileLog.e(TAG, "Skipping widget ID remap as DB already in use");
for (int widgetId : newWidgetIds) {
- Log.d(TAG, "Deleting widgetId: " + widgetId);
+ FileLog.d(TAG, "Deleting widgetId: " + widgetId);
host.deleteAppWidgetId(widgetId);
}
return;
@@ -426,7 +389,7 @@
final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
- Log.d(TAG, "restoreAppWidgetIds: "
+ FileLog.d(TAG, "restoreAppWidgetIds: "
+ "oldWidgetIds=" + IntArray.wrap(oldWidgetIds).toConcatString()
+ ", newWidgetIds=" + IntArray.wrap(newWidgetIds).toConcatString());
@@ -434,7 +397,7 @@
logDatabaseWidgetInfo(controller);
for (int i = 0; i < oldWidgetIds.length; i++) {
- Log.i(TAG, "Widget state restore id " + oldWidgetIds[i] + " => " + newWidgetIds[i]);
+ FileLog.i(TAG, "Widget state restore id " + oldWidgetIds[i] + " => " + newWidgetIds[i]);
final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(newWidgetIds[i]);
final int state;
@@ -454,7 +417,7 @@
final String where = "appWidgetId=? and (restored & 1) = 1 and profileId=?";
String profileId = Long.toString(mainProfileId);
final String[] args = new String[] { oldWidgetId, profileId };
- Log.d(TAG, "restoreAppWidgetIds: querying profile id=" + profileId
+ FileLog.d(TAG, "restoreAppWidgetIds: querying profile id=" + profileId
+ " with controller profile ID=" + controllerProfileId);
int result = new ContentWriter(context,
new ContentWriter.CommitParams(controller, where, args))
@@ -463,7 +426,7 @@
.commit();
if (result == 0) {
// TODO(b/234700507): Remove the logs after the bug is fixed
- Log.e(TAG, "restoreAppWidgetIds: remapping failed since the widget is not in"
+ FileLog.e(TAG, "restoreAppWidgetIds: remapping failed since the widget is not in"
+ " the database anymore");
try (Cursor cursor = controller.getDb().query(
Favorites.TABLE_NAME,
@@ -471,7 +434,7 @@
"appWidgetId=?", new String[]{oldWidgetId}, null, null, null)) {
if (!cursor.moveToFirst()) {
// The widget no long exists.
- Log.d(TAG, "Deleting widgetId: " + newWidgetIds[i] + " with old id: "
+ FileLog.d(TAG, "Deleting widgetId: " + newWidgetIds[i] + " with old id: "
+ oldWidgetId);
host.deleteAppWidgetId(newWidgetIds[i]);
}
@@ -523,7 +486,7 @@
}
builder.append("]");
Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: "
- + builder.toString());
+ + builder);
} catch (Exception ex) {
Log.e(TAG, "Getting widget ids from the database failed", ex);
}
@@ -572,4 +535,45 @@
Collectors.joining(" OR "));
}
+ /**
+ * Queries and logs the items from the Favorites table in the launcher db.
+ * This is to understand why items might be missing during the restore process for Launcher.
+ * @param database The Launcher db to query from.
+ * @param logHeader First line in log statement, used to explain what is being logged.
+ * @param where The SELECT statement to query items.
+ * @param profileIds The profile ID's for each user profile.
+ */
+ public static void logFavoritesTable(SQLiteDatabase database, @NonNull String logHeader,
+ String where, String[] profileIds) {
+ try (Cursor itemsToDelete = database.query(
+ /* table */ Favorites.TABLE_NAME,
+ /* columns */ DB_COLUMNS_TO_LOG,
+ /* selection */ where,
+ /* selection args */ profileIds,
+ /* groupBy */ null,
+ /* having */ null,
+ /* orderBy */ null
+ )) {
+ if (itemsToDelete.moveToFirst()) {
+ String[] columnNames = itemsToDelete.getColumnNames();
+ StringBuilder stringBuilder = new StringBuilder(logHeader + "\n");
+ do {
+ for (String columnName : columnNames) {
+ stringBuilder.append(columnName)
+ .append("=")
+ .append(itemsToDelete.getString(
+ itemsToDelete.getColumnIndex(columnName)))
+ .append(" ");
+ }
+ stringBuilder.append("\n");
+ } while (itemsToDelete.moveToNext());
+ FileLog.d(TAG, stringBuilder.toString());
+ } else {
+ FileLog.d(TAG, "logFavoritesTable: No items found from query for"
+ + "\"" + logHeader + "\"");
+ }
+ } catch (Exception e) {
+ FileLog.e(TAG, "logFavoritesTable: Error reading from database", e);
+ }
+ }
}
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index 45174a7..71957e1 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -23,10 +23,10 @@
import com.android.launcher3.BubbleTextView
import com.android.launcher3.allapps.BaseAllAppsAdapter
import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.util.ExecutorRunnable
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
import com.android.launcher3.views.ActivityContext
-import java.util.concurrent.Future
const val PREINFLATE_ICONS_ROW_COUNT = 4
const val EXTRA_ICONS_COUNT = 2
@@ -38,9 +38,8 @@
*/
class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
- private var future: Future<Void>? = null
-
var hasWorkProfile = false
+ var executorRunnable: ExecutorRunnable<List<ViewHolder>>? = null
/**
* Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate.
@@ -63,21 +62,38 @@
override fun getLayoutManager(): RecyclerView.LayoutManager? = null
}
- // Inflate view holders on background thread, and added to view pool on main thread.
- future?.cancel(true)
- future =
- VIEW_PREINFLATION_EXECUTOR.submit<Void> {
- val viewHolders =
- Array(preInflateCount) {
- adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
+ executorRunnable?.cancel(/* interrupt= */ false)
+ executorRunnable =
+ ExecutorRunnable.createAndExecute(
+ VIEW_PREINFLATION_EXECUTOR,
+ {
+ val list: ArrayList<ViewHolder> = ArrayList()
+ for (i in 0 until preInflateCount) {
+ if (Thread.interrupted()) {
+ break
+ }
+ list.add(
+ adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
+ )
}
- MAIN_EXECUTOR.execute {
+ list
+ },
+ MAIN_EXECUTOR,
+ { viewHolders ->
for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) {
putRecycledView(viewHolders[i])
}
}
- null
- }
+ )
+ }
+
+ /**
+ * When clearing [RecycledViewPool], we should also abort pre-inflation tasks. This will make
+ * sure we don't inflate app icons after DeviceProfile has changed.
+ */
+ override fun clear() {
+ super.clear()
+ executorRunnable?.cancel(/* interrupt= */ true)
}
/**
diff --git a/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
index ebbff51..7502a43 100644
--- a/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
+++ b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
@@ -40,13 +40,28 @@
return specsGroup
}
+ private fun getSpecIgnoringDimensionType(
+ availableSize: Int,
+ specsGroup: ResponsiveSpecGroup<HotseatSpec>
+ ): HotseatSpec? {
+ val specWidth = specsGroup.widthSpecs.firstOrNull { availableSize <= it.maxAvailableSize }
+ val specHeight = specsGroup.heightSpecs.firstOrNull { availableSize <= it.maxAvailableSize }
+ return specWidth ?: specHeight
+ }
+
fun getCalculatedSpec(
aspectRatio: Float,
dimensionType: DimensionType,
- availableSpace: Int
+ availableSpace: Int,
): CalculatedHotseatSpec {
val specsGroup = getSpecsByAspectRatio(aspectRatio)
- val spec = specsGroup.getSpec(dimensionType, availableSpace)
+
+ // TODO(b/315548992): Ignore the dimension type to prevent crash before launcher
+ // data migration is finished. The restore process allows the initialization of
+ // an invalid or disabled grid until the data is restored and migrated.
+ val spec = getSpecIgnoringDimensionType(availableSpace, specsGroup)
+ check(spec != null) { "No available spec found within $availableSpace. $specsGroup" }
+ // val spec = specsGroup.getSpec(dimensionType, availableSpace)
return CalculatedHotseatSpec(availableSpace, spec)
}
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpecGroup.kt b/src/com/android/launcher3/responsive/ResponsiveSpecGroup.kt
index b233d7c..a758be8 100644
--- a/src/com/android/launcher3/responsive/ResponsiveSpecGroup.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveSpecGroup.kt
@@ -18,6 +18,7 @@
import android.content.res.TypedArray
import com.android.launcher3.R
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
/**
@@ -54,10 +55,29 @@
} else {
heightSpecs.firstOrNull { availableSize <= it.maxAvailableSize }
}
- check(spec != null) { "No available $type spec found within $availableSize." }
+ check(spec != null) { "No available $type spec found within $availableSize. $this" }
return spec
}
+ override fun toString(): String {
+ fun printSpec(spec: IResponsiveSpec) =
+ when (spec.specType) {
+ ResponsiveSpecType.AllApps,
+ ResponsiveSpecType.Folder,
+ ResponsiveSpecType.Workspace -> (spec as ResponsiveSpec).toString()
+ ResponsiveSpecType.Hotseat -> (spec as HotseatSpec).toString()
+ ResponsiveSpecType.Cell -> (spec as CellSpec).toString()
+ }
+
+ val widthSpecsString = widthSpecs.joinToString(", ") { printSpec(it) }
+ val heightSpecsString = heightSpecs.joinToString(", ") { printSpec(it) }
+ return "ResponsiveSpecGroup(" +
+ "aspectRatio=${aspectRatio}, " +
+ "widthSpecs=[${widthSpecsString}], " +
+ "heightSpecs=[${heightSpecsString}]" +
+ ")"
+ }
+
companion object {
const val XML_GROUP_NAME = "specs"
diff --git a/src/com/android/launcher3/shortcuts/ShortcutRequest.java b/src/com/android/launcher3/shortcuts/ShortcutRequest.java
index 5291ce4..21efceb 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutRequest.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutRequest.java
@@ -24,10 +24,11 @@
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
-import android.util.Log;
import androidx.annotation.Nullable;
+import com.android.launcher3.logging.FileLog;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -101,7 +102,7 @@
return new QueryResult(mContext.getSystemService(LauncherApps.class)
.getShortcuts(mQuery, mUserHandle));
} catch (SecurityException | IllegalStateException e) {
- Log.e(TAG, "Failed to query for shortcuts", e);
+ FileLog.e(TAG, "Failed to query for shortcuts", e);
return QueryResult.DEFAULT;
}
}
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/DimensionUtils.kt b/src/com/android/launcher3/util/DimensionUtils.kt
index 0eb0e08..63e919a 100644
--- a/src/com/android/launcher3/util/DimensionUtils.kt
+++ b/src/com/android/launcher3/util/DimensionUtils.kt
@@ -51,12 +51,12 @@
// Taskbar on phone, portrait
if (!deviceProfile.isLandscape) {
p.x = ViewGroup.LayoutParams.MATCH_PARENT
- p.y = res.getDimensionPixelSize(R.dimen.taskbar_size)
+ p.y = res.getDimensionPixelSize(R.dimen.taskbar_phone_size)
return p
}
// Taskbar on phone, landscape
- p.x = res.getDimensionPixelSize(R.dimen.taskbar_size)
+ p.x = res.getDimensionPixelSize(R.dimen.taskbar_phone_size)
p.y = ViewGroup.LayoutParams.MATCH_PARENT
return p
}
diff --git a/src/com/android/launcher3/util/ExecutorRunnable.kt b/src/com/android/launcher3/util/ExecutorRunnable.kt
new file mode 100644
index 0000000..49cf592
--- /dev/null
+++ b/src/com/android/launcher3/util/ExecutorRunnable.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.util
+
+import java.util.concurrent.Executor
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Future
+import java.util.function.Consumer
+import java.util.function.Supplier
+
+/** A [Runnable] that can be posted to a [Executor] that can be cancelled. */
+class ExecutorRunnable<T>
+private constructor(
+ private val task: Supplier<T>,
+ // Executor where consumer needs to be executed on. Typically UI executor.
+ private val callbackExecutor: Executor,
+ // Consumer that needs to be accepted upon completion of the task. Typically work that needs to
+ // be done in UI thread after task completes.
+ private val callback: Consumer<T>
+) : Runnable {
+
+ // future of this runnable that will used for cancellation.
+ lateinit var future: Future<*>
+
+ // flag to cancel the callback
+ var canceled = false
+
+ override fun run() {
+ val value: T = task.get()
+ callbackExecutor.execute {
+ if (!canceled) {
+ callback.accept(value)
+ }
+ }
+ }
+
+ /**
+ * Cancel the [ExecutorRunnable] if not scheduled. If [ExecutorRunnable] has started execution
+ * at this time, we will try to cancel the callback if not executed yet.
+ */
+ fun cancel(interrupt: Boolean) {
+ future.cancel(interrupt)
+ canceled = true
+ }
+
+ companion object {
+ /**
+ * Create [ExecutorRunnable] and execute it on task [Executor]. It will also save the
+ * [Future] into this [ExecutorRunnable] to be used for cancellation.
+ */
+ fun <T> createAndExecute(
+ // Executor where task will be executed, typically an Executor running on background
+ // thread.
+ taskExecutor: ExecutorService,
+ task: Supplier<T>,
+ callbackExecutor: Executor,
+ callback: Consumer<T>
+ ): ExecutorRunnable<T> {
+ val executorRunnable = ExecutorRunnable(task, callbackExecutor, callback)
+ executorRunnable.future = taskExecutor.submit(executorRunnable)
+ return executorRunnable
+ }
+ }
+}
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/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index f283fb6..4f20bbc 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -62,7 +62,7 @@
public static final VibrationEffect EFFECT_CLICK =
createPredefined(VibrationEffect.EFFECT_CLICK);
- private static final float LOW_TICK_SCALE = 0.7f;
+ private static final float LOW_TICK_SCALE = 0.9f;
private static final float DRAG_TEXTURE_SCALE = 0.03f;
private static final float DRAG_COMMIT_SCALE = 0.5f;
private static final float DRAG_BUMP_SCALE = 0.4f;
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 1aa49c7..12b47e6 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -19,7 +19,6 @@
import android.annotation.TargetApi;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
@@ -32,7 +31,6 @@
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.AdapterView;
@@ -43,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;
@@ -79,9 +78,6 @@
private final CheckLongPressHelper mLongPressHelper;
protected final Launcher mLauncher;
- @ViewDebug.ExportedProperty(category = "launcher")
- private boolean mReinflateOnConfigChange;
-
// Maintain the color manager.
private final LocalColorExtractor mColorExtractor;
@@ -108,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);
@@ -176,17 +175,6 @@
// The provider info or the views might have changed.
checkIfAutoAdvance();
-
- // It is possible that widgets can receive updates while launcher is not in the foreground.
- // Consequently, the widgets will be inflated for the orientation of the foreground activity
- // (framework issue). On resuming, we ensure that any widgets are inflated for the current
- // orientation.
- mReinflateOnConfigChange = !isSameOrientation();
- }
-
- private boolean isSameOrientation() {
- return mLauncher.getResources().getConfiguration().orientation ==
- mLauncher.getOrientation();
}
private boolean checkScrollableRecursively(ViewGroup viewGroup) {
@@ -450,17 +438,6 @@
scheduleNextAdvance();
}
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- // Only reinflate when the final configuration is same as the required configuration
- if (mReinflateOnConfigChange && isSameOrientation()) {
- mReinflateOnConfigChange = false;
- reInflate();
- }
- }
-
public void reInflate() {
if (!isAttachedToWindow()) {
return;
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index c30342a..8f5e2b6 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -147,6 +147,11 @@
return mAppWidgetHostViewScale;
}
+ /** Returns the {@link WidgetItem} for this {@link WidgetCell}. */
+ public WidgetItem getWidgetItem() {
+ return mItem;
+ }
+
/**
* Called to clear the view and free attached resources. (e.g., {@link Bitmap}
*/
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 583ef1a..e9a590b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -163,7 +163,7 @@
private boolean mIsInSearchMode;
private boolean mIsNoWidgetsViewNeeded;
@Px private int mMaxSpanPerRow;
- private DeviceProfile mDeviceProfile;
+ protected DeviceProfile mDeviceProfile;
protected TextView mNoWidgetsView;
protected StickyHeaderLayout mSearchScrollView;
@@ -690,12 +690,7 @@
// Enables two pane picker for unfolded foldables if the flag is on.
|| (activity.getDeviceProfile().isTwoPanels && enableUnfoldedTwoPanePicker());
- if (isTwoPane && activity.getDeviceProfile().isTwoPanels) {
- return R.layout.widgets_two_pane_sheet_foldable;
- } else if (isTwoPane) {
- return R.layout.widgets_two_pane_sheet;
- }
- return R.layout.widgets_full_sheet;
+ return isTwoPane ? R.layout.widgets_two_pane_sheet : R.layout.widgets_full_sheet;
}
@Override
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index d85737b..c3ab08c 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.widget.picker;
+import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
+
import android.content.Context;
import android.graphics.Outline;
import android.os.Process;
@@ -23,12 +25,15 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewOutlineProvider;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import androidx.annotation.NonNull;
+import androidx.annotation.Px;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.util.PackageUserKey;
@@ -46,6 +51,8 @@
private static final int PERSONAL_TAB = 0;
private static final int WORK_TAB = 1;
+ private static final int MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 268;
+ private static final int MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 395;
private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry";
private LinearLayout mSuggestedWidgetsContainer;
@@ -117,6 +124,29 @@
}
@Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ if (changed && mDeviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) {
+ LinearLayout layout = mContent.findViewById(R.id.linear_layout_container);
+ FrameLayout leftPane = layout.findViewById(R.id.recycler_view_container);
+ LinearLayout.LayoutParams layoutParams = (LayoutParams) leftPane.getLayoutParams();
+ // Width is 1/3 of the sheet unless it's less than min width or max width
+ int leftPaneWidth = layout.getMeasuredWidth() / 3;
+ @Px int minLeftPaneWidthPx = Utilities.dpToPx(MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP);
+ @Px int maxLeftPaneWidthPx = Utilities.dpToPx(MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP);
+ if (leftPaneWidth < minLeftPaneWidthPx) {
+ layoutParams.width = minLeftPaneWidthPx;
+ } else if (leftPaneWidth > maxLeftPaneWidthPx) {
+ layoutParams.width = maxLeftPaneWidthPx;
+ } else {
+ layoutParams.width = 0;
+ }
+ layoutParams.weight = layoutParams.width == 0 ? 0.33F : 0;
+ leftPane.setLayoutParams(layoutParams);
+ }
+ }
+
+ @Override
public void onRecommendedWidgetsBound() {
super.onRecommendedWidgetsBound();
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..bdb53ba 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",
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/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index e46726d..d44ccf5 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -17,11 +17,13 @@
import android.content.Context
import android.content.res.Configuration
+import android.content.res.Resources
import android.graphics.Point
import android.graphics.Rect
import android.util.DisplayMetrics
import android.view.Surface
import androidx.test.core.app.ApplicationProvider
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
@@ -30,6 +32,8 @@
import com.android.launcher3.util.rule.TestStabilityRule
import com.android.launcher3.util.window.CachedDisplayInfo
import com.android.launcher3.util.window.WindowManagerProxy
+import com.android.wm.shell.Flags
+import com.google.common.truth.Truth
import java.io.BufferedReader
import java.io.File
import java.io.PrintWriter
@@ -49,11 +53,18 @@
* For an implementation that mocks InvariantDeviceProfile, use [FakeInvariantDeviceProfileTest]
*/
abstract class AbstractDeviceProfileTest {
+ protected val testContext: Context = InstrumentationRegistry.getInstrumentation().context
protected lateinit var context: SandboxContext
protected open val runningContext: Context = ApplicationProvider.getApplicationContext()
private val displayController: DisplayController = mock()
private val windowManagerProxy: WindowManagerProxy = mock()
private val launcherPrefs: LauncherPrefs = mock()
+ private val allowLeftRightSplitInPortrait: Boolean = initAllowLeftRightSplitInPortrait()
+ fun initAllowLeftRightSplitInPortrait() : Boolean {
+ val res = Resources.getSystem();
+ val resId = res.getIdentifier("config_leftRightSplitInPortrait", "bool", "android")
+ return Flags.enableLeftRightSplitInPortrait() && resId > 0 && res.getBoolean(resId)
+ }
@Rule @JvmField val testStabilityRule = TestStabilityRule()
@@ -306,6 +317,25 @@
whenever(info.isTransientTaskbar).thenReturn(isGestureMode)
}
+ /** Asserts that the given device profile matches a previously dumped device profile state. */
+ protected fun assertDump(dp: DeviceProfile, folderName: String, filename: String) {
+ val dump = dump(context!!, dp, "${folderName}_$filename.txt")
+ var expected = readDumpFromAssets(testContext, "$folderName/$filename.txt")
+
+ // TODO(b/315230497): We don't currently have device-specific device profile dumps, so just
+ // update the result before we do the comparison
+ if (allowLeftRightSplitInPortrait) {
+ val isLeftRightSplitInPortrait = when {
+ allowLeftRightSplitInPortrait && dp.isTablet -> !dp.isLandscape
+ else -> dp.isLandscape
+ }
+ expected = expected.replace(Regex("isLeftRightSplit:\\w+"),
+ "isLeftRightSplit:$isLeftRightSplitInPortrait")
+ }
+
+ Truth.assertThat(dump).isEqualTo(expected)
+ }
+
/** Create a new dump of DeviceProfile, saves to a file in the device and returns it */
protected fun dump(context: Context, dp: DeviceProfile, fileName: String): String {
val stringWriter = StringWriter()
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 96%
rename from tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
rename to tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
index 4a42887..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";
@@ -208,13 +208,13 @@
InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
READ_DEVICE_CONFIG_PERMISSION);
assumeFalse(FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get());
- mLauncher.getWorkspace().switchToAllApps();
- mLauncher.pressBack();
- mLauncher.getWorkspace();
+ mLauncher
+ .getWorkspace()
+ .switchToAllApps()
+ .pressBackToWorkspace();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
- mLauncher.pressBack();
- mLauncher.getWorkspace();
+ mLauncher.getLaunchedAppState().pressBackToWorkspace();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
}
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
similarity index 94%
rename from tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
rename to tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index ed34307..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 {
@@ -166,9 +166,11 @@
mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
- Map<String, Point> finalPositions =
- mLauncher.getWorkspace().getWorkspaceIconsPositions();
- assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
+ if (!TestStabilityRule.isPresubmit()) { // b/315847371
+ Map<String, Point> finalPositions =
+ mLauncher.getWorkspace().getWorkspaceIconsPositions();
+ assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
+ }
} finally {
TestUtil.uninstallDummyApp();
}
diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
index 9b67310..9409ac1 100644
--- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
@@ -30,7 +30,6 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceProfileDumpTest : AbstractDeviceProfileTest() {
- private val testContext: Context = InstrumentationRegistry.getInstrumentation().context
private val folderName: String = "DeviceProfileDumpTest"
@Test
fun phonePortrait3Button() {
@@ -154,9 +153,6 @@
}
private fun assertDump(dp: DeviceProfile, filename: String) {
- val dump = dump(context!!, dp, "${folderName}_$filename.txt")
- val expected = readDumpFromAssets(testContext, "$folderName/$filename.txt")
-
- assertThat(dump).isEqualTo(expected)
+ assertDump(dp, folderName, filename);
}
}
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/TaplUtilityTests.java b/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
similarity index 97%
rename from tests/src/com/android/launcher3/tapl/TaplUtilityTests.java
rename to tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
index 15db1d8..4a1888a 100644
--- a/tests/src/com/android/launcher3/tapl/TaplUtilityTests.java
+++ b/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
@@ -20,7 +20,7 @@
import org.junit.Test;
-public class TaplUtilityTests {
+public class TaplUtilityTest {
@Test
public void testNewStringWithRegex() {
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 95ed401..79d8c60 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -257,7 +257,7 @@
final RuleChain inner = RuleChain
.outerRule(new PortraitLandscapeRunner(this))
.around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
- .around(viewCaptureRule)
+ // .around(viewCaptureRule) // b/315482167
.around(new TestIsolationRule(mLauncher, true));
return TestHelpers.isInLauncherProcess()
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/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
new file mode 100644
index 0000000..c9d118f
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.util
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.rule.TestStabilityRule
+import java.util.concurrent.ExecutorService
+import junit.framework.Assert.assertEquals
+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)
+class ExecutorRunnableTest {
+
+ private lateinit var underTest: ExecutorRunnable<Int>
+
+ private var result: Int = -1
+ private var isTaskExecuted = false
+ private var isCallbackExecuted = false
+
+ @get:Rule(order = 0) val testStabilityRule = TestStabilityRule()
+
+ @Before
+ fun setup() {
+ reset()
+ underTest =
+ ExecutorRunnable.createAndExecute(
+ Executors.UI_HELPER_EXECUTOR,
+ {
+ isTaskExecuted = true
+ 1
+ },
+ Executors.VIEW_PREINFLATION_EXECUTOR,
+ {
+ isCallbackExecuted = true
+ result = it + 1
+ }
+ )
+ }
+
+ @Test
+ fun run_and_complete() {
+ awaitAllExecutorCompleted()
+
+ assertTrue(isTaskExecuted)
+ assertTrue(isCallbackExecuted)
+ assertEquals(2, result)
+ }
+
+ @Test
+ @TestStabilityRule.Stability(
+ flavors = TestStabilityRule.LOCAL or TestStabilityRule.PLATFORM_POSTSUBMIT
+ ) // b/316588649
+ fun run_and_cancel_cancelCallback() {
+ underTest.cancel(false)
+ awaitAllExecutorCompleted()
+
+ assertFalse(isCallbackExecuted)
+ assertEquals(0, result)
+ }
+
+ @Test
+ fun run_and_cancelAfterCompletion_executeAll() {
+ awaitAllExecutorCompleted()
+
+ underTest.cancel(false)
+
+ assertTrue(isTaskExecuted)
+ assertTrue(isCallbackExecuted)
+ assertEquals(2, result)
+ }
+
+ private fun awaitExecutorCompleted(executor: ExecutorService) {
+ executor.submit<Any> { null }.get()
+ }
+
+ private fun awaitAllExecutorCompleted() {
+ awaitExecutorCompleted(Executors.UI_HELPER_EXECUTOR)
+ awaitExecutorCompleted(Executors.VIEW_PREINFLATION_EXECUTOR)
+ }
+
+ private fun reset() {
+ result = 0
+ isTaskExecuted = false
+ isCallbackExecuted = false
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 8713b68..9f2ce22 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.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.tapl.OverviewTask.TASK_START_EVENT;
import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
@@ -244,12 +242,11 @@
endY = startY;
}
- mLauncher.executeAndWaitForEvent(
+ mLauncher.executeAndWaitForLauncherStop(
() -> mLauncher.linearGesture(
startX, startY, endX, endY, 20, false,
LauncherInstrumentation.GestureScope.EXPECT_PILFER),
- event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
- () -> "Quick switch gesture didn't change window state", "swiping");
+ "swiping");
} else {
// Double press the recents button.
UiObject2 recentsButton = mLauncher.waitForNavigationUiObject("recent_apps");
@@ -258,10 +255,8 @@
"clicking Recents button for the first time");
mLauncher.getOverview();
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
- mLauncher.executeAndWaitForEvent(
+ mLauncher.executeAndWaitForLauncherStop(
() -> recentsButton.click(),
- event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
- () -> "Pressing recents button didn't change window state",
"clicking Recents button for the second time");
}
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
index d9b179c..9ca2dc8 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
@@ -118,4 +118,17 @@
public boolean isHomeState() {
return true;
}
+
+ /** Send the "back" gesture to go to workspace. */
+ public Workspace pressBackToWorkspace() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to press back from all apps to workspace")) {
+ mLauncher.runToState(
+ () -> mLauncher.pressBackImpl(),
+ NORMAL_STATE_ORDINAL,
+ "pressing back");
+ return new Workspace(mLauncher);
+ }
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
index a1d8059..5ef82ca 100644
--- a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
+++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
@@ -58,15 +58,15 @@
private final LauncherInstrumentation mLauncher;
private final LauncherInstrumentation.ContainerType mStartingContainerType;
- private final boolean mExpectHomeKeyEventsOnDismiss;
+ private final boolean mIsHomeState;
KeyboardQuickSwitch(
LauncherInstrumentation launcher,
LauncherInstrumentation.ContainerType startingContainerType,
- boolean expectHomeKeyEventsOnDismiss) {
+ boolean isHomeState) {
mLauncher = launcher;
mStartingContainerType = startingContainerType;
- mExpectHomeKeyEventsOnDismiss = expectHomeKeyEventsOnDismiss;
+ mIsHomeState = isHomeState;
}
/**
@@ -164,7 +164,7 @@
mLauncher.verifyContainerType(mStartingContainerType);
// Wait until the device has fully settled before unpressing the key code
- if (mExpectHomeKeyEventsOnDismiss) {
+ if (mIsHomeState) {
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_HOME_ALT_LEFT_UP);
}
mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
@@ -204,7 +204,14 @@
"want to launch focused task: "
+ (expectedPackageName == null ? "Overview" : expectedPackageName))) {
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KQS_ALT_LEFT_UP);
- mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
+
+ if (expectedPackageName == null || !mIsHomeState) {
+ mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
+ } else {
+ mLauncher.executeAndWaitForLauncherStop(
+ () -> mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0),
+ "unpressing left alt");
+ }
try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
"un-pressed left alt")) {
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 184ece7..501c4c3 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -346,10 +346,14 @@
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to press back from launched app to workspace")) {
- mLauncher.executeAndWaitForWallpaperAnimation(
- () -> mLauncher.pressBackImpl(),
- "pressing back"
- );
+ if (mLauncher.isLauncher3()) {
+ mLauncher.pressBackImpl();
+ } else {
+ mLauncher.executeAndWaitForWallpaperAnimation(
+ () -> mLauncher.pressBackImpl(),
+ "pressing back"
+ );
+ }
return new Workspace(mLauncher);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 91ef472..edc6aac 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -2290,7 +2290,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/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 f8fa00c..75d6ed1 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;