Merge "Revert "Allow shortcut predictions in hybrid hotseat"" into ub-launcher3-rvc-dev
diff --git a/Android.mk b/Android.mk
index fcd4a94..7805b32 100644
--- a/Android.mk
+++ b/Android.mk
@@ -67,7 +67,10 @@
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-java-files-under, src_shortcuts_overrides) \
- $(call all-java-files-under, src_ui_overrides)
+ $(call all-java-files-under, src_ui_overrides) \
+ $(call all-java-files-under, ext_tests/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/ext_tests/res
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
# Proguard is disable for testing. Derivarive prjects to keep proguard enabled
diff --git a/ext_tests/res/values/overrides.xml b/ext_tests/res/values/overrides.xml
new file mode 100644
index 0000000..3f071d4
--- /dev/null
+++ b/ext_tests/res/values/overrides.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="test_information_handler_class" translatable="false">com.android.launcher3.testing.DebugTestInformationHandler</string>
+</resources>
+
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
new file mode 100644
index 0000000..b84c7aa
--- /dev/null
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2020 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.testing;
+
+import static android.graphics.Bitmap.Config.ARGB_8888;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Debug;
+import android.system.Os;
+import android.view.View;
+
+import androidx.annotation.Keep;
+
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class to handle requests from tests, including debug ones.
+ */
+public class DebugTestInformationHandler extends TestInformationHandler {
+ private static LinkedList sLeaks;
+
+ public DebugTestInformationHandler(Context context) {
+ init(context);
+ }
+
+ private static void runGcAndFinalizersSync() {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+
+ final CountDownLatch fence = new CountDownLatch(1);
+ createFinalizationObserver(fence);
+ try {
+ do {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ } while (!fence.await(100, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ // Create the observer in the scope of a method to minimize the chance that
+ // it remains live in a DEX/machine register at the point of the fence guard.
+ // This must be kept to avoid R8 inlining it.
+ @Keep
+ private static void createFinalizationObserver(CountDownLatch fence) {
+ new Object() {
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ fence.countDown();
+ } finally {
+ super.finalize();
+ }
+ }
+ };
+ }
+
+ @Override
+ public Bundle call(String method) {
+ final Bundle response = new Bundle();
+ switch (method) {
+ case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
+ return getLauncherUIProperty(Bundle::putInt,
+ l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
+ }
+
+ case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
+ TestProtocol.sDebugTracing = true;
+ return response;
+
+ case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
+ TestProtocol.sDebugTracing = false;
+ return response;
+
+ case TestProtocol.REQUEST_PID: {
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
+ return response;
+ }
+
+ case TestProtocol.REQUEST_TOTAL_PSS_KB: {
+ runGcAndFinalizersSync();
+ Debug.MemoryInfo mem = new Debug.MemoryInfo();
+ Debug.getMemoryInfo(mem);
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mem.getTotalPss());
+ return response;
+ }
+
+ case TestProtocol.REQUEST_JAVA_LEAK: {
+ if (sLeaks == null) sLeaks = new LinkedList();
+
+ // Allocate and dirty the memory.
+ final int leakSize = 1024 * 1024;
+ final byte[] bytes = new byte[leakSize];
+ for (int i = 0; i < leakSize; i += 239) {
+ bytes[i] = (byte) (i % 256);
+ }
+ sLeaks.add(bytes);
+ return response;
+ }
+
+ case TestProtocol.REQUEST_NATIVE_LEAK: {
+ if (sLeaks == null) sLeaks = new LinkedList();
+
+ // Allocate and dirty a bitmap.
+ final Bitmap bitmap = Bitmap.createBitmap(512, 512, ARGB_8888);
+ bitmap.eraseColor(Color.RED);
+ sLeaks.add(bitmap);
+ return response;
+ }
+
+ case TestProtocol.REQUEST_VIEW_LEAK: {
+ if (sLeaks == null) sLeaks = new LinkedList();
+ sLeaks.add(new View(mContext));
+ return response;
+ }
+
+ default:
+ return super.call(method);
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
index 7b3e378..cd64a94 100644
--- a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
+++ b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
@@ -13,25 +13,30 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.fallback.RecentsRootView
+<com.android.launcher3.LauncherRootView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:clipChildren="false"
android:fitsSystemWindows="true">
- <com.android.quickstep.fallback.FallbackRecentsView
- android:id="@id/overview_panel"
+ <com.android.quickstep.fallback.RecentsDragLayer
+ android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:outlineProvider="none"
- android:theme="@style/HomeScreenElementTheme" />
+ android:clipChildren="false">
- <include
- android:id="@+id/overview_actions_view"
- layout="@layout/overview_actions_container" />
+ <com.android.quickstep.fallback.FallbackRecentsView
+ android:id="@id/overview_panel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:outlineProvider="none"
+ android:theme="@style/HomeScreenElementTheme" />
-</com.android.quickstep.fallback.RecentsRootView>
+ <include
+ android:id="@+id/overview_actions_view"
+ layout="@layout/overview_actions_container" />
+
+ </com.android.quickstep.fallback.RecentsDragLayer>
+</com.android.launcher3.LauncherRootView>
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index f5c5874..02bae64 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -205,26 +205,30 @@
mRecentsAnimationController = recentsAnimationController;
mRecentsAnimationTargets = targets;
mTransformParams.setTargetSet(mRecentsAnimationTargets);
- DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
mGestureState.getRunningTaskId());
- if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
- Rect overviewStackBounds = mActivityInterface
- .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
- dp = dp.getMultiWindowProfile(mContext,
- new WindowBounds(overviewStackBounds, targets.homeContentInsets));
- } else {
- // If we are not in multi-window mode, home insets should be same as system insets.
- dp = dp.copy(mContext);
- }
- dp.updateInsets(targets.homeContentInsets);
- dp.updateIsSeascape(mContext);
if (runningTaskTarget != null) {
mTaskViewSimulator.setPreview(runningTaskTarget);
}
- initTransitionEndpoints(dp);
+ // Only initialize the device profile, if it has not been initialized before, as in some
+ // configurations targets.homeContentInsets may not be correct.
+ if (mActivity == null) {
+ DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
+ if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+ Rect overviewStackBounds = mActivityInterface
+ .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
+ dp = dp.getMultiWindowProfile(mContext,
+ new WindowBounds(overviewStackBounds, targets.homeContentInsets));
+ } else {
+ // If we are not in multi-window mode, home insets should be same as system insets.
+ dp = dp.copy(mContext);
+ }
+ dp.updateInsets(targets.homeContentInsets);
+ dp.updateIsSeascape(mContext);
+ initTransitionEndpoints(dp);
+ }
// Notify when the animation starts
if (!mRecentsAnimationStartCallbacks.isEmpty()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
index e49c466..ba8ba33 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
@@ -17,6 +17,7 @@
package com.android.quickstep;
import static android.content.Intent.EXTRA_STREAM;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.ImageActionUtils.persistBitmapAndStartActivity;
@@ -67,7 +68,9 @@
UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(mContext,
mBitmapSupplier.get(), crop, intent, (uri, intentForUri) -> {
- intentForUri.putExtra(EXTRA_STREAM, uri);
+ intentForUri
+ .addFlags(FLAG_GRANT_READ_URI_PERMISSION)
+ .putExtra(EXTRA_STREAM, uri);
return new Intent[]{intentForUri};
}, TAG));
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index ebc83c6..6f4d34c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -4,21 +4,18 @@
import android.content.Context;
import android.os.Bundle;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.testing.TestInformationHandler;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.quickstep.util.LayoutUtils;
-import com.android.systemui.shared.recents.model.Task;
-
-import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
public class QuickstepTestInformationHandler extends TestInformationHandler {
- private final Context mContext;
+ protected final Context mContext;
+
public QuickstepTestInformationHandler(Context context) {
mContext = context;
}
@@ -27,6 +24,15 @@
public Bundle call(String method) {
final Bundle response = new Bundle();
switch (method) {
+ case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
+ return getLauncherUIProperty(Bundle::putInt, l -> {
+ final float progress = LauncherState.OVERVIEW.getVerticalProgress(l)
+ - LauncherState.ALL_APPS.getVerticalProgress(l);
+ final float distance = l.getAllAppsController().getShiftRange() * progress;
+ return (int) distance;
+ });
+ }
+
case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
final float swipeHeight =
LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile);
@@ -47,26 +53,6 @@
Bundle::putInt, PortraitStatesTouchController::getHotseatTop);
}
- case TestProtocol.REQUEST_RECENT_TASKS_LIST: {
- ArrayList<String> taskBaseIntentComponents = new ArrayList<>();
- CountDownLatch latch = new CountDownLatch(1);
- RecentsModel.INSTANCE.get(mContext).getTasks((tasks) -> {
- for (Task t : tasks) {
- taskBaseIntentComponents.add(
- t.key.baseIntent.getComponent().flattenToString());
- }
- latch.countDown();
- });
- try {
- latch.await(2, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- response.putStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD,
- taskBaseIntentComponents);
- return response;
- }
-
case TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED: {
response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get());
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 1701020..6f2f9fb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -55,7 +55,7 @@
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.fallback.FallbackRecentsStateController;
import com.android.quickstep.fallback.FallbackRecentsView;
-import com.android.quickstep.fallback.RecentsRootView;
+import com.android.quickstep.fallback.RecentsDragLayer;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
import com.android.quickstep.views.OverviewActionsView;
@@ -78,7 +78,8 @@
new ActivityTracker<>();
private Handler mUiHandler = new Handler(Looper.getMainLooper());
- private RecentsRootView mRecentsRootView;
+
+ private RecentsDragLayer mDragLayer;
private FallbackRecentsView mFallbackRecentsView;
private OverviewActionsView mActionsView;
@@ -89,13 +90,14 @@
/**
* Init drag layer and overview panel views.
*/
- protected void initViews() {
- setContentView(R.layout.fallback_recents_activity);
- mRecentsRootView = findViewById(R.id.drag_layer);
+ protected void setupViews() {
+ inflateRootView(R.layout.fallback_recents_activity);
+ setContentView(getRootView());
+ mDragLayer = findViewById(R.id.drag_layer);
mFallbackRecentsView = findViewById(R.id.overview_panel);
mActionsView = findViewById(R.id.overview_actions_view);
- mRecentsRootView.recreateControllers();
+ mDragLayer.recreateControllers();
mFallbackRecentsView.init(mActionsView);
}
@@ -105,12 +107,6 @@
super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
}
- public void onRootViewSizeChanged() {
- if (isInMultiWindowMode()) {
- onHandleConfigChanged();
- }
- }
-
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@@ -130,7 +126,7 @@
dispatchDeviceProfileChanged();
reapplyUi();
- mRecentsRootView.recreateControllers();
+ mDragLayer.recreateControllers();
}
/**
@@ -142,19 +138,14 @@
// In case we are reusing IDP, create a copy so that we don't conflict with Launcher
// activity.
- return (mRecentsRootView != null) && isInMultiWindowMode()
+ return (mDragLayer != null) && isInMultiWindowMode()
? dp.getMultiWindowProfile(this, getMultiWindowDisplaySize())
: dp.copy(this);
}
@Override
public BaseDragLayer getDragLayer() {
- return mRecentsRootView;
- }
-
- @Override
- public View getRootView() {
- return mRecentsRootView;
+ return mDragLayer;
}
@Override
@@ -252,7 +243,7 @@
mOldConfig = new Configuration(getResources().getConfiguration());
initDeviceProfile();
- initViews();
+ setupViews();
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 37314ea..abc2a9c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -519,7 +519,17 @@
}
if (mUncheckedConsumer != InputConsumer.NO_OP) {
- ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
+ switch (event.getActionMasked()) {
+ case ACTION_DOWN:
+ case ACTION_UP:
+ ActiveGestureLog.INSTANCE.addLog("onMotionEvent("
+ + (int) event.getRawX() + ", " + (int) event.getRawY() + ")",
+ event.getActionMasked());
+ break;
+ default:
+ ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
+ break;
+ }
}
boolean cleanUpConsumer = (action == ACTION_UP || action == ACTION_CANCEL)
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 9242771..d20bbe9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -107,11 +107,22 @@
}
@Override
+ protected boolean shouldAddDummyTaskView(int runningTaskId) {
+ if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId
+ && getTaskViewCount() == 0) {
+ // Do not add a dummy task if we are running over home with empty recents, so that we
+ // show the empty recents message instead of showing a dummy task and later removing it.
+ return false;
+ }
+ return super.shouldAddDummyTaskView(runningTaskId);
+ }
+
+ @Override
protected void applyLoadPlan(ArrayList<Task> tasks) {
// When quick-switching on 3p-launcher, we add a "dummy" tile corresponding to Launcher
// as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
// track the index of the next task appropriately, as if we are switching on any other app.
- if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId) {
+ if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId && !tasks.isEmpty()) {
// Check if the task list has running task
boolean found = false;
for (Task t : tasks) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java
new file mode 100644
index 0000000..a00015a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.quickstep.fallback;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.RecentsActivity;
+
+/**
+ * Drag layer for fallback recents activity
+ */
+public class RecentsDragLayer extends BaseDragLayer<RecentsActivity> {
+
+ public RecentsDragLayer(Context context, AttributeSet attrs) {
+ super(context, attrs, 1 /* alphaChannelCount */);
+ }
+
+ @Override
+ public void recreateControllers() {
+ mControllers = new TouchController[] {
+ new RecentsTaskController(mActivity),
+ new FallbackNavBarTouchController(mActivity),
+ };
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ super.setInsets(insets);
+ setBackground(insets.top == 0 || !mAllowSysuiScrims
+ ? null
+ : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
deleted file mode 100644
index 7f5ec9b..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2018 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.quickstep.fallback;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TouchController;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.RecentsActivity;
-
-public class RecentsRootView extends BaseDragLayer<RecentsActivity> {
-
- private static final int MIN_SIZE = 10;
-
- private final Point mLastKnownSize = new Point(MIN_SIZE, MIN_SIZE);
-
- public RecentsRootView(Context context, AttributeSet attrs) {
- super(context, attrs, 1 /* alphaChannelCount */);
- setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | SYSTEM_UI_FLAG_LAYOUT_STABLE);
- }
-
- public Point getLastKnownSize() {
- return mLastKnownSize;
- }
-
- @Override
- public void recreateControllers() {
- mControllers = new TouchController[] {
- new RecentsTaskController(mActivity),
- new FallbackNavBarTouchController(mActivity),
- };
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Check size changes before the actual measure, to avoid multiple measure calls.
- int width = Math.max(MIN_SIZE, MeasureSpec.getSize(widthMeasureSpec));
- int height = Math.max(MIN_SIZE, MeasureSpec.getSize(heightMeasureSpec));
- if (mLastKnownSize.x != width || mLastKnownSize.y != height) {
- mLastKnownSize.set(width, height);
- mActivity.onRootViewSizeChanged();
- }
-
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @TargetApi(23)
- @Override
- protected boolean fitSystemWindows(Rect insets) {
- // Update device profile before notifying the children.
- mActivity.getDeviceProfile().updateInsets(insets);
- setInsets(insets);
- return false; // Let children get the full insets
- }
-
- @Override
- public void setInsets(Rect insets) {
- // If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by
- // modifying child layout params.
- if (!insets.equals(mInsets)) {
- super.setInsets(insets);
- }
- setBackground(insets.top == 0 || !mAllowSysuiScrims
- ? null
- : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
- }
-
- public void dispatchInsets() {
- mActivity.getDeviceProfile().updateInsets(mInsets);
- super.setInsets(mInsets);
- }
-}
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index a9f138e..4e967cf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -23,6 +23,8 @@
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
+import static com.android.launcher3.PagedView.ACTION_MOVE_ALLOW_EASY_FLING;
+import static com.android.launcher3.PagedView.DEBUG_FAILED_QUICKSWITCH;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
@@ -38,6 +40,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
@@ -76,7 +79,8 @@
private static final String UP_EVT = "OtherActivityInputConsumer.UP";
// TODO: Move to quickstep contract
- public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
+ public static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 9;
+ public static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 2;
private final RecentsAnimationDeviceState mDeviceState;
private final NavBarPosition mNavBarPosition;
@@ -150,10 +154,12 @@
boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning();
mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
- mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
- float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop;
- mSquaredTouchSlop = slop * slop;
+ float slopMultiplier = mDeviceState.isFullyGesturalNavMode()
+ ? QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL
+ : QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON;
+ mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
+ mSquaredTouchSlop = slopMultiplier * mTouchSlop * mTouchSlop;
mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
@@ -187,6 +193,10 @@
&& !mRecentsViewDispatcher.hasConsumer()) {
mRecentsViewDispatcher.setConsumer(mInteractionHandler
.getRecentsViewDispatcher(mNavBarPosition.getRotation()));
+ int action = ev.getAction();
+ ev.setAction(ACTION_MOVE_ALLOW_EASY_FLING);
+ mRecentsViewDispatcher.dispatchEvent(ev);
+ ev.setAction(action);
}
int edgeFlags = ev.getEdgeFlags();
ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
@@ -317,6 +327,13 @@
}
case ACTION_CANCEL:
case ACTION_UP: {
+ if (DEBUG_FAILED_QUICKSWITCH && !mPassedWindowMoveSlop) {
+ float displacementX = mLastPos.x - mDownPos.x;
+ float displacementY = mLastPos.y - mDownPos.y;
+ Log.d("Quickswitch", "mPassedWindowMoveSlop=false"
+ + " disp=" + squaredHypot(displacementX, displacementY)
+ + " slop=" + mSquaredTouchSlop);
+ }
finishTouchTracking(ev);
break;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
index 70fb78e..32da52e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -256,8 +256,8 @@
float taskHeight = mTaskRect.height();
mMatrix.set(mPositionHelper.getMatrix());
- mMatrix.postScale(scale, scale);
mMatrix.postTranslate(insets.left, insets.top);
+ mMatrix.postScale(scale, scale);
// Apply TaskView matrix: translate, scale, scroll
mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 99afe39..cb22570 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -105,6 +105,7 @@
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -146,8 +147,8 @@
* A list of recent tasks.
*/
@TargetApi(Build.VERSION_CODES.P)
-public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
- TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
+public abstract class RecentsView<T extends StatefulActivity> extends PagedView implements
+ Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener,
SplitScreenBounds.OnChangeListener {
@@ -389,12 +390,12 @@
setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
setEnableFreeScroll(true);
mSizeStrategy = sizeStrategy;
+ mActivity = BaseActivity.fromContext(context);
mOrientationState = new RecentsOrientedState(
context, mSizeStrategy, this::animateRecentsRotationInPlace);
mFastFlingVelocity = getResources()
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
- mActivity = BaseActivity.fromContext(context);
mModel = RecentsModel.INSTANCE.get(context);
mIdp = InvariantDeviceProfile.INSTANCE.get(context);
@@ -798,8 +799,10 @@
for (int i = getTaskViewCount() - 1; i >= 0; i--) {
TaskView taskView = getTaskViewAt(i);
if (mIgnoreResetTaskId != taskView.getTask().key.id) {
- taskView.resetVisualProperties();
+ taskView.resetViewTransforms();
taskView.setStableAlpha(mContentAlpha);
+ taskView.setFullscreenProgress(mFullscreenProgress);
+ taskView.setModalness(mTaskModalness);
}
}
if (mRunningTaskTileHidden) {
@@ -998,6 +1001,7 @@
mDwbToastShown = false;
mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
LayoutUtils.setViewEnabled(mActionsView, true);
+ mOrientationState.setGestureActive(false);
}
public @Nullable TaskView getRunningTaskView() {
@@ -1035,6 +1039,7 @@
*/
public void onGestureAnimationStart(int runningTaskId) {
// This needs to be called before the other states are set since it can create the task view
+ mOrientationState.setGestureActive(true);
showCurrentTask(runningTaskId);
setEnableFreeScroll(false);
setEnableDrawingLiveTile(false);
@@ -1097,6 +1102,8 @@
* Called when a gesture from an app has finished.
*/
public void onGestureAnimationEnd() {
+ mOrientationState.setGestureActive(false);
+
setOnScrollChangeListener(null);
setEnableFreeScroll(true);
setEnableDrawingLiveTile(true);
@@ -1109,13 +1116,20 @@
}
/**
+ * Returns true if we should add a dummy taskView for the running task id
+ */
+ protected boolean shouldAddDummyTaskView(int runningTaskId) {
+ return getTaskView(runningTaskId) == null;
+ }
+
+ /**
* Creates a task view (if necessary) to represent the task with the {@param runningTaskId}.
*
* All subsequent calls to reload will keep the task as the first item until {@link #reset()}
* is called. Also scrolls the view to this task.
*/
public void showCurrentTask(int runningTaskId) {
- if (getTaskView(runningTaskId) == null) {
+ if (shouldAddDummyTaskView(runningTaskId)) {
boolean wasEmpty = getChildCount() == 0;
// Add an empty view for now until the task plan is loaded and applied
final TaskView taskView = mTaskViewPool.getView();
@@ -1650,6 +1664,8 @@
!mOrientationState.canLauncherRotate() && isInLandscape);
resetPaddingFromTaskSize();
requestLayout();
+ // Reapply the current page to update page scrolls.
+ setCurrentPage(mCurrentPage);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 411bab4..82fabac 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -545,7 +545,7 @@
setIconAndDimTransitionProgress(iconScale, invert);
}
- private void resetViewTransforms() {
+ protected void resetViewTransforms() {
setCurveScale(1);
setTranslationX(0f);
setTranslationY(0f);
@@ -554,12 +554,6 @@
setIconScaleAndDim(1);
}
- public void resetVisualProperties() {
- resetViewTransforms();
- setFullscreenProgress(0);
- setModalness(0);
- }
-
public void setStableAlpha(float parentAlpha) {
mStableAlpha = parentAlpha;
setAlpha(mStableAlpha);
@@ -953,9 +947,6 @@
*/
public void setFullscreenProgress(float progress) {
progress = Utilities.boundToRange(progress, 0, 1);
- if (progress == mFullscreenProgress) {
- return;
- }
mFullscreenProgress = progress;
boolean isFullscreen = mFullscreenProgress > 0;
mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
new file mode 100644
index 0000000..a31ba21
--- /dev/null
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2020 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.quickstep.util;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.shadows.LShadowDisplay;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.LauncherActivityInterface;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowDisplayManager;
+
+@RunWith(RobolectricTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class TaskViewSimulatorTest {
+
+ @Test
+ public void taskProperlyScaled_portrait_noRotation_sameInsets1() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(1200, 2450)
+ .withInsets(new Rect(0, 80, 0, 120))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_portrait_noRotation_sameInsets2() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(1200, 2450)
+ .withInsets(new Rect(55, 80, 55, 120))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_noRotation_sameInsets1() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(2450, 1250)
+ .withInsets(new Rect(0, 80, 0, 40))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_noRotation_sameInsets2() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(2450, 1250)
+ .withInsets(new Rect(0, 80, 120, 0))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_noRotation_sameInsets3() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(2450, 1250)
+ .withInsets(new Rect(55, 80, 55, 120))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_rotated() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(1200, 2450)
+ .withInsets(new Rect(0, 80, 0, 120))
+ .withAppBounds(
+ new Rect(0, 0, 2450, 1200),
+ new Rect(0, 80, 0, 120),
+ Surface.ROTATION_90)
+ .verifyNoTransforms();
+ }
+
+ private static class TaskMatrixVerifier extends TransformParams {
+
+ private final Context mContext = RuntimeEnvironment.application;
+
+ private Rect mAppBounds = new Rect();
+ private Rect mLauncherInsets = new Rect();
+
+ private Rect mAppInsets;
+
+ private int mAppRotation = -1;
+ private DeviceProfile mDeviceProfile;
+
+ TaskMatrixVerifier withLauncherSize(int width, int height) {
+ ShadowDisplayManager.changeDisplay(DEFAULT_DISPLAY,
+ String.format("w%sdp-h%sdp-mdpi", width, height));
+ if (mAppBounds.isEmpty()) {
+ mAppBounds.set(0, 0, width, height);
+ }
+ return this;
+ }
+
+ TaskMatrixVerifier withInsets(Rect insets) {
+ LShadowDisplay shadowDisplay = Shadow.extract(
+ mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
+ shadowDisplay.setInsets(insets);
+ mLauncherInsets.set(insets);
+ return this;
+ }
+
+ TaskMatrixVerifier withAppBounds(Rect bounds, Rect insets, int appRotation) {
+ mAppBounds.set(bounds);
+ mAppInsets = insets;
+ mAppRotation = appRotation;
+ return this;
+ }
+
+ void verifyNoTransforms() {
+ mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(mContext)
+ .getDeviceProfile(mContext);
+ mDeviceProfile.updateInsets(mLauncherInsets);
+
+ TaskViewSimulator tvs = new TaskViewSimulator(mContext,
+ LauncherActivityInterface.INSTANCE);
+ tvs.setDp(mDeviceProfile);
+
+ int launcherRotation = DefaultDisplay.INSTANCE.get(mContext).getInfo().rotation;
+ if (mAppRotation < 0) {
+ mAppRotation = launcherRotation;
+ }
+ tvs.setLayoutRotation(launcherRotation, mAppRotation);
+ if (mAppInsets == null) {
+ mAppInsets = new Rect(mLauncherInsets);
+ }
+ tvs.setPreviewBounds(mAppBounds, mAppInsets);
+
+ tvs.fullScreenProgress.value = 1;
+ tvs.recentsViewScale.value = tvs.getFullScreenScale();
+ tvs.apply(this);
+ }
+
+ @Override
+ public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
+ SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
+ proxy.onBuildTargetParams(builder, null, this);
+ return new SurfaceParams[] {builder.build()};
+ }
+
+ @Override
+ public void applySurfaceParams(SurfaceParams[] params) {
+ // Verify that the task position remains the same
+ RectF newAppBounds = new RectF(mAppBounds);
+ params[0].matrix.mapRect(newAppBounds);
+ Assert.assertThat(newAppBounds, new AlmostSame(mAppBounds));
+
+ System.err.println("Bounds mapped: " + mAppBounds + " => " + newAppBounds);
+ }
+ }
+
+ private static class AlmostSame extends TypeSafeMatcher<RectF> {
+
+ // Allow 1px error margin to account for float to int conversions
+ private final float mError = 1f;
+ private final Rect mExpected;
+
+ AlmostSame(Rect expected) {
+ mExpected = expected;
+ }
+
+ @Override
+ protected boolean matchesSafely(RectF item) {
+ return Math.abs(item.left - mExpected.left) < mError
+ && Math.abs(item.top - mExpected.top) < mError
+ && Math.abs(item.right - mExpected.right) < mError
+ && Math.abs(item.bottom - mExpected.bottom) < mError;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValue(mExpected);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index e496807..d605317 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -34,6 +34,9 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.FolderIcon;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
@@ -44,6 +47,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.LogConfig;
import com.android.systemui.shared.system.SysUiStatsLog;
@@ -121,11 +125,14 @@
writeSnapshot(atomInfo, mInstanceId);
}
for (FolderInfo fInfo : folders) {
- ArrayList<WorkspaceItemInfo> folderContents = (ArrayList) fInfo.contents.clone();
- for (ItemInfo info : folderContents) {
- LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo);
- writeSnapshot(atomInfo, mInstanceId);
- }
+ try {
+ ArrayList<WorkspaceItemInfo> folderContents =
+ (ArrayList) Executors.MAIN_EXECUTOR.submit(fInfo.contents::clone).get();
+ for (ItemInfo info : folderContents) {
+ LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo);
+ writeSnapshot(atomInfo, mInstanceId);
+ }
+ } catch (Exception e) { }
}
for (ItemInfo info : appWidgets) {
LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
@@ -174,6 +181,9 @@
private Optional<ContainerInfo> mContainerInfo = Optional.empty();
private int mSrcState = LAUNCHER_UICHANGED__SRC_STATE__HOME;
private int mDstState = LAUNCHER_UICHANGED__DST_STATE__BACKGROUND;
+ private Optional<FromState> mFromState = Optional.empty();
+ private Optional<ToState> mToState = Optional.empty();
+ private Optional<String> mEditText = Optional.empty();
@Override
public StatsLogger withItemInfo(ItemInfo itemInfo) {
@@ -220,25 +230,33 @@
}
@Override
+ public StatsLogger withFromState(FromState fromState) {
+ this.mFromState = Optional.of(fromState);
+ return this;
+ }
+
+ @Override
+ public StatsLogger withToState(ToState toState) {
+ this.mToState = Optional.of(toState);
+ return this;
+ }
+
+ @Override
+ public StatsLogger withEditText(String editText) {
+ this.mEditText = Optional.of(editText);
+ return this;
+ }
+
+ @Override
public void log(EventEnum event) {
if (!Utilities.ATLEAST_R) {
return;
}
- LauncherAtom.ItemInfo.Builder itemInfoBuilder =
- (LauncherAtom.ItemInfo.Builder) mItemInfo.buildProto().toBuilder();
- mRank.ifPresent(itemInfoBuilder::setRank);
- if (mContainerInfo.isPresent()) {
- // User already provided container info;
- // default container info from item info will be ignored.
- itemInfoBuilder.setContainerInfo(mContainerInfo.get());
- write(event, mInstanceId, itemInfoBuilder.build(), mSrcState, mDstState);
- return;
- }
-
if (mItemInfo.container < 0) {
// Item is not within a folder. Write to StatsLog in same thread.
- write(event, mInstanceId, itemInfoBuilder.build(), mSrcState, mDstState);
+ write(event, mInstanceId, applyOverwrites(mItemInfo.buildProto()), mSrcState,
+ mDstState);
} else {
// Item is inside the folder, fetch folder info in a BG thread
// and then write to StatsLog.
@@ -248,17 +266,33 @@
public void execute(LauncherAppState app, BgDataModel dataModel,
AllAppsList apps) {
FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container);
- LauncherAtom.ItemInfo.Builder atomInfoBuilder =
- (LauncherAtom.ItemInfo.Builder) mItemInfo
- .buildProto(folderInfo).toBuilder();
- mRank.ifPresent(atomInfoBuilder::setRank);
- write(event, mInstanceId, atomInfoBuilder.build(), mSrcState,
- mDstState);
+ write(event, mInstanceId,
+ applyOverwrites(mItemInfo.buildProto(folderInfo)),
+ mSrcState, mDstState);
}
});
}
}
+ private LauncherAtom.ItemInfo applyOverwrites(LauncherAtom.ItemInfo atomInfo) {
+ LauncherAtom.ItemInfo.Builder itemInfoBuilder =
+ (LauncherAtom.ItemInfo.Builder) atomInfo.toBuilder();
+
+ mRank.ifPresent(itemInfoBuilder::setRank);
+ mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
+
+ if (mFromState.isPresent() || mToState.isPresent() || mEditText.isPresent()) {
+ FolderIcon.Builder folderIconBuilder = (FolderIcon.Builder) itemInfoBuilder
+ .getFolderIcon()
+ .toBuilder();
+ mFromState.ifPresent(folderIconBuilder::setFromLabelState);
+ mToState.ifPresent(folderIconBuilder::setToLabelState);
+ mEditText.ifPresent(folderIconBuilder::setLabelInfo);
+ itemInfoBuilder.setFolderIcon(folderIconBuilder);
+ }
+ return itemInfoBuilder.build();
+ }
+
private void write(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo,
int srcState, int dstState) {
if (IS_VERBOSE) {
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index 0b48a8b..e998e9a 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -19,6 +19,7 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.ClipData;
@@ -55,6 +56,9 @@
public class ImageActionUtils {
private static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".overview.fileprovider";
+ private static final long FILE_LIFE = 1000L /*ms*/ * 60L /*s*/ * 60L /*m*/ * 24L /*h*/;
+ private static final String SUB_FOLDER = "Overview";
+ private static final String BASE_NAME = "overview_image_";
/**
* Saves screenshot to location determine by SystemUiProxy
@@ -88,8 +92,14 @@
@WorkerThread
public static void persistBitmapAndStartActivity(Context context, Bitmap bitmap, Rect crop,
Intent intent, BiFunction<Uri, Intent, Intent[]> uriToIntentMap, String tag) {
- context.startActivities(
- uriToIntentMap.apply(getImageUri(bitmap, crop, context, tag), intent));
+ Intent[] intents = uriToIntentMap.apply(getImageUri(bitmap, crop, context, tag), intent);
+
+ // Work around b/159412574
+ if (intents.length == 1) {
+ context.startActivity(intents[0]);
+ } else {
+ context.startActivities(intents);
+ }
}
/**
@@ -104,10 +114,13 @@
*/
@WorkerThread
public static Uri getImageUri(Bitmap bitmap, Rect crop, Context context, String tag) {
+ clearOldCacheFiles(context);
Bitmap croppedBitmap = cropBitmap(bitmap, crop);
int cropHash = crop == null ? 0 : crop.hashCode();
- String baseName = "image_" + bitmap.hashCode() + "_" + cropHash + ".png";
- File file = new File(context.getCacheDir(), baseName);
+ String baseName = BASE_NAME + bitmap.hashCode() + "_" + cropHash + ".png";
+ File parent = new File(context.getCacheDir(), SUB_FOLDER);
+ parent.mkdir();
+ File file = new File(parent, baseName);
try (FileOutputStream fos = new FileOutputStream(file)) {
croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
@@ -158,15 +171,30 @@
intent = new Intent();
}
ClipData clipdata = new ClipData(new ClipDescription("content",
- new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
+ new String[]{"image/png"}),
new ClipData.Item(uri));
intent.setAction(Intent.ACTION_SEND)
.setComponent(null)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(FLAG_GRANT_READ_URI_PERMISSION)
.setType("image/png")
- .setFlags(FLAG_GRANT_READ_URI_PERMISSION)
.putExtra(Intent.EXTRA_STREAM, uri)
.setClipData(clipdata);
return new Intent[]{Intent.createChooser(intent, null).addFlags(FLAG_ACTIVITY_NEW_TASK)};
}
+
+ private static void clearOldCacheFiles(Context context) {
+ THREAD_POOL_EXECUTOR.execute(() -> {
+ File parent = new File(context.getCacheDir(), SUB_FOLDER);
+ File[] files = parent.listFiles((File f, String s) -> s.startsWith(BASE_NAME));
+ if (files != null) {
+ for (File file: files) {
+ if (file.lastModified() + FILE_LIFE < System.currentTimeMillis()) {
+ file.delete();
+ }
+ }
+ }
+ });
+
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 68636cb..90ee18f 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -105,6 +105,9 @@
private static final int FLAG_ROTATION_WATCHER_ENABLED = 1 << 6;
// Enable home rotation for UI tests, ignoring home rotation value from prefs
private static final int FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING = 1 << 7;
+ // Whether the swipe gesture is running, so the recents would stay locked in the
+ // current orientation
+ private static final int FLAG_SWIPE_UP_NOT_RUNNING = 1 << 8;
private static final int MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE =
FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY
@@ -114,7 +117,8 @@
// multi-window is enabled as in that case, activity itself rotates.
private static final int VALUE_ROTATION_WATCHER_ENABLED =
MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE | FLAG_SYSTEM_ROTATION_ALLOWED
- | FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED;
+ | FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED
+ | FLAG_SWIPE_UP_NOT_RUNNING;
private final Context mContext;
private final ContentResolver mContentResolver;
@@ -156,6 +160,7 @@
if (originalSmallestWidth < 600) {
mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
}
+ mFlags |= FLAG_SWIPE_UP_NOT_RUNNING;
initFlags();
}
@@ -167,6 +172,13 @@
}
/**
+ * Sets if the swipe up gesture is currently running or not
+ */
+ public void setGestureActive(boolean isGestureActive) {
+ setFlag(FLAG_SWIPE_UP_NOT_RUNNING, !isGestureActive);
+ }
+
+ /**
* Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
* @param touchRotation The rotation the nav bar region that is touched is in
* @param displayRotation Rotation of the display/device
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index bd8ab08..b9e0f62 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -131,6 +131,10 @@
TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
getString("result"));
}
+ // b/143488140
+ mDevice.pressHome();
+ mDevice.waitForIdle();
+ startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
}
// b/143488140
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index 4b7097a..881df1b 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -22,7 +22,7 @@
android:theme="@style/PopupItem" >
<com.android.launcher3.BubbleTextView
- style="@style/BaseIcon"
+ style="@style/BaseIconUnBounded"
android:id="@+id/bubble_text"
android:background="?android:attr/selectableItemBackground"
android:gravity="start|center_vertical"
@@ -30,6 +30,7 @@
android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
android:paddingEnd="@dimen/popup_padding_end"
android:textSize="14sp"
+ android:maxLines="2"
android:textColor="?android:attr/textColorPrimary"
launcher:iconDisplay="shortcut_popup"
launcher:layoutHorizontal="true"
diff --git a/res/layout/work_mode_switch.xml b/res/layout/work_mode_switch.xml
index b5237db..31953c7 100644
--- a/res/layout/work_mode_switch.xml
+++ b/res/layout/work_mode_switch.xml
@@ -17,7 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/PrimaryMediumText"
+ style="@style/PrimaryHeadline"
android:id="@+id/work_mode_toggle"
android:drawableStart="@drawable/ic_corp"
android:drawablePadding="16dp"
@@ -25,6 +25,7 @@
android:textColor="?attr/workProfileOverlayTextColor"
android:layout_alignParentBottom="true"
android:ellipsize="end"
+ android:elevation="10dp"
android:gravity="start"
android:lines="1"
android:showText="false"
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ac00488..25f21f3 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -55,7 +55,7 @@
<item name="iconOnlyShortcutColor">?android:attr/textColorSecondary</item>
<item name="workProfileOverlayTextColor">#FF212121</item>
<item name="eduHalfSheetBGColor">?android:attr/colorAccent</item>
- <item name="disabledIconAlpha">.36</item>
+ <item name="disabledIconAlpha">.54</item>
<item name="android:windowTranslucentStatus">false</item>
<item name="android:windowTranslucentNavigation">false</item>
@@ -69,7 +69,7 @@
<style name="LauncherTheme.DarkMainColor" parent="@style/LauncherTheme">
<item name="folderFillColor">#FF3C4043</item> <!-- 100% GM2 800 -->
<item name="folderTextColor">?attr/workspaceTextColor</item>
- <item name="disabledIconAlpha">.24</item>
+ <item name="disabledIconAlpha">.254</item>
</style>
@@ -117,7 +117,7 @@
<style name="LauncherTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark">
<item name="folderFillColor">#FF3C4043</item> <!-- 100% GM2 800 -->
<item name="folderTextColor">@android:color/white</item>
- <item name="disabledIconAlpha">.24</item>
+ <item name="disabledIconAlpha">.54</item>
</style>
<style name="LauncherTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark">
@@ -187,28 +187,31 @@
</style>
<style name="AllAppsTheme">
- <item name="disabledIconAlpha">.24</item>
+ <item name="disabledIconAlpha">.54</item>
</style>
<style name="AllAppsTheme.Dark">
- <item name="disabledIconAlpha">.28</item>
+ <item name="disabledIconAlpha">.54</item>
</style>
- <!-- Base theme for BubbleTextView and sub classes -->
- <style name="BaseIcon" parent="@android:style/TextAppearance.DeviceDefault">
+
+ <style name="BaseIconUnBounded" parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_gravity">center</item>
<item name="android:focusable">true</item>
<item name="android:gravity">center_horizontal</item>
- <item name="android:lines">1</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:defaultFocusHighlightEnabled">false</item>
-
<!-- No shadows in the base theme -->
<item name="android:shadowRadius">0</item>
</style>
+ <!-- Base theme for BubbleTextView and sub classes -->
+ <style name="BaseIcon" parent="BaseIconUnBounded">
+ <item name="android:lines">1</item>
+ </style>
+
<!-- Icon displayed on the workspace -->
<style name="BaseIcon.Workspace" >
<item name="android:shadowRadius">2.0</item>
@@ -243,7 +246,6 @@
<style name="DropTargetButton" parent="DropTargetButtonBase" />
<style name="TextHeadline" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
- <style name="PrimaryMediumText" parent="@android:style/TextAppearance.DeviceDefault.Medium"/>
<style name="PrimaryHeadline" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/>
<style name="TextTitle" parent="@android:style/TextAppearance.DeviceDefault" />
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties
index b171712..a8e0cb3 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/config/robolectric.properties
@@ -5,6 +5,7 @@
com.android.launcher3.shadows.LShadowAppWidgetManager \
com.android.launcher3.shadows.LShadowBackupManager \
com.android.launcher3.shadows.LShadowBitmap \
+ com.android.launcher3.shadows.LShadowDisplay \
com.android.launcher3.shadows.LShadowLauncherApps \
com.android.launcher3.shadows.LShadowTypeface \
com.android.launcher3.shadows.LShadowUserManager \
diff --git a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
index d927ffc..b7ba106 100644
--- a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -16,6 +16,7 @@
package com.android.launcher3.folder;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
import android.content.Context;
@@ -61,16 +62,16 @@
ArrayList<WorkspaceItemInfo> list = new ArrayList<>();
list.add(mItem1);
list.add(mItem2);
- FolderNameInfo[] nameInfos =
- new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ FolderNameInfos nameInfos = new FolderNameInfos();
new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
- assertEquals("Work", nameInfos[0].getLabel());
+ assertEquals("Work", nameInfos.getLabels()[0]);
- nameInfos[0] = new FolderNameInfo("candidate1", 0.9);
- nameInfos[1] = new FolderNameInfo("candidate2", 0.8);
- nameInfos[2] = new FolderNameInfo("candidate3", 0.7);
+ nameInfos.setLabel(0, "candidate1", 1.0f);
+ nameInfos.setLabel(1, "candidate2", 1.0f);
+ nameInfos.setLabel(2, "candidate3", 1.0f);
new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
- assertEquals("Work", nameInfos[3].getLabel());
-
+ assertEquals("Work", nameInfos.getLabels()[3]);
+ assertTrue(nameInfos.hasSuggestions());
+ assertTrue(nameInfos.hasPrimary());
}
}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
new file mode 100644
index 0000000..3813fa1
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 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.shadows;
+
+import static org.robolectric.shadow.api.Shadow.directlyOn;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Display;
+
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.shadows.ShadowDisplay;
+
+/**
+ * Extension of {@link ShadowDisplay} with missing shadow methods
+ */
+@Implements(value = Display.class)
+public class LShadowDisplay extends ShadowDisplay {
+
+ private final Rect mInsets = new Rect();
+
+ @RealObject Display realObject;
+
+ /**
+ * Sets the insets for the display
+ */
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ }
+
+ @Override
+ protected void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) {
+ directlyOn(realObject, Display.class).getCurrentSizeRange(outSmallestSize, outLargestSize);
+ outSmallestSize.x -= mInsets.left + mInsets.right;
+ outLargestSize.x -= mInsets.left + mInsets.right;
+
+ outSmallestSize.y -= mInsets.top + mInsets.bottom;
+ outLargestSize.y -= mInsets.top + mInsets.bottom;
+ }
+}
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index c1aed98..b27abc4 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -28,6 +28,7 @@
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.Executors;
/**
* Interface defining an object that can receive a drag.
@@ -84,7 +85,9 @@
public DragObject(Context context) {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- folderNameProvider = FolderNameProvider.newInstance(context);
+ Executors.MODEL_EXECUTOR.post(() -> {
+ folderNameProvider = FolderNameProvider.newInstance(context);
+ });
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ec32e62..7fc64ea 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -260,7 +260,6 @@
@Thunk
Workspace mWorkspace;
- private View mLauncherView;
@Thunk
DragLayer mDragLayer;
private DragController mDragController;
@@ -363,6 +362,7 @@
LauncherAppState app = LauncherAppState.getInstance(this);
mOldConfig = new Configuration(getResources().getConfiguration());
mModel = app.getModel();
+
mRotationHelper = new RotationHelper(this);
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
initDeviceProfile(idp);
@@ -382,8 +382,7 @@
appWidgetId -> getWorkspace().removeWidget(appWidgetId));
mAppWidgetHost.startListening();
- mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
-
+ inflateRootView(R.layout.launcher);
setupViews();
mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
@@ -420,7 +419,7 @@
// For handling default keys
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
- setContentView(mLauncherView);
+ setContentView(getRootView());
getRootView().dispatchInsets();
// Listen for broadcasts
@@ -520,12 +519,6 @@
}
@Override
- public void reapplyUi(boolean cancelCurrentAnimation) {
- getRootView().dispatchInsets();
- super.reapplyUi(cancelCurrentAnimation);
- }
-
- @Override
public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
onIdpChanged(idp);
}
@@ -581,11 +574,6 @@
return mStateManager;
}
- @Override
- public <T extends View> T findViewById(int id) {
- return mLauncherView.findViewById(id);
- }
-
private LauncherCallbacks mLauncherCallbacks;
/**
@@ -1118,10 +1106,6 @@
mHotseat = findViewById(R.id.hotseat);
mHotseat.setWorkspace(mWorkspace);
- mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
-
// Setup the drag layer
mDragLayer.setup(mDragController, mWorkspace);
@@ -1335,11 +1319,6 @@
}
@Override
- public LauncherRootView getRootView() {
- return (LauncherRootView) mLauncherView;
- }
-
- @Override
public DragLayer getDragLayer() {
return mDragLayer;
}
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 6951ff2..51504ce 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -10,6 +10,8 @@
import android.view.ViewDebug;
import android.view.WindowInsets;
+import com.android.launcher3.statemanager.StatefulActivity;
+
import java.util.Collections;
import java.util.List;
@@ -17,7 +19,7 @@
private final Rect mTempRect = new Rect();
- private final Launcher mLauncher;
+ private final StatefulActivity mActivity;
@ViewDebug.ExportedProperty(category = "launcher")
private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
@@ -31,17 +33,17 @@
public LauncherRootView(Context context, AttributeSet attrs) {
super(context, attrs);
- mLauncher = Launcher.getLauncher(context);
+ mActivity = StatefulActivity.fromContext(context);
}
private void handleSystemWindowInsets(Rect insets) {
// Update device profile before notifying th children.
- mLauncher.getDeviceProfile().updateInsets(insets);
+ mActivity.getDeviceProfile().updateInsets(insets);
boolean resetState = !insets.equals(mInsets);
setInsets(insets);
if (resetState) {
- mLauncher.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
+ mActivity.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
}
}
@@ -63,7 +65,7 @@
}
public void dispatchInsets() {
- mLauncher.getDeviceProfile().updateInsets(mInsets);
+ mActivity.getDeviceProfile().updateInsets(mInsets);
super.setInsets(mInsets);
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index dc1ff66..e29faac 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -71,7 +71,9 @@
public abstract class PagedView<T extends View & PageIndicator> extends ViewGroup {
private static final String TAG = "PagedView";
private static final boolean DEBUG = false;
+ public static final boolean DEBUG_FAILED_QUICKSWITCH = false;
+ public static final int ACTION_MOVE_ALLOW_EASY_FLING = MotionEvent.ACTION_MASK - 1;
public static final int INVALID_PAGE = -1;
protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
@@ -89,14 +91,16 @@
// The following constants need to be scaled based on density. The scaled versions will be
// assigned to the corresponding member variables below.
private static final int FLING_THRESHOLD_VELOCITY = 500;
+ private static final int EASY_FLING_THRESHOLD_VELOCITY = 400;
private static final int MIN_SNAP_VELOCITY = 1500;
private static final int MIN_FLING_VELOCITY = 250;
private boolean mFreeScroll = false;
- protected int mFlingThresholdVelocity;
- protected int mMinFlingVelocity;
- protected int mMinSnapVelocity;
+ protected final int mFlingThresholdVelocity;
+ protected final int mEasyFlingThresholdVelocity;
+ protected final int mMinFlingVelocity;
+ protected final int mMinSnapVelocity;
protected boolean mFirstLayout = true;
@@ -118,12 +122,17 @@
private float mLastMotion;
private float mLastMotionRemainder;
private float mTotalMotion;
+ // Used in special cases where the fling checks can be relaxed for an intentional gesture
+ private boolean mAllowEasyFling;
protected PagedOrientationHandler mOrientationHandler = PagedOrientationHandler.PORTRAIT;
protected int[] mPageScrolls;
private boolean mIsBeingDragged;
+ // The amount of movement to begin scrolling
protected int mTouchSlop;
+ // The amount of movement to begin paging
+ protected int mPageSlop;
private int mMaximumVelocity;
protected boolean mAllowOverScroll = true;
@@ -170,24 +179,19 @@
setHapticFeedbackEnabled(false);
mIsRtl = Utilities.isRtl(getResources());
- init();
- }
- /**
- * Initializes various states for this workspace.
- */
- protected void init() {
- Context context = getContext();
mScroller = new OverScroller(context);
setDefaultInterpolator(Interpolators.SCROLL);
mCurrentPage = 0;
final ViewConfiguration configuration = ViewConfiguration.get(context);
- mTouchSlop = configuration.getScaledPagingTouchSlop();
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mPageSlop = configuration.getScaledPagingTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
float density = getResources().getDisplayMetrics().density;
mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density);
+ mEasyFlingThresholdVelocity = (int) (EASY_FLING_THRESHOLD_VELOCITY * density);
mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density);
mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density);
@@ -913,6 +917,7 @@
mDownMotionPrimary = mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0);
mLastMotionRemainder = 0;
mTotalMotion = 0;
+ mAllowEasyFling = false;
mActivePointerId = ev.getPointerId(0);
updateIsBeingDraggedOnTouchDown();
@@ -944,7 +949,7 @@
private void updateIsBeingDraggedOnTouchDown() {
// mScroller.isFinished should be false when being flinged.
final int xDist = Math.abs(mScroller.getFinalPos() - mScroller.getCurrPos());
- final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
+ final boolean finishedScrolling = (mScroller.isFinished() || xDist < mPageSlop / 3);
if (finishedScrolling) {
mIsBeingDragged = false;
@@ -977,7 +982,7 @@
final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev, pointerIndex);
final int diff = (int) Math.abs(primaryDirection - mLastMotion);
final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
- boolean moved = diff > touchSlop;
+ boolean moved = diff > touchSlop || ev.getAction() == ACTION_MOVE_ALLOW_EASY_FLING;
if (moved) {
// Scroll if the user moved far enough along the X axis
@@ -1160,6 +1165,7 @@
mDownMotionPrimary = mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0);
mLastMotionRemainder = 0;
mTotalMotion = 0;
+ mAllowEasyFling = false;
mActivePointerId = ev.getPointerId(0);
if (mIsBeingDragged) {
onScrollInteractionBegin();
@@ -1167,8 +1173,14 @@
}
break;
- case MotionEvent.ACTION_MOVE:
- if (mIsBeingDragged) {
+ case ACTION_MOVE_ALLOW_EASY_FLING:
+ // Start scrolling immediately
+ determineScrollingStart(ev);
+ mAllowEasyFling = true;
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mIsBeingDragged) {
// Scroll to follow the motion event
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
@@ -1214,9 +1226,14 @@
SIGNIFICANT_MOVE_THRESHOLD;
mTotalMotion += Math.abs(mLastMotion + mLastMotionRemainder - primaryDirection);
- boolean isFling = mTotalMotion > mTouchSlop && shouldFlingForVelocity(velocity);
+ boolean passedSlop = mAllowEasyFling || mTotalMotion > mPageSlop;
+ boolean isFling = passedSlop && shouldFlingForVelocity(velocity);
boolean isDeltaLeft = mIsRtl ? delta > 0 : delta < 0;
boolean isVelocityLeft = mIsRtl ? velocity > 0 : velocity < 0;
+ if (DEBUG_FAILED_QUICKSWITCH && !isFling && mAllowEasyFling) {
+ Log.d("Quickswitch", "isFling=false vel=" + velocity
+ + " threshold=" + mEasyFlingThresholdVelocity);
+ }
if (!mFreeScroll) {
// In the case that the page is moved far to one direction and then is flung
@@ -1316,7 +1333,8 @@
}
protected boolean shouldFlingForVelocity(int velocity) {
- return Math.abs(velocity) > mFlingThresholdVelocity;
+ float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity;
+ return Math.abs(velocity) > threshold;
}
private void resetTouchState() {
@@ -1393,8 +1411,7 @@
}
private void onSecondaryPointerUp(MotionEvent ev) {
- final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
- MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerIndex = ev.getActionIndex();
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index fb58f21..a8dca12 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -415,7 +415,7 @@
// Always enter the spring loaded mode
mLauncher.getStateManager().goToState(SPRING_LOADED);
- mStatsLogManager.logger().withItemInfo(dragObject.originalDragInfo)
+ mStatsLogManager.logger().withItemInfo(dragObject.dragInfo)
.withInstanceId(dragObject.logInstanceId)
.log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 7998c2d..4af3544 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -28,16 +28,12 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
import static com.android.launcher3.model.data.FolderInfo.FLAG_MANUAL_FOLDER_NAME;
-import static java.util.Arrays.asList;
-import static java.util.Optional.ofNullable;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
-import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Rect;
@@ -81,7 +77,10 @@
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.FolderInfo.FolderListener;
@@ -99,7 +98,9 @@
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
+import java.util.StringJoiner;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Represents a set of icons chosen by the user or generated by the system.
@@ -109,6 +110,12 @@
View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
private static final String TAG = "Launcher.Folder";
private static final boolean DEBUG = false;
+
+ /**
+ * Used for separating folder title when logging together.
+ */
+ private static final CharSequence FOLDER_LABEL_DELIMITER = "~";
+
/**
* We avoid measuring {@link #mContent} with a 0 width or height, as this
* results in CellLayout being measured as UNSPECIFIED, which it does not support.
@@ -155,6 +162,8 @@
protected final Launcher mLauncher;
protected DragController mDragController;
public FolderInfo mInfo;
+ private CharSequence mFromTitle;
+ private FromState mFromLabelState;
@Thunk FolderIcon mFolderIcon;
@@ -315,11 +324,7 @@
public void startEditingFolderName() {
post(() -> {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- ofNullable(mInfo)
- .map(info -> info.suggestedFolderNames)
- .map(folderNames -> (FolderNameInfo[]) folderNames
- .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
- .ifPresent(this::showLabelSuggestions);
+ showLabelSuggestions();
}
mFolderName.setHint("");
mIsEditingName = true;
@@ -335,7 +340,6 @@
Log.d(TAG, "onBackKey newTitle=" + newTitle);
}
mInfo.setTitle(newTitle);
- mInfo.fromCustom = mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !mInfo.getAcceptedSuggestionIndex().isPresent(),
mLauncher.getModelWriter());
mFolderIcon.onTitleChanged(newTitle);
@@ -415,6 +419,8 @@
void bind(FolderInfo info) {
mInfo = info;
+ mFromTitle = info.title;
+ mFromLabelState = info.getFromLabelState();
ArrayList<WorkspaceItemInfo> children = info.contents;
Collections.sort(children, ITEM_POS_COMPARATOR);
updateItemLocationsInDatabaseBatch(true);
@@ -448,32 +454,24 @@
* Show suggested folder title in FolderEditText if the first suggestion is non-empty, push
* rest of the suggestions to InputMethodManager.
*/
- private void showLabelSuggestions(FolderNameInfo[] nameInfos) {
- if (nameInfos == null) {
+ private void showLabelSuggestions() {
+ if (mInfo.suggestedFolderNames == null) {
return;
}
- // Open the Folder and Keyboard when the first or second suggestion is valid non-empty
- // string.
- boolean shouldOpen = nameInfos.length > 0 && nameInfos[0] != null && !isEmpty(
- nameInfos[0].getLabel())
- || nameInfos.length > 1 && nameInfos[1] != null && !isEmpty(
- nameInfos[1].getLabel());
-
- if (shouldOpen) {
+ if (mInfo.suggestedFolderNames.hasSuggestions()) {
// update the primary suggestion if the folder name is empty.
if (isEmpty(mFolderName.getText())) {
- CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel();
- if (!isEmpty(firstLabel)) {
+ if (mInfo.suggestedFolderNames.hasPrimary()) {
mFolderName.setHint("");
- mFolderName.setText(firstLabel);
+ mFolderName.setText(mInfo.suggestedFolderNames.getLabels()[0]);
mFolderName.selectAll();
}
}
mFolderName.showKeyboard();
mFolderName.displayCompletions(
- asList(nameInfos).subList(0, nameInfos.length).stream()
+ Stream.of(mInfo.suggestedFolderNames.getLabels())
.filter(Objects::nonNull)
- .map(s -> s.getLabel().toString())
+ .map(Object::toString)
.filter(s -> !s.isEmpty())
.filter(s -> !s.equalsIgnoreCase(mFolderName.getText().toString()))
.collect(Collectors.toList()));
@@ -1005,16 +1003,14 @@
if (!items.isEmpty()) {
mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
}
- if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind) {
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind
+ && total > 1 /* no need to update if there's one icon */) {
Executors.MODEL_EXECUTOR.post(() -> {
- FolderNameInfo[] nameInfos =
- new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ FolderNameInfos nameInfos = new FolderNameInfos();
FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
fnp.getSuggestedFolderName(
getContext(), mInfo.contents, nameInfos);
- mInfo.suggestedFolderNames = new Intent().putExtra(
- FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
- nameInfos);
+ mInfo.suggestedFolderNames = nameInfos;
});
}
}
@@ -1441,10 +1437,38 @@
public void onFocusChange(View v, boolean hasFocus) {
if (v == mFolderName) {
if (hasFocus) {
+ mFromLabelState = mInfo.getFromLabelState();
+ mFromTitle = mInfo.title;
startEditingFolderName();
} else {
- mStatsLogManager.logger().withItemInfo(mInfo).log(LAUNCHER_FOLDER_LABEL_UPDATED);
- logFolderLabelState();
+ StatsLogger statsLogger = mStatsLogManager.logger()
+ .withItemInfo(mInfo)
+ .withFromState(mFromLabelState);
+
+ // If the folder label is suggested, it is logged to improve prediction model.
+ // When both old and new labels are logged together delimiter is used.
+ StringJoiner labelInfoBuilder = new StringJoiner(FOLDER_LABEL_DELIMITER);
+ if (mFromLabelState.equals(FromState.FROM_SUGGESTED)) {
+ labelInfoBuilder.add(mFromTitle);
+ }
+
+ ToState toLabelState;
+ if (mFromTitle != null && mFromTitle.equals(mInfo.title)) {
+ toLabelState = ToState.UNCHANGED;
+ } else {
+ toLabelState = mInfo.getToLabelState();
+ if (toLabelState.toString().startsWith("TO_SUGGESTION")) {
+ labelInfoBuilder.add(mInfo.title);
+ }
+ }
+ statsLogger.withToState(toLabelState);
+
+ if (labelInfoBuilder.length() > 0) {
+ statsLogger.withEditText(labelInfoBuilder.toString());
+ }
+
+ statsLogger.log(LAUNCHER_FOLDER_LABEL_UPDATED);
+ logFolderLabelState(mFromLabelState, toLabelState);
mFolderName.dispatchBackKey();
}
}
@@ -1649,8 +1673,8 @@
* @deprecated This method is only used for log validation and soon will be removed.
*/
@Deprecated
- public void logFolderLabelState() {
+ public void logFolderLabelState(FromState fromState, ToState toState) {
mLauncher.getUserEventDispatcher()
- .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent());
+ .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent(fromState, toState));
}
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index b40b1e2..152fd37 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -20,7 +20,9 @@
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -63,6 +65,8 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.icons.DotRenderer;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppInfo;
@@ -81,6 +85,7 @@
import java.util.List;
import java.util.function.Predicate;
+
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
@@ -406,10 +411,9 @@
if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
final int finalIndex = index;
- FolderNameInfo[] nameInfos =
- new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ FolderNameInfos nameInfos = new FolderNameInfos();
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- Executors.UI_HELPER_EXECUTOR.post(() -> {
+ Executors.MODEL_EXECUTOR.post(() -> {
d.folderNameProvider.getSuggestedFolderName(
getContext(), mInfo.contents, nameInfos);
showFinalView(finalIndex, item, nameInfos, d.logInstanceId);
@@ -423,12 +427,11 @@
}
private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
- FolderNameInfo[] nameInfos, InstanceId instanceId) {
+ FolderNameInfos nameInfos, InstanceId instanceId) {
postDelayed(() -> {
mPreviewItemManager.hidePreviewItem(finalIndex, false);
mFolder.showItem(item);
setLabelSuggestion(nameInfos, instanceId);
- mFolder.logFolderLabelState();
invalidate();
}, DROP_IN_ANIMATION_DURATION);
}
@@ -436,7 +439,7 @@
/**
* Set the suggested folder name.
*/
- public void setLabelSuggestion(FolderNameInfo[] nameInfos, InstanceId instanceId) {
+ public void setLabelSuggestion(FolderNameInfos nameInfos, InstanceId instanceId) {
if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
return;
}
@@ -444,15 +447,39 @@
|| mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
return;
}
- if (nameInfos == null || nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) {
+ if (nameInfos == null || !nameInfos.hasSuggestions()) {
+ StatsLogManager.newInstance(getContext()).logger()
+ .withInstanceId(instanceId)
+ .withItemInfo(mInfo)
+ .log(LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS);
return;
}
- mInfo.setTitle(nameInfos[0].getLabel());
- StatsLogManager.newInstance(getContext()).logger().withItemInfo(mInfo)
- .withInstanceId(instanceId).log(LAUNCHER_FOLDER_LABEL_UPDATED);
+ if (!nameInfos.hasPrimary()) {
+ StatsLogManager.newInstance(getContext()).logger()
+ .withInstanceId(instanceId)
+ .withItemInfo(mInfo)
+ .log(LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY);
+ return;
+ }
+ CharSequence newTitle = nameInfos.getLabels()[0];
+ FromState fromState = mInfo.getFromLabelState();
+
+ mInfo.setTitle(newTitle);
onTitleChanged(mInfo.title);
mFolder.mFolderName.setText(mInfo.title);
mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo);
+
+ // Logging for folder creation flow
+ StatsLogManager.newInstance(getContext()).logger()
+ .withInstanceId(instanceId)
+ .withItemInfo(mInfo)
+ .withFromState(fromState)
+ .withToState(ToState.TO_SUGGESTION0)
+ // When LAUNCHER_FOLDER_LABEL_UPDATED event.edit_text does not have delimiter,
+ // event is assumed to be folder creation on the server side.
+ .withEditText(newTitle.toString())
+ .log(LAUNCHER_FOLDER_AUTO_LABELED);
+ mFolder.logFolderLabelState(fromState, ToState.TO_SUGGESTION0);
}
diff --git a/src/com/android/launcher3/folder/FolderNameInfo.java b/src/com/android/launcher3/folder/FolderNameInfo.java
deleted file mode 100644
index 1841cd9..0000000
--- a/src/com/android/launcher3/folder/FolderNameInfo.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2020 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.folder;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-
-/**
- * Information about a single label suggestions of the Folder.
- */
-
-public final class FolderNameInfo implements Parcelable {
- private final double mScore;
- private final CharSequence mLabel;
-
- /**
- * Create a simple completion with label.
- *
- * @param label The text that should be inserted into the editor and pushed to
- * InputMethodManager suggestions.
- * @param score The score for the label between 0.0 and 1.0.
- */
- public FolderNameInfo(CharSequence label, double score) {
- mScore = score;
- mLabel = label;
- }
-
- private FolderNameInfo(Parcel source) {
- mScore = source.readDouble();
- mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- }
-
- public CharSequence getLabel() {
- return mLabel;
- }
-
- public double getScore() {
- return mScore;
- }
-
- /**
- * Used to package this object into a {@link Parcel}.
- *
- * @param dest The {@link Parcel} to be written.
- * @param flags The flags used for parceling.
- */
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeDouble(mScore);
- TextUtils.writeToParcel(mLabel, dest, flags);
- }
-
- /**
- * Used to make this class parcelable.
- */
- @NonNull
- public static final Parcelable.Creator<FolderNameInfo> CREATOR =
- new Parcelable.Creator<FolderNameInfo>() {
- public FolderNameInfo createFromParcel(Parcel source) {
- return new FolderNameInfo(source);
- }
-
- public FolderNameInfo[] newArray(int size) {
- return new FolderNameInfo[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- @NonNull
- public String toString() {
- return String.format("%s:%.2f", mLabel, mScore);
- }
-}
diff --git a/src/com/android/launcher3/folder/FolderNameInfos.java b/src/com/android/launcher3/folder/FolderNameInfos.java
new file mode 100644
index 0000000..457ae87
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameInfos.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2020 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.folder;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Information about a label suggestions of a Folder.
+ */
+
+public class FolderNameInfos {
+ public static final int SUCCESS = 1;
+ public static final int HAS_PRIMARY = 1 << 1;
+ public static final int HAS_SUGGESTIONS = 1 << 2;
+ public static final int ERROR_NO_PROVIDER = 1 << 3;
+ public static final int ERROR_APP_LOOKUP_FAILED = 1 << 4;
+ public static final int ERROR_ALL_APP_LOOKUP_FAILED = 1 << 5;
+ public static final int ERROR_NO_LABELS_GENERATED = 1 << 6;
+ public static final int ERROR_LABEL_LOOKUP_FAILED = 1 << 7;
+ public static final int ERROR_ALL_LABEL_LOOKUP_FAILED = 1 << 8;
+ public static final int ERROR_NO_PACKAGES = 1 << 9;
+
+ private int mStatus;
+ private final CharSequence[] mLabels;
+ private final Float[] mScores;
+
+ public FolderNameInfos() {
+ mStatus = 0;
+ mLabels = new CharSequence[FolderNameProvider.SUGGEST_MAX];
+ mScores = new Float[FolderNameProvider.SUGGEST_MAX];
+ }
+
+ /**
+ * set the status of FolderNameInfos.
+ */
+ public void setStatus(int statusBit) {
+ mStatus = mStatus | statusBit;
+ }
+
+ /**
+ * returns status of FolderNameInfos generations.
+ */
+ public int status() {
+ return mStatus;
+ }
+
+ /**
+ * return true if the first suggestion is a Primary suggestion.
+ */
+ public boolean hasPrimary() {
+ return (mStatus & HAS_PRIMARY) > 0 && (mLabels[0] != null);
+ }
+
+ /**
+ * return true if there is at least one valid suggestion.
+ */
+ public boolean hasSuggestions() {
+ for (CharSequence l : mLabels) {
+ if (l != null && !TextUtils.isEmpty(l)) return true;
+ }
+ return false;
+ }
+
+ /**
+ * assign label and score in the specified index.
+ */
+ public void setLabel(int index, CharSequence label, Float score) {
+ if (index < mLabels.length) {
+ mLabels[index] = label;
+ mScores[index] = score;
+ }
+ }
+
+ /**
+ * returns true if the label is found in label suggestions/
+ */
+ public boolean contains(CharSequence label) {
+ return Arrays.stream(mLabels)
+ .filter(Objects::nonNull)
+ .anyMatch(l -> l.toString().equalsIgnoreCase(label.toString()));
+ }
+
+
+ public CharSequence[] getLabels() {
+ return mLabels;
+ }
+
+ public Float[] getScores() {
+ return mScores;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return String.format("status=%s, labels=%s", Integer.toBinaryString(mStatus),
+ Arrays.toString(mLabels));
+ }
+}
+
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 7731e6e..d166e27 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -31,6 +31,7 @@
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.ResourceBasedOverride;
import java.util.ArrayList;
@@ -64,6 +65,7 @@
public static FolderNameProvider newInstance(Context context) {
FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
context.getApplicationContext(), R.string.folder_name_provider_class);
+ Preconditions.assertWorkerThread();
fnp.load(context);
return fnp;
@@ -71,6 +73,7 @@
public static FolderNameProvider newInstance(Context context, List<AppInfo> appInfos,
IntSparseArrayMap<FolderInfo> folderInfos) {
+ Preconditions.assertWorkerThread();
FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
context.getApplicationContext(), R.string.folder_name_provider_class);
fnp.load(appInfos, folderInfos);
@@ -93,10 +96,10 @@
*/
public void getSuggestedFolderName(Context context,
ArrayList<WorkspaceItemInfo> workspaceItemInfos,
- FolderNameInfo[] nameInfos) {
-
+ FolderNameInfos nameInfos) {
+ Preconditions.assertWorkerThread();
if (DEBUG) {
- Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
+ Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString());
}
// If all the icons are from work profile,
// Then, suggest "Work" as the folder name
@@ -121,7 +124,7 @@
info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
}
if (DEBUG) {
- Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
+ Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString());
}
}
@@ -135,39 +138,37 @@
.findAny();
}
- private void setAsFirstSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
- if (nameInfos.length == 0 || contains(nameInfos, label)) {
+ private void setAsFirstSuggestion(FolderNameInfos nameInfos, CharSequence label) {
+ if (nameInfos == null || nameInfos.contains(label)) {
return;
}
- for (int i = nameInfos.length - 1; i > 0; i--) {
- if (nameInfos[i - 1] != null && !TextUtils.isEmpty(nameInfos[i - 1].getLabel())) {
- nameInfos[i] = nameInfos[i - 1];
+ nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY);
+ nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS);
+ CharSequence[] labels = nameInfos.getLabels();
+ Float[] scores = nameInfos.getScores();
+ for (int i = labels.length - 1; i > 0; i--) {
+ if (labels[i - 1] != null && !TextUtils.isEmpty(labels[i - 1])) {
+ nameInfos.setLabel(i, labels[i - 1], scores[i - 1]);
}
}
- nameInfos[0] = new FolderNameInfo(label, 1.0);
+ nameInfos.setLabel(0, label, 1.0f);
}
- private void setAsLastSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
- if (nameInfos.length == 0 || contains(nameInfos, label)) {
+ private void setAsLastSuggestion(FolderNameInfos nameInfos, CharSequence label) {
+ if (nameInfos == null || nameInfos.contains(label)) {
return;
}
-
- for (int i = 0; i < nameInfos.length; i++) {
- if (nameInfos[i] == null || TextUtils.isEmpty(nameInfos[i].getLabel())) {
- nameInfos[i] = new FolderNameInfo(label, 1.0);
+ nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY);
+ nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS);
+ CharSequence[] labels = nameInfos.getLabels();
+ for (int i = 0; i < labels.length; i++) {
+ if (labels[i] == null || TextUtils.isEmpty(labels[i])) {
+ nameInfos.setLabel(i, label, 1.0f);
return;
}
}
// Overwrite the last suggestion.
- int lastIndex = nameInfos.length - 1;
- nameInfos[lastIndex] = new FolderNameInfo(label, 1.0);
- }
-
- private boolean contains(FolderNameInfo[] nameInfos, CharSequence label) {
- return Arrays.stream(nameInfos)
- .filter(Objects::nonNull)
- .anyMatch(nameInfo -> nameInfo.getLabel().toString().equalsIgnoreCase(
- label.toString()));
+ nameInfos.setLabel(labels.length - 1, label, 1.0f);
}
private class FolderNameWorker extends BaseModelUpdateTask {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 82d61da..3edfa8d 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -21,6 +21,8 @@
import com.android.launcher3.R;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -66,8 +68,16 @@
+ "resulting in a new folder creation")
LAUNCHER_ITEM_DROP_FOLDER_CREATED(386),
- @UiEvent(doc = "User action resulted in or manually updated the folder label to "
- + "new/same value.")
+ @UiEvent(doc = "Folder's label is automatically assigned.")
+ LAUNCHER_FOLDER_AUTO_LABELED(591),
+
+ @UiEvent(doc = "Could not auto-label a folder because primary suggestion is null or empty.")
+ LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY(592),
+
+ @UiEvent(doc = "Could not auto-label a folder because no suggestions exist.")
+ LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS(593),
+
+ @UiEvent(doc = "User manually updated the folder label.")
LAUNCHER_FOLDER_LABEL_UPDATED(460),
@UiEvent(doc = "User long pressed on the workspace empty space.")
@@ -246,6 +256,27 @@
}
/**
+ * Sets FromState field of log message.
+ */
+ default StatsLogger withFromState(FromState fromState) {
+ return this;
+ }
+
+ /**
+ * Sets ToState field of log message.
+ */
+ default StatsLogger withToState(ToState toState) {
+ return this;
+ }
+
+ /**
+ * Sets editText field of log message.
+ */
+ default StatsLogger withEditText(String editText) {
+ return this;
+ }
+
+ /**
* Sets the final value for container related fields of log message.
*
* By default container related fields are derived from {@link ItemInfo}, this method would
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index f2073ef..102ec31 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -59,7 +59,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderGridOrganizer;
-import com.android.launcher3.folder.FolderNameInfo;
+import com.android.launcher3.folder.FolderNameInfos;
import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.icons.ComponentWithLabelAndIcon.ComponentWithIconCachingLogic;
@@ -953,13 +953,12 @@
synchronized (mBgDataModel) {
for (int i = 0; i < mBgDataModel.folders.size(); i++) {
- FolderNameInfo[] suggestionInfos =
- new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ FolderNameInfos suggestionInfos = new FolderNameInfos();
FolderInfo info = mBgDataModel.folders.valueAt(i);
if (info.suggestedFolderNames == null) {
provider.getSuggestedFolderName(mApp.getContext(), info.contents,
suggestionInfos);
- info.suggestedFolderNames = new Intent().putExtra("suggest", suggestionInfos);
+ info.suggestedFolderNames = suggestionInfos;
}
}
}
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 8f577b5..ecd18ce 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -29,17 +29,12 @@
import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED;
-import static java.util.Arrays.stream;
-import static java.util.Optional.ofNullable;
-
-import android.content.Intent;
import android.os.Process;
-import android.text.TextUtils;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderNameInfo;
+import com.android.launcher3.folder.FolderNameInfos;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.FromState;
import com.android.launcher3.logger.LauncherAtom.ToState;
@@ -51,10 +46,7 @@
import com.android.launcher3.util.ContentWriter;
import java.util.ArrayList;
-import java.util.Objects;
-import java.util.Optional;
import java.util.OptionalInt;
-import java.util.StringJoiner;
import java.util.stream.IntStream;
@@ -86,21 +78,7 @@
public int options;
- public Intent suggestedFolderNames;
-
- // Represents the title before current.
- // Primarily used for logging purpose.
- private CharSequence mPreviousTitle;
-
- // True if the title before was manually entered, suggested otherwise.
- // Primarily used for logging purpose.
- public boolean fromCustom;
-
- /**
- * Used for separating {@link #mPreviousTitle} and {@link #title} when concatenating them
- * for logging.
- */
- private static final CharSequence FOLDER_LABEL_DELIMITER = "=>";
+ public FolderNameInfos suggestedFolderNames;
/**
* The apps and shortcuts
@@ -207,21 +185,16 @@
return getDefaultItemInfoBuilder()
.setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size()))
.setRank(rank)
- .setAttribute(fromCustom ? MANUAL_LABEL : SUGGESTED_LABEL)
+ .setAttribute(hasOption(FLAG_MANUAL_FOLDER_NAME) ? MANUAL_LABEL : SUGGESTED_LABEL)
.setContainerInfo(getContainerInfo())
.build();
}
@Override
public void setTitle(CharSequence title) {
- mPreviousTitle = this.title;
this.title = title;
}
- public CharSequence getPreviousTitle() {
- return mPreviousTitle;
- }
-
@Override
public ItemInfo makeShallowCopy() {
FolderInfo folderInfo = new FolderInfo();
@@ -235,30 +208,7 @@
*/
@Override
public LauncherAtom.ItemInfo buildProto() {
- FromState fromFolderLabelState = getFromFolderLabelState();
- ToState toFolderLabelState = getToFolderLabelState();
- LauncherAtom.FolderIcon.Builder folderIconBuilder = LauncherAtom.FolderIcon.newBuilder()
- .setCardinality(contents.size())
- .setFromLabelState(fromFolderLabelState)
- .setToLabelState(toFolderLabelState);
-
- // If the folder label is suggested, it is logged to improve prediction model.
- // When both old and new labels are logged together delimiter is used.
- StringJoiner labelInfoBuilder = new StringJoiner(FOLDER_LABEL_DELIMITER);
- if (fromFolderLabelState.equals(FromState.FROM_SUGGESTED)) {
- labelInfoBuilder.add(mPreviousTitle);
- }
- if (toFolderLabelState.toString().startsWith("TO_SUGGESTION")) {
- labelInfoBuilder.add(title);
- }
- if (labelInfoBuilder.length() > 0) {
- folderIconBuilder.setLabelInfo(labelInfoBuilder.toString());
- }
-
- return getDefaultItemInfoBuilder()
- .setFolderIcon(folderIconBuilder)
- .setContainerInfo(getContainerInfo())
- .build();
+ return buildProto(null);
}
/**
@@ -267,47 +217,54 @@
public OptionalInt getAcceptedSuggestionIndex() {
String newLabel = checkNotNull(title,
"Expected valid folder label, but found null").toString();
- return getSuggestedLabels()
- .map(suggestionsArray ->
- IntStream.range(0, suggestionsArray.length)
- .filter(
- index -> !isEmpty(suggestionsArray[index])
- && newLabel.equalsIgnoreCase(
- suggestionsArray[index]))
- .sequential()
- .findFirst()
- ).orElse(OptionalInt.empty());
-
+ if (suggestedFolderNames == null || !suggestedFolderNames.hasSuggestions()) {
+ return OptionalInt.empty();
+ }
+ CharSequence[] labels = suggestedFolderNames.getLabels();
+ return IntStream.range(0, labels.length)
+ .filter(index -> !isEmpty(labels[index])
+ && newLabel.equalsIgnoreCase(
+ labels[index].toString()))
+ .sequential()
+ .findFirst();
}
- private LauncherAtom.ToState getToFolderLabelState() {
+ /**
+ * Returns {@link FromState} based on current {@link #title}.
+ */
+ public LauncherAtom.FromState getFromLabelState() {
+ return title == null
+ ? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED
+ : title.length() == 0
+ ? LauncherAtom.FromState.FROM_EMPTY
+ : hasOption(FLAG_MANUAL_FOLDER_NAME)
+ ? LauncherAtom.FromState.FROM_CUSTOM
+ : LauncherAtom.FromState.FROM_SUGGESTED;
+ }
+
+ /**
+ * Returns {@link ToState} based on current {@link #title}.
+ */
+ public LauncherAtom.ToState getToLabelState() {
if (title == null) {
return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
}
- if (title.equals(mPreviousTitle)) {
- return LauncherAtom.ToState.UNCHANGED;
- }
-
if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
return title.length() > 0
? LauncherAtom.ToState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED
: LauncherAtom.ToState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED;
}
- Optional<String[]> suggestedLabels = getSuggestedLabels();
- boolean isEmptySuggestions = suggestedLabels
- .map(labels -> stream(labels).allMatch(TextUtils::isEmpty))
- .orElse(true);
- if (isEmptySuggestions) {
+ // TODO: if suggestedFolderNames is null then it infrastructure issue, not
+ // ranking issue. We should log these appropriately.
+ if (suggestedFolderNames == null || !suggestedFolderNames.hasSuggestions()) {
return title.length() > 0
? LauncherAtom.ToState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS
: LauncherAtom.ToState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS;
}
- boolean hasValidPrimary = suggestedLabels
- .map(labels -> !isEmpty(labels[0]))
- .orElse(false);
+ boolean hasValidPrimary = suggestedFolderNames != null && suggestedFolderNames.hasPrimary();
if (title.length() == 0) {
return hasValidPrimary ? LauncherAtom.ToState.TO_EMPTY_WITH_VALID_PRIMARY
: LauncherAtom.ToState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
@@ -335,31 +292,6 @@
// fall through
}
return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
-
- }
-
- private LauncherAtom.FromState getFromFolderLabelState() {
- return mPreviousTitle == null
- ? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED
- : mPreviousTitle.length() == 0
- ? LauncherAtom.FromState.FROM_EMPTY
- : fromCustom
- ? LauncherAtom.FromState.FROM_CUSTOM
- : LauncherAtom.FromState.FROM_SUGGESTED;
- }
-
- private Optional<String[]> getSuggestedLabels() {
- return ofNullable(suggestedFolderNames)
- .map(folderNames ->
- (FolderNameInfo[])
- folderNames.getParcelableArrayExtra(EXTRA_FOLDER_SUGGESTIONS))
- .map(folderNameInfoArray ->
- stream(folderNameInfoArray)
- .filter(Objects::nonNull)
- .map(FolderNameInfo::getLabel)
- .filter(Objects::nonNull)
- .map(CharSequence::toString)
- .toArray(String[]::new));
}
/**
@@ -368,7 +300,8 @@
* @deprecated This method is used only for validation purpose and soon will be removed.
*/
@Deprecated
- public LauncherLogProto.LauncherEvent getFolderLabelStateLauncherEvent() {
+ public LauncherLogProto.LauncherEvent getFolderLabelStateLauncherEvent(FromState fromState,
+ ToState toState) {
return LauncherLogProto.LauncherEvent.newBuilder()
.setAction(LauncherLogProto.Action
.newBuilder()
@@ -377,8 +310,8 @@
.newBuilder()
.setType(Target.Type.ITEM)
.setItemType(LauncherLogProto.ItemType.EDITTEXT)
- .setFromFolderLabelState(convertFolderLabelState(getFromFolderLabelState()))
- .setToFolderLabelState(convertFolderLabelState(getToFolderLabelState())))
+ .setFromFolderLabelState(convertFolderLabelState(fromState))
+ .setToFolderLabelState(convertFolderLabelState(toState)))
.addSrcTarget(Target.newBuilder()
.setType(Target.Type.CONTAINER)
.setContainerType(LauncherLogProto.ContainerType.FOLDER)
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 0a1607c..dbe5f42 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -18,10 +18,13 @@
import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
import androidx.annotation.CallSuper;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.LauncherRootView;
import com.android.launcher3.Utilities;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.statemanager.StateManager.StateHandler;
@@ -38,6 +41,8 @@
private final Runnable mHandleDeferredResume = this::handleDeferredResume;
private boolean mDeferredResumePending;
+ private LauncherRootView mRootView;
+
/**
* Create handlers to control the property changes for this activity
*/
@@ -55,6 +60,23 @@
*/
public abstract StateManager<STATE_TYPE> getStateManager();
+ protected void inflateRootView(int layoutId) {
+ mRootView = (LauncherRootView) LayoutInflater.from(this).inflate(layoutId, null);
+ mRootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
+
+ @Override
+ public final LauncherRootView getRootView() {
+ return mRootView;
+ }
+
+ @Override
+ public <T extends View> T findViewById(int id) {
+ return mRootView.findViewById(id);
+ }
+
/**
* Called when transition to the state starts
*/
@@ -87,6 +109,7 @@
* the transition if requested.
*/
public void reapplyUi(boolean cancelCurrentAnimation) {
+ getRootView().dispatchInsets();
getStateManager().reapplyState(cancelCurrentAnimation);
}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index e786f07..38b3712 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -15,27 +15,17 @@
*/
package com.android.launcher3.testing;
-import static android.graphics.Bitmap.Config.ARGB_8888;
-
import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Color;
import android.graphics.Insets;
import android.os.Build;
import android.os.Bundle;
-import android.os.Debug;
-import android.system.Os;
-import android.util.Log;
-import android.view.View;
import android.view.WindowInsets;
-import androidx.annotation.Keep;
-
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
@@ -44,10 +34,7 @@
import com.android.launcher3.R;
import com.android.launcher3.util.ResourceBasedOverride;
-import java.util.LinkedList;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -65,7 +52,6 @@
protected Context mContext;
protected DeviceProfile mDeviceProfile;
protected LauncherAppState mLauncherAppState;
- private static LinkedList mLeaks;
public void init(Context context) {
mContext = context;
@@ -77,15 +63,6 @@
public Bundle call(String method) {
final Bundle response = new Bundle();
switch (method) {
- case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
- return getLauncherUIProperty(Bundle::putInt, l -> {
- final float progress = LauncherState.OVERVIEW.getVerticalProgress(l)
- - LauncherState.ALL_APPS.getVerticalProgress(l);
- final float distance = l.getAllAppsController().getShiftRange() * progress;
- return (int) distance;
- });
- }
-
case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
return getLauncherUIProperty(Bundle::putInt, l -> {
final float progress = LauncherState.NORMAL.getVerticalProgress(l)
@@ -99,14 +76,6 @@
return getUIProperty(Bundle::putBoolean, t -> isLauncherInitialized(), () -> true);
}
- case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
- TestProtocol.sDebugTracing = true;
- break;
-
- case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
- TestProtocol.sDebugTracing = false;
- break;
-
case TestProtocol.REQUEST_FREEZE_APP_LIST:
return getLauncherUIProperty(Bundle::putBoolean, l -> {
l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
@@ -118,11 +87,6 @@
return true;
});
- case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
- return getLauncherUIProperty(Bundle::putInt,
- l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
- }
-
case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
return getLauncherUIProperty(Bundle::putInt,
l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
@@ -137,59 +101,19 @@
}, this::getCurrentActivity);
}
- case TestProtocol.REQUEST_PID: {
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
- break;
- }
-
- case TestProtocol.REQUEST_TOTAL_PSS_KB: {
- runGcAndFinalizersSync();
- Debug.MemoryInfo mem = new Debug.MemoryInfo();
- Debug.getMemoryInfo(mem);
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mem.getTotalPss());
- break;
- }
-
- case TestProtocol.REQUEST_JAVA_LEAK: {
- if (mLeaks == null) mLeaks = new LinkedList();
-
- // Allocate and dirty the memory.
- final int leakSize = 1024 * 1024;
- final byte[] bytes = new byte[leakSize];
- for (int i = 0; i < leakSize; i += 239) {
- bytes[i] = (byte) (i % 256);
- }
- mLeaks.add(bytes);
- break;
- }
-
- case TestProtocol.REQUEST_NATIVE_LEAK: {
- if (mLeaks == null) mLeaks = new LinkedList();
-
- // Allocate and dirty a bitmap.
- final Bitmap bitmap = Bitmap.createBitmap(512, 512, ARGB_8888);
- bitmap.eraseColor(Color.RED);
- mLeaks.add(bitmap);
- break;
- }
-
- case TestProtocol.REQUEST_VIEW_LEAK: {
- if (mLeaks == null) mLeaks = new LinkedList();
- mLeaks.add(new View(mContext));
- break;
- }
-
case TestProtocol.REQUEST_ICON_HEIGHT: {
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
mDeviceProfile.allAppsCellHeightPx);
- break;
+ return response;
}
case TestProtocol.REQUEST_MOCK_SENSOR_ROTATION:
TestProtocol.sDisableSensorRotation = true;
- break;
+ return response;
+
+ default:
+ return null;
}
- return response;
}
protected boolean isLauncherInitialized() {
@@ -201,22 +125,6 @@
return Launcher.ACTIVITY_TRACKER.getCreatedActivity();
}
- private static void runGcAndFinalizersSync() {
- Runtime.getRuntime().gc();
- Runtime.getRuntime().runFinalization();
-
- final CountDownLatch fence = new CountDownLatch(1);
- createFinalizationObserver(fence);
- try {
- do {
- Runtime.getRuntime().gc();
- Runtime.getRuntime().runFinalization();
- } while (!fence.await(100, TimeUnit.MILLISECONDS));
- } catch (InterruptedException ex) {
- throw new RuntimeException(ex);
- }
- }
-
/**
* Returns the result by getting a Launcher property on UI thread
*/
@@ -257,21 +165,4 @@
*/
void set(Bundle b, String key, T value);
}
-
- // Create the observer in the scope of a method to minimize the chance that
- // it remains live in a DEX/machine register at the point of the fence guard.
- // This must be kept to avoid R8 inlining it.
- @Keep
- private static void createFinalizationObserver(CountDownLatch fence) {
- new Object() {
- @Override
- protected void finalize() throws Throwable {
- try {
- fence.countDown();
- } finally {
- super.finalize();
- }
- }
- };
- }
}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 7cce044..3d39d25 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -294,7 +294,8 @@
// Limits UI tests affecting tests running after them.
mLauncher.waitForLauncherInitialized();
if (mLauncherPid != 0) {
- assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+ assertEquals("Launcher crashed, pid mismatch:",
+ mLauncherPid, mLauncher.getPid().intValue());
}
checkDetectedLeaks(mLauncher);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 808be66..b6c17df 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -18,6 +18,7 @@
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Bundle;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -244,8 +245,8 @@
}
private void verifyNotFrozen(String message) {
- mLauncher.assertEquals(message, 0, mLauncher.getTestInfo(
- TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS).
- getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD));
+ final Bundle testInfo = mLauncher.getTestInfo(TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS);
+ if (testInfo == null) return;
+ mLauncher.assertEquals(message, 0, testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD));
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7ddb492..9f4d9ce 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1309,8 +1309,9 @@
getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
- public int getPid() {
- return getTestInfo(TestProtocol.REQUEST_PID).getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ public Integer getPid() {
+ final Bundle testInfo = getTestInfo(TestProtocol.REQUEST_PID);
+ return testInfo != null ? testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) : null;
}
public void produceJavaLeak() {
@@ -1338,13 +1339,13 @@
public Closable eventsCheck() {
Assert.assertTrue("Nested event checking", !sCheckingEvents);
disableSensorRotation();
- final int initialPid = getPid();
+ final Integer initialPid = getPid();
if (sEventChecker == null) sEventChecker = new LogEventChecker();
sEventChecker.start();
sCheckingEvents = true;
return () -> {
- if (initialPid != getPid()) {
+ if (initialPid != null && initialPid.intValue() != getPid()) {
if (mOnLauncherCrashed != null) mOnLauncherCrashed.run();
checkForAnomaly();
Assert.fail(