Merge "Productizing useful diags" into ub-launcher3-master
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8e01f82..4664c93 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -49,7 +49,7 @@
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
diff --git a/OWNERS b/OWNERS
index 3069afa..1d6ad8c 100644
--- a/OWNERS
+++ b/OWNERS
@@ -28,6 +28,7 @@
peanutbutter@google.com
xuqiu@google.com
sreyasr@google.com
+thiruram@google.com
per-file FeatureFlags.java, globs = set noparent
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, zakcohen@google.com, mrcasey@google.com, adamcohen@google.com, hyunyoungs@google.com
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 5611969..cd229ae 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -131,6 +131,7 @@
// Legacy shortcuts and shortcuts handled by ShortcutManager
message Shortcut {
optional string shortcut_name = 1;
+ optional string shortcut_id = 2;
}
// AppWidgets handled by AppWidgetManager
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
index 53910e3..19f85e4 100644
--- a/quickstep/AndroidManifest-launcher.xml
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -49,7 +49,7 @@
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 4e7c3fa..5be32a8 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -59,7 +59,7 @@
android:stateNotNeeded="true"
android:theme="@style/LauncherTheme"
android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""/>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
index e4442da..8cabe3d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
@@ -16,6 +16,12 @@
package com.android.launcher3.appprediction;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.annotation.TargetApi;
@@ -31,29 +37,38 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.FolderContainer;
+import com.android.launcher3.logger.LauncherAtom.HotseatContainer;
+import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer;
+import com.android.launcher3.logging.StatsLogManager.EventEnum;
import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.systemui.plugins.AppLaunchEventsPlugin;
-import com.android.systemui.plugins.PluginListener;
+import com.android.launcher3.pm.UserCache;
+import com.android.quickstep.logging.StatsLogCompatManager;
+import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Locale;
+import java.util.function.Predicate;
/**
* Subclass of app tracker which publishes the data to the prediction engine and gets back results.
*/
@TargetApi(Build.VERSION_CODES.Q)
-public class PredictionAppTracker extends AppLaunchTracker
- implements PluginListener<AppLaunchEventsPlugin> {
+public class PredictionAppTracker extends AppLaunchTracker implements StatsLogConsumer {
private static final String TAG = "PredictionAppTracker";
private static final boolean DBG = false;
@@ -65,7 +80,6 @@
protected final Context mContext;
private final Handler mMessageHandler;
- private final List<AppLaunchEventsPlugin> mAppLaunchEventsPluginsList;
// Accessed only on worker thread
private AppPredictor mHomeAppPredictor;
@@ -76,10 +90,6 @@
InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged);
mMessageHandler.sendEmptyMessage(MSG_INIT);
-
- mAppLaunchEventsPluginsList = new ArrayList<>();
- PluginManagerWrapper.INSTANCE.get(context)
- .addPluginListener(this, AppLaunchEventsPlugin.class, true);
}
@UiThread
@@ -96,6 +106,7 @@
mHomeAppPredictor.destroy();
mHomeAppPredictor = null;
}
+ StatsLogCompatManager.LOGS_CONSUMER.remove(this);
}
@WorkerThread
@@ -137,6 +148,7 @@
// Initialize the clients
int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns;
mHomeAppPredictor = createPredictor(Client.HOME, count);
+ StatsLogCompatManager.LOGS_CONSUMER.add(this);
return true;
}
case MSG_DESTROY: {
@@ -168,98 +180,142 @@
if (DBG) {
Log.d(TAG, String.format("Sent immediate message to update %s", client));
}
-
- // Relay onReturnedToHome to every plugin.
- mAppLaunchEventsPluginsList.forEach(AppLaunchEventsPlugin::onReturnedToHome);
}
- @Override
- @UiThread
- public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
- String container) {
- // TODO: Use the full shortcut info
- AppTarget target = new AppTarget.Builder(
- new AppTargetId("shortcut:" + shortcutId), packageName, user)
- .setClassName(shortcutId)
- .build();
-
- sendLaunch(target, container);
-
- // Relay onStartShortcut info to every connected plugin.
- mAppLaunchEventsPluginsList
- .forEach(plugin -> plugin.onStartShortcut(
- packageName,
- shortcutId,
- user,
- container != null ? container : CONTAINER_DEFAULT)
- );
-
- }
-
- @Override
- @UiThread
- public void onStartApp(ComponentName cn, UserHandle user, String container) {
- if (cn != null) {
- AppTarget target = new AppTarget.Builder(
- new AppTargetId("app:" + cn), cn.getPackageName(), user)
- .setClassName(cn.getClassName())
+ @AnyThread
+ private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId) {
+ AppTarget target = toAppTarget(atomInfo);
+ if (target != null) {
+ AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
+ .setLaunchLocation(getContainer(atomInfo))
.build();
- sendLaunch(target, container);
-
- // Relay onStartApp to every connected plugin.
- mAppLaunchEventsPluginsList
- .forEach(plugin -> plugin.onStartApp(
- cn,
- user,
- container != null ? container : CONTAINER_DEFAULT)
- );
+ Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
}
}
@Override
- @UiThread
- public void onDismissApp(ComponentName cn, UserHandle user, String container) {
- if (cn == null) return;
- AppTarget target = new AppTarget.Builder(
- new AppTargetId("app: " + cn), cn.getPackageName(), user)
- .setClassName(cn.getClassName())
- .build();
- sendDismiss(target, container);
-
- // Relay onDismissApp to every connected plugin.
- mAppLaunchEventsPluginsList
- .forEach(plugin -> plugin.onDismissApp(
- cn,
- user,
- container != null ? container : CONTAINER_DEFAULT)
- );
+ public void consume(EventEnum event, LauncherAtom.ItemInfo atomInfo) {
+ if (event == LAUNCHER_APP_LAUNCH_TAP
+ || event == LAUNCHER_TASK_LAUNCH_SWIPE_DOWN
+ || event == LAUNCHER_TASK_LAUNCH_TAP
+ || event == LAUNCHER_QUICKSWITCH_RIGHT
+ || event == LAUNCHER_QUICKSWITCH_LEFT) {
+ sendEvent(atomInfo, AppTargetEvent.ACTION_LAUNCH);
+ } else if (event == LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST) {
+ sendEvent(atomInfo, AppTargetEvent.ACTION_DISMISS);
+ }
}
- @UiThread
- private void sendEvent(AppTarget target, String container, int eventId) {
- AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
- .setLaunchLocation(container == null ? CONTAINER_DEFAULT : container)
- .build();
- Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
+ @Nullable
+ private AppTarget toAppTarget(LauncherAtom.ItemInfo info) {
+ UserHandle userHandle = Process.myUserHandle();
+ if (info.getIsWork()) {
+ userHandle = UserCache.INSTANCE.get(mContext).getUserProfiles().stream()
+ .filter(((Predicate<UserHandle>) userHandle::equals).negate())
+ .findAny()
+ .orElse(null);
+ }
+ if (userHandle == null) {
+ return null;
+ }
+ ComponentName cn = null;
+ String id = null;
+
+ switch (info.getItemCase()) {
+ case APPLICATION: {
+ LauncherAtom.Application app = info.getApplication();
+ if ((cn = parseNullable(app.getComponentName())) != null) {
+ id = "app:" + cn.getPackageName();
+ }
+ break;
+ }
+ case SHORTCUT: {
+ LauncherAtom.Shortcut si = info.getShortcut();
+ if (!TextUtils.isEmpty(si.getShortcutId())
+ && (cn = parseNullable(si.getShortcutName())) != null) {
+ id = "shortcut:" + si.getShortcutId();
+ }
+ break;
+ }
+ case WIDGET: {
+ LauncherAtom.Widget widget = info.getWidget();
+ if ((cn = parseNullable(widget.getComponentName())) != null) {
+ id = "widget:" + cn.getPackageName();
+ }
+ break;
+ }
+ case TASK: {
+ LauncherAtom.Task task = info.getTask();
+ if ((cn = parseNullable(task.getComponentName())) != null) {
+ id = "app:" + cn.getPackageName();
+ }
+ break;
+ }
+ case FOLDER_ICON: {
+ id = "folder:" + SystemClock.uptimeMillis();
+ cn = new ComponentName(mContext.getPackageName(), "#folder");
+ }
+ }
+ if (id != null && cn != null) {
+ return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle)
+ .setClassName(cn.getClassName())
+ .build();
+ }
+ return null;
}
- @UiThread
- private void sendLaunch(AppTarget target, String container) {
- sendEvent(target, container, AppTargetEvent.ACTION_LAUNCH);
+ private String getContainer(LauncherAtom.ItemInfo info) {
+ ContainerInfo ci = info.getContainerInfo();
+ switch (ci.getContainerCase()) {
+ case WORKSPACE: {
+ // In case the item type is not widgets, the spaceX and spanY default to 1.
+ int spanX = info.getWidget().getSpanX();
+ int spanY = info.getWidget().getSpanY();
+ return getWorkspaceContainerString(ci.getWorkspace(), spanX, spanY);
+ }
+ case HOTSEAT: {
+ return getHotseatContainerString(ci.getHotseat());
+ }
+ case TASK_SWITCHER_CONTAINER: {
+ return "task-switcher";
+ }
+ case ALL_APPS_CONTAINER: {
+ return "all-apps";
+ }
+ case SEARCH_RESULT_CONTAINER: {
+ return "search-results";
+ }
+ case PREDICTED_HOTSEAT_CONTAINER: {
+ return "predictions/hotseat";
+ }
+ case PREDICTION_CONTAINER: {
+ return "predictions";
+ }
+ case FOLDER: {
+ FolderContainer fc = ci.getFolder();
+ switch (fc.getParentContainerCase()) {
+ case WORKSPACE:
+ return "folder/" + getWorkspaceContainerString(fc.getWorkspace(), 1, 1);
+ case HOTSEAT:
+ return "folder/" + getHotseatContainerString(fc.getHotseat());
+ }
+ return "folder";
+ }
+ }
+ return "";
}
- @UiThread
- private void sendDismiss(AppTarget target, String container) {
- sendEvent(target, container, AppTargetEvent.ACTION_DISMISS);
+ private static String getWorkspaceContainerString(WorkspaceContainer wc, int spanX, int spanY) {
+ return String.format(Locale.ENGLISH, "workspace/%d/[%d,%d]/[%d,%d]",
+ wc.getPageIndex(), wc.getGridX(), wc.getGridY(), spanX, spanY);
}
- @Override
- public void onPluginConnected(AppLaunchEventsPlugin appLaunchEventsPlugin, Context context) {
- mAppLaunchEventsPluginsList.add(appLaunchEventsPlugin);
+ private static String getHotseatContainerString(HotseatContainer hc) {
+ return String.format(Locale.ENGLISH, "hotseat/%d", hc.getIndex());
}
- @Override
- public void onPluginDisconnected(AppLaunchEventsPlugin appLaunchEventsPlugin) {
- mAppLaunchEventsPluginsList.remove(appLaunchEventsPlugin);
+ private static ComponentName parseNullable(String componentNameString) {
+ return TextUtils.isEmpty(componentNameString)
+ ? null : ComponentName.unflattenFromString(componentNameString);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index 44691d3..e11c701 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -56,7 +56,6 @@
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
-import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -93,9 +92,6 @@
private static final Interpolator ALPHA_FACTOR_INTERPOLATOR =
(t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f;
- private static final OnClickListener PREDICTION_CLICK_LISTENER =
- ItemClickHandler.getInstance(AppLaunchTracker.CONTAINER_PREDICTIONS);
-
private final Launcher mLauncher;
private final PredictionUiStateManager mPredictionUiStateManager;
private int mNumPredictedAppsPerRow;
@@ -246,7 +242,7 @@
while (getChildCount() < mNumPredictedAppsPerRow) {
BubbleTextView icon = (BubbleTextView) inflater.inflate(
R.layout.all_apps_icon, this, false);
- icon.setOnClickListener(PREDICTION_CLICK_LISTENER);
+ icon.setOnClickListener(ItemClickHandler.INSTANCE);
icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mFocusHelper);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
index c15a596..20e1edc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
@@ -71,10 +71,9 @@
}
private PrintWriter getWriter() {
- String fName = FILE_NAME_PREFIX + (LOG_DAYS % 10);
- if (fName.equals(mFileName)) return mCurrentWriter;
-
Calendar cal = Calendar.getInstance();
+ String fName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) % 10);
+ if (fName.equals(mFileName)) return mCurrentWriter;
boolean append = false;
File logFile = new File(mLogsDir, fName);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
index 8c1db4e..9e7c9fb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
@@ -30,7 +30,6 @@
*/
public class HotseatRestoreHelper {
private final Launcher mLauncher;
- private boolean mBackupRestored = false;
HotseatRestoreHelper(Launcher context) {
mLauncher = context;
@@ -62,7 +61,6 @@
* Finds and restores a previously saved snapshow of Favorites table
*/
public void restoreBackup() {
- if (mBackupRestored) return;
MODEL_EXECUTOR.execute(() -> {
try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
LauncherSettings.Settings.call(
@@ -78,7 +76,6 @@
idp.numRows);
backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
transaction.commit();
- mBackupRestored = true;
mLauncher.getModel().forceReload();
}
});
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 7d86cea..f87138e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -34,8 +34,6 @@
import android.util.Log;
import android.view.View;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
@@ -129,12 +127,11 @@
}
@Override
- public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
- @Nullable String sourceContainer) {
+ public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
if (mHotseatPredictionController != null) {
mHotseatPredictionController.setPauseUIUpdate(true);
}
- return super.startActivitySafely(v, intent, item, sourceContainer);
+ return super.startActivitySafely(v, intent, item);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index c1a585e..e45fa9d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -159,8 +159,7 @@
builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET,
-mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- builder.addOnFrameCallback(
- () -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
+ builder.addOnFrameCallback(recentsView::redrawLiveTile);
}
} else if (mStartState == ALL_APPS) {
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 0ee5d04..37a595e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -263,8 +263,10 @@
totalDisplacement * mProgressMultiplier, 0, 1));
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- if (mRecentsView.getCurrentPage() != 0 || isGoingUp) {
- mRecentsView.redrawLiveTile(true);
+ if (mRecentsView.getCurrentPage() == 0) {
+ mRecentsView.getLiveTileTaskViewSimulator().setOffsetY(
+ isGoingUp ? totalDisplacement : 0);
+ mRecentsView.redrawLiveTile();
}
}
return true;
@@ -300,7 +302,7 @@
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> {
if (mRecentsView.getCurrentPage() != 0 || mCurrentAnimationIsGoingUp) {
- mRecentsView.redrawLiveTile(true);
+ mRecentsView.redrawLiveTile();
}
});
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
similarity index 80%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
index bf03587..5a3449b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep;
+import static android.widget.Toast.LENGTH_SHORT;
+
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
@@ -29,7 +31,9 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -51,14 +55,19 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.os.Build;
import android.os.SystemClock;
+import android.util.Log;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.ViewTreeObserver.OnDrawListener;
import android.view.WindowInsets;
import android.view.animation.Interpolator;
+import android.widget.Toast;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
@@ -69,19 +78,27 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.ShelfPeekAnim;
import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
+import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -93,17 +110,34 @@
import com.android.systemui.shared.system.TaskInfoCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
/**
* Handles the navigation gestures when Launcher is the default home activity.
- * TODO: Merge this with BaseSwipeUpHandler
*/
-@TargetApi(Build.VERSION_CODES.O)
-public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q extends RecentsView>
- extends BaseSwipeUpHandler<T, Q> implements OnApplyWindowInsetsListener {
- private static final String TAG = BaseSwipeUpHandlerV2.class.getSimpleName();
+@TargetApi(Build.VERSION_CODES.R)
+public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
+ extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
+ RecentsAnimationCallbacks.RecentsAnimationListener {
+ private static final String TAG = "AbsSwipeUpHandler";
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
+ protected final BaseActivityInterface<?, T> mActivityInterface;
+ protected final InputConsumerProxy mInputConsumerProxy;
+ protected final ActivityInitListener mActivityInitListener;
+ // Callbacks to be made once the recents animation starts
+ private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
+ protected RecentsAnimationController mRecentsAnimationController;
+ protected RecentsAnimationTargets mRecentsAnimationTargets;
+ protected T mActivity;
+ protected Q mRecentsView;
+ protected Runnable mGestureEndCallback;
+ protected MultiStateCallback mStateCallback;
+ protected boolean mCanceled;
+ private boolean mRecentsViewScrollLinked = false;
+
private static int getFlagForIndex(int index, String name) {
if (DEBUG_STATES) {
STATE_NAMES[index] = name;
@@ -199,11 +233,15 @@
private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
- public BaseSwipeUpHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
+ public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture,
InputConsumerController inputConsumer) {
- super(context, deviceState, gestureState, inputConsumer);
+ super(context, deviceState, gestureState, new TransformParams());
+ mActivityInterface = gestureState.getActivityInterface();
+ mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
+ mInputConsumerProxy =
+ new InputConsumerProxy(inputConsumer, this::createNewInputProxyHandler);
mTaskAnimationManager = taskAnimationManager;
mTouchTimeMs = touchTimeMs;
mContinuingLastGesture = continuingLastGesture;
@@ -265,10 +303,6 @@
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
this::notifyTransitionCancelled);
- mGestureState.runOnceAtState(STATE_END_TARGET_SET,
- () -> mDeviceState.onEndTargetCalculated(mGestureState.getEndTarget(),
- mActivityInterface));
-
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
| STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
@@ -276,9 +310,17 @@
}
}
- @Override
protected boolean onActivityInit(Boolean alreadyOnHome) {
- super.onActivityInit(alreadyOnHome);
+ T createdActivity = mActivityInterface.getCreatedActivity();
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1");
+ }
+ if (createdActivity != null) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
+ }
+ initTransitionEndpoints(createdActivity.getDeviceProfile());
+ }
final T activity = mActivityInterface.getCreatedActivity();
if (mActivity == activity) {
return true;
@@ -317,7 +359,9 @@
return true;
}
- @Override
+ /**
+ * Return true if the window should be translated horizontally if the recents view scrolls
+ */
protected boolean moveWindowWithRecentsScroll() {
return mGestureState.getEndTarget() != HOME;
}
@@ -330,7 +374,7 @@
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
return;
}
- mTaskViewSimulator.setRecentsConfiguration(mActivity.getResources().getConfiguration());
+ mTaskViewSimulator.setRecentsRotation(mActivity.getDisplay().getRotation());
// If we've already ended the gesture and are going home, don't prepare recents UI,
// as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
@@ -393,6 +437,11 @@
mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback(
mOnDeferredActivityLaunch);
+ mGestureState.runOnceAtState(STATE_END_TARGET_SET,
+ () -> mDeviceState.getRotationTouchHelper().
+ onEndTargetCalculated(mGestureState.getEndTarget(),
+ mActivityInterface));
+
notifyGestureStartedAsync();
}
@@ -441,7 +490,9 @@
.getHighResLoadingState().setVisible(true);
}
- @Override
+ /**
+ * Called when motion pause is detected
+ */
public void onMotionPauseChanged(boolean isPaused) {
setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
}
@@ -479,7 +530,6 @@
mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
}
- @Override
public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
}
@@ -535,11 +585,14 @@
updateLauncherTransitionProgress();
}
- @Override
public Intent getLaunchIntent() {
return mGestureState.getOverviewIntent();
}
+ /**
+ * Called when the value of {@link #mCurrentShift} changes
+ */
+ @UiThread
@Override
public void updateFinalShift() {
final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
@@ -600,7 +653,41 @@
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
- super.onRecentsAnimationStart(controller, targets);
+ mRecentsAnimationController = controller;
+ mRecentsAnimationTargets = targets;
+ mTransformParams.setTargetSet(mRecentsAnimationTargets);
+ RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
+ mGestureState.getRunningTaskId());
+
+ if (runningTaskTarget != null) {
+ mTaskViewSimulator.setPreview(runningTaskTarget);
+ }
+
+ // 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()) {
+ for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
+ action.run();
+ }
+ mRecentsAnimationStartCallbacks.clear();
+ }
// Only add the callback to enable the input consumer after we actually have the controller
mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
@@ -617,10 +704,14 @@
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
// Defer clearing the controller and the targets until after we've updated the state
- super.onRecentsAnimationCanceled(thumbnailData);
+ mRecentsAnimationController = null;
+ mRecentsAnimationTargets = null;
+ if (mRecentsView != null) {
+ mRecentsView.setRecentsAnimationTargets(null, null);
+ }
}
- @Override
+ @UiThread
public void onGestureStarted(boolean isLikelyToStartNewTask) {
notifyGestureStartedAsync();
setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
@@ -644,7 +735,7 @@
/**
* Called as a result on ACTION_CANCEL to return the UI to the start state.
*/
- @Override
+ @UiThread
public void onGestureCancelled() {
updateDisplacement(0);
mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
@@ -657,7 +748,7 @@
* @param velocity The x and y components of the velocity when the gesture ends.
* @param downPos The x and y value of where the gesture started.
*/
- @Override
+ @UiThread
public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
@@ -675,7 +766,10 @@
handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
}
- @Override
+ /**
+ * Called to create a input proxy for the running task
+ */
+ @UiThread
protected InputConsumer createNewInputProxyHandler() {
endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
endLauncherTransitionController();
@@ -716,7 +810,7 @@
ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget());
}
- @Override
+ /** @return Whether this was the task we were waiting to appear, and thus handled it. */
protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
return false;
@@ -837,11 +931,9 @@
}
}
- if (endTarget.isLauncher && mRecentsAnimationController != null) {
- mRecentsAnimationController.enableInputProxy(mInputConsumer,
- this::createNewInputProxyHandler);
+ if (endTarget.isLauncher) {
+ mInputConsumerProxy.enable();
}
-
if (endTarget == HOME) {
setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
@@ -876,22 +968,7 @@
animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
}
- private void doLogGesture(GestureEndTarget endTarget) {
- DeviceProfile dp = mDp;
- if (dp == null || mDownPos == null) {
- // We probably never received an animation controller, skip logging.
- return;
- }
-
- int pageIndex = endTarget == LAST_TASK
- ? LOG_NO_OP_PAGE_INDEX
- : mRecentsView.getNextPage();
- UserEventDispatcher.newInstance(mContext).logStateChangeAction(
- mLogAction, mLogDirection,
- (int) mDownPos.x, (int) mDownPos.y,
- ContainerType.NAVBAR, ContainerType.APP,
- endTarget.containerType,
- pageIndex);
+ private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
StatsLogManager.EventEnum event;
switch (endTarget) {
case HOME:
@@ -909,10 +986,29 @@
default:
event = IGNORE;
}
- StatsLogManager.newInstance(mContext).logger()
+ StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
.withSrcState(LAUNCHER_STATE_BACKGROUND)
- .withDstState(StatsLogManager.containerTypeToAtomState(endTarget.containerType))
- .log(event);
+ .withDstState(StatsLogManager.containerTypeToAtomState(endTarget.containerType));
+ if (targetTask != null) {
+ logger.withItemInfo(targetTask.getItemInfo());
+ }
+ logger.log(event);
+
+
+ DeviceProfile dp = mDp;
+ if (dp == null || mDownPos == null) {
+ // We probably never received an animation controller, skip logging.
+ return;
+ }
+ int pageIndex = endTarget == LAST_TASK
+ ? LOG_NO_OP_PAGE_INDEX
+ : mRecentsView.getNextPage();
+ UserEventDispatcher.newInstance(mContext).logStateChangeAction(
+ mLogAction, mLogDirection,
+ (int) mDownPos.x, (int) mDownPos.y,
+ ContainerType.NAVBAR, ContainerType.APP,
+ endTarget.containerType,
+ pageIndex);
}
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
@@ -1076,6 +1172,7 @@
anim.addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
if (mActivity != null) {
removeLiveTileOverlay();
}
@@ -1091,10 +1188,12 @@
mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
}
});
+ if (mRecentsAnimationTargets != null) {
+ mRecentsAnimationTargets.addReleaseCheck(anim);
+ }
return anim;
}
- @Override
public void onConsumerAboutToBeSwitched() {
if (mActivity != null) {
// In the off chance that the gesture ends before Launcher is started, we should clear
@@ -1117,7 +1216,7 @@
private void resumeLastTask() {
mRecentsAnimationController.finish(false /* toRecents */, null);
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
- doLogGesture(LAST_TASK);
+ doLogGesture(LAST_TASK, null);
reset();
}
@@ -1132,6 +1231,7 @@
@UiThread
private void startNewTaskInternal() {
+ TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
startNewTask(success -> {
if (!success) {
reset();
@@ -1140,13 +1240,23 @@
endLauncherTransitionController();
updateSysUiFlags(1 /* windowProgress == overview */);
}
- doLogGesture(NEW_TASK);
+ doLogGesture(NEW_TASK, taskToLaunch);
});
}
- @Override
+ /**
+ * Called when we successfully startNewTask() on the task that was previously running. Normally
+ * we call resumeLastTask() when returning to the previously running task, but this handles a
+ * specific edge case: if we switch from A to B, and back to A before B appears, we need to
+ * start A again to ensure it stays on top.
+ */
+ @androidx.annotation.CallSuper
protected void onRestartPreviouslyAppearedTask() {
- super.onRestartPreviouslyAppearedTask();
+ // Finish the controller here, since we won't get onTaskAppeared() for a task that already
+ // appeared.
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.finish(false, null);
+ }
reset();
}
@@ -1168,6 +1278,7 @@
}
private void invalidateHandler() {
+ mInputConsumerProxy.destroy();
endRunningWindowAnim(false /* cancel */);
if (mGestureEndCallback != null) {
@@ -1250,7 +1361,7 @@
// new thumbnail
finishTransitionPosted = ViewUtils.postDraw(taskView,
() -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
- this::isCanceled);
+ this::isCanceled);
}
}
if (!finishTransitionPosted) {
@@ -1285,7 +1396,7 @@
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
- doLogGesture(HOME);
+ doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
}
protected abstract void finishRecentsControllerToHome(Runnable callback);
@@ -1300,7 +1411,7 @@
mRecentsView.onSwipeUpAnimationSuccess();
SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
- doLogGesture(RECENTS);
+ doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
reset();
}
@@ -1319,4 +1430,172 @@
return app.isNotInRecents
|| app.activityType == ACTIVITY_TYPE_HOME;
}
+
+ /**
+ * To be called at the end of constructor of subclasses. This calls various methods which can
+ * depend on proper class initialization.
+ */
+ protected void initAfterSubclassConstructor() {
+ initTransitionEndpoints(
+ mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
+ }
+
+ protected void performHapticFeedback() {
+ VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+ }
+
+ public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
+ return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
+ }
+
+ public void setGestureEndCallback(Runnable gestureEndCallback) {
+ mGestureEndCallback = gestureEndCallback;
+ }
+
+ protected void linkRecentsViewScroll() {
+ SurfaceTransactionApplier.create(mRecentsView, applier -> {
+ mTransformParams.setSyncTransactionApplier(applier);
+ runOnRecentsAnimationStart(() ->
+ mRecentsAnimationTargets.addReleaseCheck(applier));
+ });
+
+ mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+ if (moveWindowWithRecentsScroll()) {
+ updateFinalShift();
+ }
+ });
+ runOnRecentsAnimationStart(() ->
+ mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
+ mRecentsAnimationTargets));
+ mRecentsViewScrollLinked = true;
+ }
+
+ protected void startNewTask(Consumer<Boolean> resultCallback) {
+ // Launch the task user scrolled to (mRecentsView.getNextPage()).
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ // We finish recents animation inside launchTask() when live tile is enabled.
+ mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
+ true /* freezeTaskList */);
+ } else {
+ if (!mCanceled) {
+ TaskView nextTask = mRecentsView.getNextPageTaskView();
+ if (nextTask != null) {
+ int taskId = nextTask.getTask().key.id;
+ mGestureState.updateLastStartedTaskId(taskId);
+ boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
+ .contains(taskId);
+ nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
+ success -> {
+ resultCallback.accept(success);
+ if (success) {
+ if (hasTaskPreviouslyAppeared) {
+ onRestartPreviouslyAppearedTask();
+ }
+ } else {
+ mActivityInterface.onLaunchTaskFailed();
+ nextTask.notifyTaskLaunchFailed(TAG);
+ mRecentsAnimationController.finish(true /* toRecents */, null);
+ }
+ }, MAIN_EXECUTOR.getHandler());
+ } else {
+ mActivityInterface.onLaunchTaskFailed();
+ Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
+ mRecentsAnimationController.finish(true /* toRecents */, null);
+ }
+ }
+ mCanceled = false;
+ }
+ }
+
+ /**
+ * Runs the given {@param action} if the recents animation has already started, or queues it to
+ * be run when it is next started.
+ */
+ protected void runOnRecentsAnimationStart(Runnable action) {
+ if (mRecentsAnimationTargets == null) {
+ mRecentsAnimationStartCallbacks.add(action);
+ } else {
+ action.run();
+ }
+ }
+
+ /**
+ * TODO can we remove this now that we don't finish the controller until onTaskAppeared()?
+ * @return whether the recents animation has started and there are valid app targets.
+ */
+ protected boolean hasTargets() {
+ return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
+ }
+
+ @Override
+ public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ mRecentsAnimationController = null;
+ mRecentsAnimationTargets = null;
+ if (mRecentsView != null) {
+ mRecentsView.setRecentsAnimationTargets(null, null);
+ }
+ }
+
+ @Override
+ public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ if (mRecentsAnimationController != null) {
+ if (handleTaskAppeared(appearedTaskTarget)) {
+ mRecentsAnimationController.finish(false /* toRecents */,
+ null /* onFinishComplete */);
+ mActivityInterface.onLaunchTaskSuccess();
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+ }
+ }
+ }
+
+ /**
+ * @return The index of the TaskView in RecentsView whose taskId matches the task that will
+ * resume if we finish the controller.
+ */
+ protected int getLastAppearedTaskIndex() {
+ return mGestureState.getLastAppearedTaskId() != -1
+ ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
+ : mRecentsView.getRunningTaskIndex();
+ }
+
+ /**
+ * @return Whether we are continuing a gesture that already landed on a new task,
+ * but before that task appeared.
+ */
+ protected boolean hasStartedNewTask() {
+ return mGestureState.getLastStartedTaskId() != -1;
+ }
+
+ /**
+ * Registers a callback to run when the activity is ready.
+ * @param intent The intent that will be used to start the activity if it doesn't exist already.
+ */
+ public void initWhenReady(Intent intent) {
+ // Preload the plan
+ RecentsModel.INSTANCE.get(mContext).getTasks(null);
+
+ mActivityInitListener.register(intent);
+ }
+
+ /**
+ * Applies the transform on the recents animation
+ */
+ protected void applyWindowTransform() {
+ if (mWindowTransitionController != null) {
+ float progress = mCurrentShift.value / mDragLengthFactor;
+ mWindowTransitionController.setPlayFraction(progress);
+ }
+ if (mRecentsAnimationTargets != null) {
+ if (mRecentsViewScrollLinked) {
+ mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
+ }
+ mTaskViewSimulator.apply(mTransformParams);
+ }
+ }
+
+ public interface Factory {
+
+ AbsSwipeUpHandler<StatefulActivity<?>, RecentsView> newHandler(
+ GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
deleted file mode 100644
index a63f3a8..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * Copyright (C) 2019 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;
-
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.Build;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.VibratorWrapper;
-import com.android.launcher3.util.WindowBounds;
-import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
-import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.SurfaceTransactionApplier;
-import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * Base class for swipe up handler with some utility methods
- */
-@TargetApi(Build.VERSION_CODES.Q)
-public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
- extends SwipeUpAnimationLogic implements RecentsAnimationListener {
-
- private static final String TAG = "BaseSwipeUpHandler";
-
- protected final BaseActivityInterface<?, T> mActivityInterface;
- protected final InputConsumerController mInputConsumer;
-
- protected final ActivityInitListener mActivityInitListener;
-
- protected RecentsAnimationController mRecentsAnimationController;
- protected RecentsAnimationTargets mRecentsAnimationTargets;
-
- // Callbacks to be made once the recents animation starts
- private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
-
- protected T mActivity;
- protected Q mRecentsView;
-
- protected Runnable mGestureEndCallback;
-
- protected MultiStateCallback mStateCallback;
-
- protected boolean mCanceled;
-
- private boolean mRecentsViewScrollLinked = false;
-
- protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
- GestureState gestureState, InputConsumerController inputConsumer) {
- super(context, deviceState, gestureState, new TransformParams());
- mActivityInterface = gestureState.getActivityInterface();
- mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
- mInputConsumer = inputConsumer;
- }
-
- /**
- * To be called at the end of constructor of subclasses. This calls various methods which can
- * depend on proper class initialization.
- */
- protected void initAfterSubclassConstructor() {
- initTransitionEndpoints(
- mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
- }
-
- protected void performHapticFeedback() {
- VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
- }
-
- public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
- return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
- }
-
- public void setGestureEndCallback(Runnable gestureEndCallback) {
- mGestureEndCallback = gestureEndCallback;
- }
-
- public abstract Intent getLaunchIntent();
-
- protected void linkRecentsViewScroll() {
- SurfaceTransactionApplier.create(mRecentsView, applier -> {
- mTransformParams.setSyncTransactionApplier(applier);
- runOnRecentsAnimationStart(() ->
- mRecentsAnimationTargets.addReleaseCheck(applier));
- });
-
- mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
- if (moveWindowWithRecentsScroll()) {
- updateFinalShift();
- }
- });
- runOnRecentsAnimationStart(() ->
- mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
- mRecentsAnimationTargets));
- mRecentsViewScrollLinked = true;
- }
-
- protected void startNewTask(Consumer<Boolean> resultCallback) {
- // Launch the task user scrolled to (mRecentsView.getNextPage()).
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- // We finish recents animation inside launchTask() when live tile is enabled.
- mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
- true /* freezeTaskList */);
- } else {
- int taskId = mRecentsView.getNextPageTaskView().getTask().key.id;
- if (!mCanceled) {
- TaskView nextTask = mRecentsView.getTaskView(taskId);
- if (nextTask != null) {
- mGestureState.updateLastStartedTaskId(taskId);
- boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
- .contains(taskId);
- nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
- success -> {
- resultCallback.accept(success);
- if (success) {
- if (hasTaskPreviouslyAppeared) {
- onRestartPreviouslyAppearedTask();
- }
- } else {
- mActivityInterface.onLaunchTaskFailed();
- nextTask.notifyTaskLaunchFailed(TAG);
- mRecentsAnimationController.finish(true /* toRecents */, null);
- }
- }, MAIN_EXECUTOR.getHandler());
- }
- }
- mCanceled = false;
- }
- }
-
- /**
- * Called when we successfully startNewTask() on the task that was previously running. Normally
- * we call resumeLastTask() when returning to the previously running task, but this handles a
- * specific edge case: if we switch from A to B, and back to A before B appears, we need to
- * start A again to ensure it stays on top.
- */
- @CallSuper
- protected void onRestartPreviouslyAppearedTask() {
- // Finish the controller here, since we won't get onTaskAppeared() for a task that already
- // appeared.
- if (mRecentsAnimationController != null) {
- mRecentsAnimationController.finish(false, null);
- }
- }
-
- /**
- * Runs the given {@param action} if the recents animation has already started, or queues it to
- * be run when it is next started.
- */
- protected void runOnRecentsAnimationStart(Runnable action) {
- if (mRecentsAnimationTargets == null) {
- mRecentsAnimationStartCallbacks.add(action);
- } else {
- action.run();
- }
- }
-
- /**
- * TODO can we remove this now that we don't finish the controller until onTaskAppeared()?
- * @return whether the recents animation has started and there are valid app targets.
- */
- protected boolean hasTargets() {
- return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
- }
-
- @Override
- public void onRecentsAnimationStart(RecentsAnimationController recentsAnimationController,
- RecentsAnimationTargets targets) {
- mRecentsAnimationController = recentsAnimationController;
- mRecentsAnimationTargets = targets;
- mTransformParams.setTargetSet(mRecentsAnimationTargets);
- RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
- mGestureState.getRunningTaskId());
-
- if (runningTaskTarget != null) {
- mTaskViewSimulator.setPreview(runningTaskTarget);
- }
-
- // 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()) {
- for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
- action.run();
- }
- mRecentsAnimationStartCallbacks.clear();
- }
- }
-
- @Override
- public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
- mRecentsAnimationController = null;
- mRecentsAnimationTargets = null;
- if (mRecentsView != null) {
- mRecentsView.setRecentsAnimationTargets(null, null);
- }
- }
-
- @Override
- public void onRecentsAnimationFinished(RecentsAnimationController controller) {
- mRecentsAnimationController = null;
- mRecentsAnimationTargets = null;
- if (mRecentsView != null) {
- mRecentsView.setRecentsAnimationTargets(null, null);
- }
- }
-
- @Override
- public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
- if (mRecentsAnimationController != null) {
- if (handleTaskAppeared(appearedTaskTarget)) {
- mRecentsAnimationController.finish(false /* toRecents */,
- null /* onFinishComplete */);
- mActivityInterface.onLaunchTaskSuccess();
- ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
- }
- }
- }
-
- /** @return Whether this was the task we were waiting to appear, and thus handled it. */
- protected abstract boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget);
-
- /**
- * @return The index of the TaskView in RecentsView whose taskId matches the task that will
- * resume if we finish the controller.
- */
- protected int getLastAppearedTaskIndex() {
- return mGestureState.getLastAppearedTaskId() != -1
- ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
- : mRecentsView.getRunningTaskIndex();
- }
-
- /**
- * @return Whether we are continuing a gesture that already landed on a new task,
- * but before that task appeared.
- */
- protected boolean hasStartedNewTask() {
- return mGestureState.getLastStartedTaskId() != -1;
- }
-
- /**
- * Return true if the window should be translated horizontally if the recents view scrolls
- */
- protected abstract boolean moveWindowWithRecentsScroll();
-
- protected boolean onActivityInit(Boolean alreadyOnHome) {
- T createdActivity = mActivityInterface.getCreatedActivity();
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1");
- }
- if (createdActivity != null) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
- }
- initTransitionEndpoints(createdActivity.getDeviceProfile());
- }
- return true;
- }
-
- /**
- * Called to create a input proxy for the running task
- */
- @UiThread
- protected abstract InputConsumer createNewInputProxyHandler();
-
- /**
- * Called when the value of {@link #mCurrentShift} changes
- */
- @UiThread
- public abstract void updateFinalShift();
-
- /**
- * Called when motion pause is detected
- */
- public abstract void onMotionPauseChanged(boolean isPaused);
-
- @UiThread
- public void onGestureStarted(boolean isLikelyToStartNewTask) { }
-
- @UiThread
- public abstract void onGestureCancelled();
-
- @UiThread
- public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
-
- public abstract void onConsumerAboutToBeSwitched();
-
- public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
-
- /**
- * Registers a callback to run when the activity is ready.
- * @param intent The intent that will be used to start the activity if it doesn't exist already.
- */
- public void initWhenReady(Intent intent) {
- // Preload the plan
- RecentsModel.INSTANCE.get(mContext).getTasks(null);
-
- mActivityInitListener.register(intent);
- }
-
- /**
- * Applies the transform on the recents animation
- */
- protected void applyWindowTransform() {
- if (mWindowTransitionController != null) {
- float progress = mCurrentShift.value / mDragLengthFactor;
- mWindowTransitionController.setPlayFraction(progress);
- }
- if (mRecentsAnimationTargets != null) {
- if (mRecentsViewScrollLinked) {
- mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
- }
- mTaskViewSimulator.apply(mTransformParams);
- }
- }
-
- @Override
- protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
- HomeAnimationFactory homeAnimationFactory) {
- RectFSpringAnim anim =
- super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
- if (mRecentsAnimationTargets != null) {
- mRecentsAnimationTargets.addReleaseCheck(anim);
- }
- return anim;
- }
-
- public interface Factory {
-
- BaseSwipeUpHandler newHandler(
- GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index 33b9cde..d1da0c1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -140,7 +140,7 @@
}
@Override
- public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) {
+ public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
// no-op, fake landscape not supported for 3P
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index fc7a119..ffb05df 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -15,14 +15,35 @@
*/
package com.android.quickstep;
+import static android.content.Intent.EXTRA_COMPONENT_NAME;
+import static android.content.Intent.EXTRA_USER;
+
+import static com.android.launcher3.GestureNavContract.EXTRA_GESTURE_CONTRACT;
+import static com.android.launcher3.GestureNavContract.EXTRA_ICON_POSITION;
+import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE;
+import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelUuid;
+import android.os.UserHandle;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import androidx.annotation.NonNull;
@@ -32,18 +53,32 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+import java.lang.ref.WeakReference;
+import java.util.UUID;
+import java.util.function.Consumer;
+
/**
* Handles the navigation gestures when a 3rd party launcher is the default home activity.
*/
+@TargetApi(Build.VERSION_CODES.R)
public class FallbackSwipeHandler extends
- BaseSwipeUpHandlerV2<RecentsActivity, FallbackRecentsView> {
+ AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
+
+ /**
+ * Message used for receiving gesture nav contract information. We use a static messenger to
+ * avoid leaking too make binders in case the receiving launcher does not handle the contract
+ * properly.
+ */
+ private static StaticMessageReceiver sMessageReceiver = null;
private FallbackHomeAnimationFactory mActiveAnimationFactory;
private final boolean mRunningOverHome;
@@ -89,7 +124,9 @@
protected HomeAnimationFactory createHomeAnimationFactory(long duration) {
mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
- mContext.startActivity(new Intent(mGestureState.getHomeIntent()), options.toBundle());
+ Intent intent = new Intent(mGestureState.getHomeIntent());
+ mActiveAnimationFactory.addGestureContract(intent);
+ mContext.startActivity(intent, options.toBundle());
return mActiveAnimationFactory;
}
@@ -130,17 +167,20 @@
}
private class FallbackHomeAnimationFactory extends HomeAnimationFactory {
-
+ private final Rect mTempRect = new Rect();
private final TransformParams mHomeAlphaParams = new TransformParams();
private final AnimatedFloat mHomeAlpha;
private final AnimatedFloat mVerticalShiftForScale = new AnimatedFloat();
-
private final AnimatedFloat mRecentsAlpha = new AnimatedFloat();
+ private final RectF mTargetRect = new RectF();
+ private SurfaceControl mSurfaceControl;
+
private final long mDuration;
+
+ private RectFSpringAnim mSpringAnim;
FallbackHomeAnimationFactory(long duration) {
- super(null);
mDuration = duration;
if (mRunningOverHome) {
@@ -162,6 +202,15 @@
this::updateRecentsActivityTransformDuringHomeAnim);
}
+ @NonNull
+ @Override
+ public RectF getWindowTargetRect() {
+ if (mTargetRect.isEmpty()) {
+ mTargetRect.set(super.getWindowTargetRect());
+ }
+ return mTargetRect;
+ }
+
private void updateRecentsActivityTransformDuringHomeAnim(SurfaceParams.Builder builder,
RemoteAnimationTargetCompat app, TransformParams params) {
builder.withAlpha(mRecentsAlpha.value);
@@ -218,5 +267,87 @@
.start();
}
}
+
+ @Override
+ public void setAnimation(RectFSpringAnim anim) {
+ mSpringAnim = anim;
+ }
+
+ private void onMessageReceived(Message msg) {
+ try {
+ Bundle data = msg.getData();
+ RectF position = data.getParcelable(EXTRA_ICON_POSITION);
+ if (!position.isEmpty()) {
+ mSurfaceControl = data.getParcelable(EXTRA_ICON_SURFACE);
+ mTargetRect.set(position);
+ if (mSpringAnim != null) {
+ mSpringAnim.onTargetPositionChanged();
+ }
+ }
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+
+ @Override
+ public void update(RectF currentRect, float progress, float radius) {
+ if (mSurfaceControl != null) {
+ currentRect.roundOut(mTempRect);
+ Transaction t = new Transaction();
+ t.setGeometry(mSurfaceControl, null, mTempRect, Surface.ROTATION_0);
+ t.apply();
+ }
+ }
+
+ private void addGestureContract(Intent intent) {
+ if (mRunningOverHome || mGestureState.getRunningTask() == null) {
+ return;
+ }
+
+ TaskKey key = new TaskKey(mGestureState.getRunningTask());
+ if (key.getComponent() != null) {
+ if (sMessageReceiver == null) {
+ sMessageReceiver = new StaticMessageReceiver();
+ }
+
+ Bundle gestureNavContract = new Bundle();
+ gestureNavContract.putParcelable(EXTRA_COMPONENT_NAME, key.getComponent());
+ gestureNavContract.putParcelable(EXTRA_USER, UserHandle.of(key.userId));
+ gestureNavContract.putParcelable(EXTRA_REMOTE_CALLBACK,
+ sMessageReceiver.newCallback(this::onMessageReceived));
+ intent.putExtra(EXTRA_GESTURE_CONTRACT, gestureNavContract);
+ }
+ }
+ }
+
+ private static class StaticMessageReceiver implements Handler.Callback {
+
+ private final Messenger mMessenger =
+ new Messenger(new Handler(Looper.getMainLooper(), this));
+
+ private ParcelUuid mCurrentUID = new ParcelUuid(UUID.randomUUID());
+ private WeakReference<Consumer<Message>> mCurrentCallback = new WeakReference<>(null);
+
+ public Message newCallback(Consumer<Message> callback) {
+ mCurrentUID = new ParcelUuid(UUID.randomUUID());
+ mCurrentCallback = new WeakReference<>(callback);
+
+ Message msg = Message.obtain();
+ msg.replyTo = mMessenger;
+ msg.obj = mCurrentUID;
+ return msg;
+ }
+
+ @Override
+ public boolean handleMessage(@NonNull Message message) {
+ if (mCurrentUID.equals(message.obj)) {
+ Consumer<Message> consumer = mCurrentCallback.get();
+ if (consumer != null) {
+ consumer.accept(message);
+ return true;
+ }
+ }
+ return false;
+ }
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 6621b41..b020355 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -105,7 +105,7 @@
// recents, we assume the first task is invisible, making translation off by one task.
launcher.getStateManager().reapplyState();
launcher.getRootView().setForceHideBackArrow(false);
- notifyRecentsOfOrientation(deviceState);
+ notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
}
@Override
@@ -120,7 +120,7 @@
@Override
public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
- notifyRecentsOfOrientation(deviceState);
+ notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) {
@Override
public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
@@ -228,7 +228,7 @@
@Override
- public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) {
+ public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
final StateManager<LauncherState> stateManager = getCreatedActivity().getStateManager();
stateManager.addStateListener(
new StateManager.StateListener<LauncherState>() {
@@ -244,11 +244,11 @@
});
}
- private void notifyRecentsOfOrientation(RecentsAnimationDeviceState deviceState) {
+ private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
// reset layout on swipe to home
RecentsView recentsView = getCreatedActivity().getOverviewPanel();
- recentsView.setLayoutRotation(deviceState.getCurrentActiveRotation(),
- deviceState.getDisplayRotation());
+ recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
+ rotationTouchHelper.getDisplayRotation());
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index fa7d268..4411455 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -16,6 +16,7 @@
package com.android.quickstep;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import android.animation.AnimatorSet;
import android.content.Context;
@@ -28,6 +29,7 @@
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.views.FloatingIconView;
+import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -37,7 +39,7 @@
* Temporary class to allow easier refactoring
*/
public class LauncherSwipeHandlerV2 extends
- BaseSwipeUpHandlerV2<BaseQuickstepLauncher, RecentsView> {
+ AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView> {
public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
@@ -72,36 +74,39 @@
mActivity.getRootView().setForceHideBackArrow(true);
mActivity.setHintUserWillBeActive();
- homeAnimFactory = new HomeAnimationFactory(floatingIconView) {
-
- @Override
- public RectF getWindowTargetRect() {
- if (canUseWorkspaceView) {
+ if (canUseWorkspaceView) {
+ // We want the window alpha to be 0 once this threshold is met, so that the
+ // FolderIconView can be seen morphing into the icon shape.
+ float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
+ homeAnimFactory = new LauncherHomeAnimationFactory() {
+ @Override
+ public RectF getWindowTargetRect() {
return iconLocation;
- } else {
- return super.getWindowTargetRect();
}
- }
- @NonNull
- @Override
- public AnimatorPlaybackController createActivityAnimationToHome() {
- // Return an empty APC here since we have an non-user controlled animation
- // to home.
- long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
- return mActivity.getStateManager().createAnimationToNewWorkspace(
- NORMAL, accuracy, 0 /* animComponents */);
- }
+ @Override
+ public void setAnimation(RectFSpringAnim anim) {
+ anim.addAnimatorListener(floatingIconView);
+ floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
+ floatingIconView.setFastFinishRunnable(anim::end);
+ }
- @Override
- public void playAtomicAnimation(float velocity) {
- new StaggeredWorkspaceAnim(mActivity, velocity,
- true /* animateOverviewScrim */).start();
- }
- };
+ @Override
+ public void update(RectF currentRect, float progress, float radius) {
+ floatingIconView.update(currentRect, 1f, progress, windowAlphaThreshold,
+ radius, false);
+ }
+ @Override
+ public void onCancel() {
+ floatingIconView.fastFinish();
+ }
+ };
+ } else {
+ homeAnimFactory = new LauncherHomeAnimationFactory();
+ }
} else {
- homeAnimFactory = new HomeAnimationFactory(null) {
+ homeAnimFactory = new HomeAnimationFactory() {
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
@@ -118,4 +123,22 @@
mRecentsAnimationController.finish(
true /* toRecents */, callback, true /* sendUserLeaveHint */);
}
+
+ private class LauncherHomeAnimationFactory extends HomeAnimationFactory {
+ @NonNull
+ @Override
+ public AnimatorPlaybackController createActivityAnimationToHome() {
+ // Return an empty APC here since we have an non-user controlled animation
+ // to home.
+ long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
+ return mActivity.getStateManager().createAnimationToNewWorkspace(
+ NORMAL, accuracy, 0 /* animComponents */);
+ }
+
+ @Override
+ public void playAtomicAnimation(float velocity) {
+ new StaggeredWorkspaceAnim(mActivity, velocity,
+ true /* animateOverviewScrim */).start();
+ }
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
index dc8f1c5..07faab7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import android.animation.Animator;
import android.content.Context;
@@ -28,7 +27,6 @@
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
@@ -37,7 +35,6 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
@@ -85,7 +82,8 @@
mTransformParams = transformParams;
mTaskViewSimulator.setLayoutRotation(
- mDeviceState.getCurrentActiveRotation(), mDeviceState.getDisplayRotation());
+ mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
+ mDeviceState.getRotationTouchHelper().getDisplayRotation());
}
protected void initTransitionEndpoints(DeviceProfile dp) {
@@ -148,12 +146,6 @@
protected abstract class HomeAnimationFactory {
- public FloatingIconView mIconView;
-
- public HomeAnimationFactory(@Nullable FloatingIconView iconView) {
- mIconView = iconView;
- }
-
public @NonNull RectF getWindowTargetRect() {
PagedOrientationHandler orientationHandler = getOrientationHandler();
DeviceProfile dp = mDp;
@@ -174,6 +166,12 @@
public void playAtomicAnimation(float velocity) {
// No-op
}
+
+ public void setAnimation(RectFSpringAnim anim) { }
+
+ public void update(RectF currentRect, float progress, float radius) { }
+
+ public void onCancel() { }
}
/**
@@ -184,8 +182,6 @@
protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
- final FloatingIconView fiv = homeAnimationFactory.mIconView;
- final boolean isFloatingIconView = fiv != null;
mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor);
mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
@@ -203,11 +199,7 @@
windowToHomePositionMap.mapRect(startRect);
RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext);
- if (isFloatingIconView) {
- anim.addAnimatorListener(fiv);
- fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
- fiv.setFastFinishRunnable(anim::end);
- }
+ homeAnimationFactory.setAnimation(anim);
SpringAnimationRunner runner = new SpringAnimationRunner(
homeAnimationFactory, cropRectF, homeToWindowPositionMap);
@@ -242,32 +234,27 @@
final RectF mWindowCurrentRect = new RectF();
final Matrix mHomeToWindowPositionMap;
+ final HomeAnimationFactory mAnimationFactory;
- final FloatingIconView mFIV;
final AnimatorPlaybackController mHomeAnim;
final RectF mCropRectF;
final float mStartRadius;
final float mEndRadius;
- final float mWindowAlphaThreshold;
SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
Matrix homeToWindowPositionMap) {
+ mAnimationFactory = factory;
mHomeAnim = factory.createActivityAnimationToHome();
mCropRectF = cropRectF;
mHomeToWindowPositionMap = homeToWindowPositionMap;
cropRectF.roundOut(mCropRect);
- mFIV = factory.mIconView;
// End on a "round-enough" radius so that the shape reveal doesn't have to do too much
// rounding at the end of the animation.
mStartRadius = mTaskViewSimulator.getCurrentCornerRadius();
mEndRadius = cropRectF.width() / 2f;
-
- // We want the window alpha to be 0 once this threshold is met, so that the
- // FolderIconView can be seen morphing into the icon shape.
- mWindowAlphaThreshold = mFIV != null ? 1f - SHAPE_PROGRESS_DURATION : 1f;
}
@Override
@@ -282,10 +269,7 @@
.setCornerRadius(cornerRadius);
mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
- if (mFIV != null) {
- mFIV.update(currentRect, 1f, progress,
- mWindowAlphaThreshold, mMatrix.mapRadius(cornerRadius), false);
- }
+ mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
}
@Override
@@ -298,13 +282,12 @@
@Override
public void onCancel() {
- if (mFIV != null) {
- mFIV.fastFinish();
- }
+ mAnimationFactory.onCancel();
}
@Override
public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
mHomeAnim.dispatchOnStart();
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index e9614d1..db512fa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -19,6 +19,7 @@
import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
import android.annotation.SuppressLint;
@@ -146,26 +147,29 @@
*/
public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
boolean rotated) {
- final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
+ getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null);
- getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
+ if (thumbnail != null) {
+ getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
+ final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
- getActionsView().setCallbacks(new OverlayUICallbacks() {
- @Override
- public void onShare() {
- if (isAllowedByPolicy) {
- mImageApi.startShareActivity();
- } else {
- showBlockedByPolicyMessage();
+ getActionsView().setCallbacks(new OverlayUICallbacks() {
+ @Override
+ public void onShare() {
+ if (isAllowedByPolicy) {
+ mImageApi.startShareActivity();
+ } else {
+ showBlockedByPolicyMessage();
+ }
}
- }
- @SuppressLint("NewApi")
- @Override
- public void onScreenshot() {
- saveScreenshot(task);
- }
- });
+ @SuppressLint("NewApi")
+ @Override
+ public void onScreenshot() {
+ saveScreenshot(task);
+ }
+ });
+ }
}
/**
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 6e0b517..1012ab2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -58,10 +58,10 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestLogging;
@@ -75,6 +75,7 @@
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
import com.android.quickstep.inputconsumers.AssistantInputConsumer;
import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
+import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
import com.android.quickstep.inputconsumers.OverscrollInputConsumer;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -258,6 +259,7 @@
private static boolean sConnected = false;
private static boolean sIsInitialized = false;
+ private RotationTouchHelper mRotationTouchHelper;
public static boolean isConnected() {
return sConnected;
@@ -267,9 +269,9 @@
return sIsInitialized;
}
- private final BaseSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
+ private final AbsSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
this::createLauncherSwipeHandler;
- private final BaseSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
+ private final AbsSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
this::createFallbackSwipeHandler;
private ActivityManagerWrapper mAM;
@@ -297,7 +299,9 @@
mAM = ActivityManagerWrapper.getInstance();
mDeviceState = new RecentsAnimationDeviceState(this);
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
+ mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
+ mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
ProtoTracer.INSTANCE.get(this).add(this);
sConnected = true;
@@ -326,7 +330,7 @@
mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
mMainChoreographer, this::onInputEvent);
- mDeviceState.updateGestureTouchRegions();
+ mRotationTouchHelper.updateGestureTouchRegions();
}
/**
@@ -337,6 +341,13 @@
resetHomeBounceSeenOnQuickstepEnabledFirstTime();
}
+ /**
+ * Called when the one handed mode overlay package changes, to recreate touch region.
+ */
+ private void onOneHandedModeOverlayChanged(int newGesturalHeight) {
+ initInputMonitor();
+ }
+
@UiThread
public void onUserUnlocked() {
mTaskAnimationManager = new TaskAnimationManager();
@@ -470,9 +481,9 @@
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "TouchInteractionService.onInputEvent:DOWN");
}
- mDeviceState.setOrientationTransformIfNeeded(event);
+ mRotationTouchHelper.setOrientationTransformIfNeeded(event);
- if (mDeviceState.isInSwipeUpTouchRegion(event)) {
+ if (mRotationTouchHelper.isInSwipeUpTouchRegion(event)) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SWIPE_TO_HOME,
"TouchInteractionService.onInputEvent:isInSwipeUpTouchRegion");
@@ -499,6 +510,11 @@
mGestureState,
InputConsumer.NO_OP, mInputMonitorCompat,
mOverviewComponentObserver.assistantGestureIsConstrained());
+ } else if (mDeviceState.canTriggerOneHandedAction(event)
+ && !mDeviceState.isOneHandedModeActive()) {
+ // Consume gesture event for triggering one handed feature.
+ mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
+ InputConsumer.NO_OP, mInputMonitorCompat);
} else {
mUncheckedConsumer = InputConsumer.NO_OP;
}
@@ -509,7 +525,7 @@
// Other events
if (mUncheckedConsumer != InputConsumer.NO_OP) {
// Only transform the event if we are handling it in a proper consumer
- mDeviceState.setOrientationTransformIfNeeded(event);
+ mRotationTouchHelper.setOrientationTransformIfNeeded(event);
}
}
@@ -547,7 +563,7 @@
gestureState.updatePreviouslyAppearedTaskIds(
previousGestureState.getPreviouslyAppearedTaskIds());
} else {
- gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
+ gestureState.updateRunningTask(TraceHelper.allowIpcs("getRunningTask.0",
() -> mAM.getRunningTask(false /* filterOnlyVisibleRecents */)));
}
return gestureState;
@@ -626,6 +642,11 @@
base = new ScreenPinnedInputConsumer(this, newGestureState);
}
+ if (mDeviceState.canTriggerOneHandedAction(event)) {
+ base = new OneHandedModeInputConsumer(this, mDeviceState, base,
+ mInputMonitorCompat);
+ }
+
if (mDeviceState.isAccessibilityMenuAvailable()) {
base = new AccessibilityInputConsumer(this, mDeviceState, base,
mInputMonitorCompat);
@@ -634,6 +655,11 @@
if (mDeviceState.isScreenPinningActive()) {
base = mResetGestureInputConsumer;
}
+
+ if (mDeviceState.canTriggerOneHandedAction(event)) {
+ base = new OneHandedModeInputConsumer(this, mDeviceState, base,
+ mInputMonitorCompat);
+ }
}
return base;
}
@@ -660,7 +686,7 @@
if (AssistantUtilities.isExcludedAssistant(gestureState.getRunningTask())) {
// In the case where we are in the excluded assistant state, ignore it and treat the
// running activity as the task behind the assistant
- gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.assistant",
+ gestureState.updateRunningTask(TraceHelper.allowIpcs("getRunningTask.assistant",
() -> mAM.getRunningTask(true /* filterOnlyVisibleRecents */)));
ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
ComponentName runningComponent =
@@ -690,7 +716,7 @@
private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
MotionEvent event) {
- final BaseSwipeUpHandler.Factory factory;
+ final AbsSwipeUpHandler.Factory factory;
if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
factory = mFallbackSwipeHandlerFactory;
} else {
@@ -771,13 +797,7 @@
mOverviewComponentObserver.getActivityInterface();
final Intent overviewIntent = new Intent(
mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
- if (activityInterface.getCreatedActivity() == null) {
- // Make sure that UI states will be initialized.
- activityInterface.createActivityInitListener((wasVisible) -> {
- AppLaunchTracker.INSTANCE.get(TouchInteractionService.this);
- return false;
- }).register(overviewIntent);
- } else if (fromInit) {
+ if (activityInterface.getCreatedActivity() != null && fromInit) {
// The activity has been created before the initialization of overview service. It is
// usually happens when booting or launcher is the top activity, so we should already
// have the latest state.
@@ -801,6 +821,13 @@
}
if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
activity.getResources().getConfiguration().diff(newConfig))) {
+ // Since navBar gestural height are different between portrait and landscape,
+ // can handle orientation changes and refresh navigation gestural region through
+ // onOneHandedModeChanged()
+ int newGesturalHeight = ResourceUtils.getNavbarSize(
+ ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
+ getApplicationContext().getResources());
+ mDeviceState.onOneHandedModeChanged(newGesturalHeight);
return;
}
@@ -862,13 +889,13 @@
}
}
- private BaseSwipeUpHandler createLauncherSwipeHandler(
+ private AbsSwipeUpHandler createLauncherSwipeHandler(
GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
}
- private BaseSwipeUpHandler createFallbackSwipeHandler(
+ private AbsSwipeUpHandler createFallbackSwipeHandler(
GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
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 ffe9d6a..a946304 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
@@ -63,12 +63,6 @@
mActivity.startHome();
}
- @Override
- public boolean shouldUseMultiWindowTaskSizeStrategy() {
- // Just use the activity task size for multi-window as well.
- return false;
- }
-
/**
* When starting gesture interaction from home, we add a temporary invisible tile corresponding
* to the home task. This allows us to handle quick-switch similarly to a quick-switching
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index 5ad48eb..0c2c92c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -99,7 +99,8 @@
case ACTION_POINTER_DOWN: {
if (mState == STATE_INACTIVE) {
int pointerIndex = ev.getActionIndex();
- if (mDeviceState.isInSwipeUpTouchRegion(ev, pointerIndex)
+ if (mDeviceState.getRotationTouchHelper()
+ .isInSwipeUpTouchRegion(ev, pointerIndex)
&& mDelegate.allowInterceptByParent()) {
setActive(ev);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 3a97216..db1948b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -21,7 +21,7 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
@@ -147,7 +147,7 @@
if (!mThresholdCrossed) {
// Cancel interaction in case of multi-touch interaction
int ptrIdx = ev.getActionIndex();
- if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) {
+ if (!mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev, ptrIdx)) {
int action = ev.getAction();
ev.setAction(ACTION_CANCEL);
finishTouchTracking(ev);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
new file mode 100644
index 0000000..0bb8fff
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 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.inputconsumers;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Touch consumer for handling gesture event to launch one handed
+ * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
+ */
+public class OneHandedModeInputConsumer extends DelegateInputConsumer {
+
+ private static final String TAG = "OneHandedModeInputConsumer";
+ private static final int ANGLE_MAX = 150;
+ private static final int ANGLE_MIN = 30;
+
+ private final Context mContext;
+ private final RecentsAnimationDeviceState mDeviceState;
+
+ private final float mDragDistThreshold;
+ private final float mSquaredSlop;
+
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ private final PointF mStartDragPos = new PointF();
+
+ private boolean mPassedSlop;
+
+ public OneHandedModeInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
+ InputConsumer delegate, InputMonitorCompat inputMonitor) {
+ super(delegate, inputMonitor);
+ mContext = context;
+ mDeviceState = deviceState;
+ mDragDistThreshold = context.getResources().getDimensionPixelSize(
+ R.dimen.gestures_onehanded_drag_threshold);
+ mSquaredSlop = Utilities.squaredTouchSlop(context);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_ONE_HANDED | mDelegate.getType();
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case ACTION_DOWN: {
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+ break;
+ }
+ case ACTION_MOVE: {
+ if (mState == STATE_DELEGATE_ACTIVE) {
+ break;
+ }
+ if (!mDelegate.allowInterceptByParent()) {
+ mState = STATE_DELEGATE_ACTIVE;
+ break;
+ }
+
+ mLastPos.set(ev.getX(), ev.getY());
+ if (!mPassedSlop) {
+ if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
+ > mSquaredSlop) {
+ mStartDragPos.set(mLastPos.x, mLastPos.y);
+ if ((!mDeviceState.isOneHandedModeActive() && isValidStartAngle(
+ mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))
+ || (mDeviceState.isOneHandedModeActive() && isValidExitAngle(
+ mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))) {
+ mPassedSlop = true;
+ setActive(ev);
+ } else {
+ mState = STATE_DELEGATE_ACTIVE;
+ }
+ }
+ } else {
+ float distance = (float) Math.hypot(mLastPos.x - mStartDragPos.x,
+ mLastPos.y - mStartDragPos.y);
+ if (distance > mDragDistThreshold && mPassedSlop) {
+ onStopGestureDetected();
+ }
+ }
+ break;
+ }
+ case ACTION_UP:
+ case ACTION_CANCEL: {
+ if (mLastPos.y >= mStartDragPos.y && mPassedSlop) {
+ onStartGestureDetected();
+ }
+
+ mPassedSlop = false;
+ mState = STATE_INACTIVE;
+ break;
+ }
+ }
+
+ if (mState != STATE_ACTIVE) {
+ mDelegate.onMotionEvent(ev);
+ }
+ }
+
+ private void onStartGestureDetected() {
+ if (mDeviceState.isOneHandedModeEnabled()) {
+ if (!mDeviceState.isOneHandedModeActive()) {
+ SystemUiProxy.INSTANCE.get(mContext).startOneHandedMode();
+ }
+ } else if (mDeviceState.isSwipeToNotificationEnabled()) {
+ SystemUiProxy.INSTANCE.get(mContext).expandNotificationPanel();
+ }
+ }
+
+ private void onStopGestureDetected() {
+ if (!mDeviceState.isOneHandedModeEnabled() || !mDeviceState.isOneHandedModeActive()) {
+ return;
+ }
+
+ SystemUiProxy.INSTANCE.get(mContext).stopOneHandedMode();
+ }
+
+ private boolean isValidStartAngle(float deltaX, float deltaY) {
+ final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+ return angle > -(ANGLE_MAX) && angle < -(ANGLE_MIN);
+ }
+
+ private boolean isValidExitAngle(float deltaX, float deltaY) {
+ final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+ return angle > ANGLE_MIN && angle < ANGLE_MAX;
+ }
+}
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 26df9c7..f02e5e6 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
@@ -53,12 +53,13 @@
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.BaseActivityInterface;
-import com.android.quickstep.BaseSwipeUpHandler;
-import com.android.quickstep.BaseSwipeUpHandler.Factory;
+import com.android.quickstep.AbsSwipeUpHandler;
+import com.android.quickstep.AbsSwipeUpHandler.Factory;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RotationTouchHelper;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.CachedEventDispatcher;
@@ -86,12 +87,13 @@
private final NavBarPosition mNavBarPosition;
private final TaskAnimationManager mTaskAnimationManager;
private final GestureState mGestureState;
+ private final RotationTouchHelper mRotationTouchHelper;
private RecentsAnimationCallbacks mActiveCallbacks;
private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
private final InputMonitorCompat mInputMonitorCompat;
private final BaseActivityInterface mActivityInterface;
- private final BaseSwipeUpHandler.Factory mHandlerFactory;
+ private final AbsSwipeUpHandler.Factory mHandlerFactory;
private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
private final MotionPauseDetector mMotionPauseDetector;
@@ -99,7 +101,7 @@
private VelocityTracker mVelocityTracker;
- private BaseSwipeUpHandler mInteractionHandler;
+ private AbsSwipeUpHandler mInteractionHandler;
private final boolean mIsDeferredDownTarget;
private final PointF mDownPos = new PointF();
@@ -163,6 +165,7 @@
mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
+ mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
}
@Override
@@ -230,7 +233,7 @@
if (!mPassedPilferInputSlop) {
// Cancel interaction in case of multi-touch interaction
int ptrIdx = ev.getActionIndex();
- if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) {
+ if (!mRotationTouchHelper.isInSwipeUpTouchRegion(ev, ptrIdx)) {
forceCancelGesture(ev);
}
}
@@ -424,7 +427,7 @@
@Override
public void notifyOrientationSetup() {
- mDeviceState.onStartGesture();
+ mRotationTouchHelper.onStartGesture();
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/InputConsumerProxy.java
new file mode 100644
index 0000000..3e87f48
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/InputConsumerProxy.java
@@ -0,0 +1,118 @@
+/*
+ * 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.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import com.android.quickstep.InputConsumer;
+import com.android.systemui.shared.system.InputConsumerController;
+
+import java.util.function.Supplier;
+
+/**
+ * Utility class which manages proxying input events from {@link InputConsumerController}
+ * to an {@link InputConsumer}
+ */
+public class InputConsumerProxy {
+
+ private static final String TAG = "InputConsumerProxy";
+
+ private final InputConsumerController mInputConsumerController;
+ private final Supplier<InputConsumer> mConsumerSupplier;
+
+ // The consumer is created lazily on demand.
+ private InputConsumer mInputConsumer;
+
+ private boolean mDestroyed = false;
+ private boolean mTouchInProgress = false;
+ private boolean mDestroyPending = false;
+
+ public InputConsumerProxy(InputConsumerController inputConsumerController,
+ Supplier<InputConsumer> consumerSupplier) {
+ mInputConsumerController = inputConsumerController;
+ mConsumerSupplier = consumerSupplier;
+ }
+
+ public void enable() {
+ if (mDestroyed) {
+ return;
+ }
+ mInputConsumerController.setInputListener(this::onInputConsumerEvent);
+ }
+
+ private boolean onInputConsumerEvent(InputEvent ev) {
+ if (ev instanceof MotionEvent) {
+ onInputConsumerMotionEvent((MotionEvent) ev);
+ } else if (ev instanceof KeyEvent) {
+ if (mInputConsumer == null) {
+ mInputConsumer = mConsumerSupplier.get();
+ }
+ mInputConsumer.onKeyEvent((KeyEvent) ev);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean onInputConsumerMotionEvent(MotionEvent ev) {
+ int action = ev.getAction();
+
+ // Just to be safe, verify that ACTION_DOWN comes before any other action,
+ // and ignore any ACTION_DOWN after the first one (though that should not happen).
+ if (!mTouchInProgress && action != ACTION_DOWN) {
+ Log.w(TAG, "Received non-down motion before down motion: " + action);
+ return false;
+ }
+ if (mTouchInProgress && action == ACTION_DOWN) {
+ Log.w(TAG, "Received down motion while touch was already in progress");
+ return false;
+ }
+
+ if (action == ACTION_DOWN) {
+ mTouchInProgress = true;
+ if (mInputConsumer == null) {
+ mInputConsumer = mConsumerSupplier.get();
+ }
+ } else if (action == ACTION_CANCEL || action == ACTION_UP) {
+ // Finish any pending actions
+ mTouchInProgress = false;
+ if (mDestroyPending) {
+ destroy();
+ }
+ }
+ if (mInputConsumer != null) {
+ mInputConsumer.onMotionEvent(ev);
+ }
+
+ return true;
+ }
+
+ public void destroy() {
+ if (mTouchInProgress) {
+ mDestroyPending = true;
+ return;
+ }
+ mDestroyPending = false;
+ mDestroyed = true;
+ mInputConsumerController.setInputListener(null);
+ }
+}
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 64ae1e0..105b62a 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
@@ -73,6 +73,7 @@
private final BaseActivityInterface mSizeStrategy;
private final Rect mTaskRect = new Rect();
+ private float mOffsetY;
private final PointF mPivot = new PointF();
private DeviceProfile mDp;
@@ -130,8 +131,8 @@
/**
* @see com.android.quickstep.views.RecentsView#onConfigurationChanged(Configuration)
*/
- public void setRecentsConfiguration(Configuration configuration) {
- mOrientationState.setActivityConfiguration(configuration);
+ public void setRecentsRotation(int recentsRotation) {
+ mOrientationState.setRecentsRotation(recentsRotation);
mLayoutValid = false;
}
@@ -178,6 +179,10 @@
}
}
+ public void setOffsetY(float offsetY) {
+ mOffsetY = offsetY;
+ }
+
/**
* Adds animation for all the components corresponding to transition from an app to overview
*/
@@ -281,7 +286,7 @@
mMatrix.postScale(scale, scale);
// Apply TaskView matrix: translate, scale, scroll
- mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
+ mMatrix.postTranslate(mTaskRect.left, mTaskRect.top + mOffsetY);
mMatrix.postScale(mCurveScale, mCurveScale, taskWidth / 2, taskHeight / 2);
mOrientationState.getOrientationHandler().set(
mMatrix, MATRIX_POST_TRANSLATE, mScrollState.scroll);
@@ -307,7 +312,7 @@
.withCornerRadius(getCurrentCornerRadius());
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
- builder.withRelativeLayerTo(params.getRecentsSurface(), Integer.MAX_VALUE);
+ builder.withRelativeLayerTo(params.getRecentsSurface(), Integer.MIN_VALUE);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
index 3d44eb6..c06dd9c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
@@ -54,6 +54,7 @@
/**
* View used to educate the user on how to access All Apps when in No Nav Button navigation mode.
+ * Consumes all touches until after the animation is completed and the view is removed.
*/
public class AllAppsEduView extends AbstractFloatingView {
@@ -114,8 +115,18 @@
}
@Override
+ public boolean onBackPressed() {
+ return true;
+ }
+
+ @Override
+ public boolean canInterceptEventsInSystemGestureRegion() {
+ return true;
+ }
+
+ @Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- return mAnimation != null && mAnimation.isRunning();
+ return true;
}
private void playAnimation() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 59c6815..0fcd74b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep.views;
-import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -31,7 +30,6 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
-import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.Surface;
@@ -41,18 +39,14 @@
import com.android.launcher3.Hotseat;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.util.TransformParams;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.RecentsExtraCard;
-import com.android.systemui.shared.recents.model.Task;
/**
* {@link RecentsView} used in Launcher activity
@@ -61,8 +55,6 @@
public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher>
implements StateListener<LauncherState> {
- private final TransformParams mTransformParams = new TransformParams();
-
private RecentsExtraCard mRecentsExtraCardPlugin;
private RecentsExtraViewContainer mRecentsExtraViewContainer;
private PluginListener<RecentsExtraCard> mRecentsExtraCardPluginListener =
@@ -112,17 +104,6 @@
}
}
- @Override
- public void setTranslationY(float translationY) {
- super.setTranslationY(translationY);
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- LauncherState state = mActivity.getStateManager().getState();
- if (state == OVERVIEW || state == ALL_APPS) {
- redrawLiveTile(false);
- }
- }
- }
-
/**
* Animates adjacent tasks and translate hotseat off screen as well.
*/
@@ -158,11 +139,11 @@
protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (tv.isRunningTask()) {
- mTransformParams.setProgress(1 - progress)
+ mLiveTileParams.setProgress(1 - progress)
.setSyncTransactionApplier(mSyncTransactionApplier);
// TODO: Revisit live tiles
} else {
- redrawLiveTile(true);
+ redrawLiveTile();
}
}
}
@@ -179,58 +160,6 @@
}
@Override
- public void onTaskLaunched(Task task) {
- UserHandle user = UserHandle.of(task.key.userId);
- AppLaunchTracker.INSTANCE.get(getContext()).onStartApp(task.getTopComponent(), user,
- AppLaunchTracker.CONTAINER_OVERVIEW);
- }
-
- @Override
- public boolean shouldUseMultiWindowTaskSizeStrategy() {
- return TraceHelper.whitelistIpcs("isInMultiWindowMode", mActivity::isInMultiWindowMode);
- }
-
- @Override
- public void scrollTo(int x, int y) {
- super.scrollTo(x, y);
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
- redrawLiveTile(true);
- }
- }
-
- @Override
- public TransformParams getLiveTileParams(
- boolean mightNeedToRefill) {
- if (!mEnableDrawingLiveTile || mRecentsAnimationController == null
- || mRecentsAnimationTargets == null) {
- return null;
- }
- TaskView taskView = getRunningTaskView();
- if (taskView != null) {
- taskView.getThumbnail().getGlobalVisibleRect(mTempRect);
- int offsetX = (int) (mTaskWidth * taskView.getScaleX() * getScaleX()
- - mTempRect.width());
- int offsetY = (int) (mTaskHeight * taskView.getScaleY() * getScaleY()
- - mTempRect.height());
- if (((mCurrentPage != 0) || mightNeedToRefill) && offsetX > 0) {
- if (mTempRect.left - offsetX < 0) {
- mTempRect.left -= offsetX;
- } else {
- mTempRect.right += offsetX;
- }
- }
- if (mightNeedToRefill && offsetY > 0) {
- mTempRect.top -= offsetY;
- }
- mTransformParams.setProgress(1f)
- .setTargetAlpha(taskView.getAlpha())
- .setSyncTransactionApplier(mSyncTransactionApplier)
- .setTargetSet(mRecentsAnimationTargets);
- }
- return mTransformParams;
- }
-
- @Override
public void reset() {
super.reset();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
index a2da398..79d57c5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -70,12 +70,14 @@
@IntDef(flag = true, value = {
DISABLED_SCROLLING,
- DISABLED_ROTATED})
+ DISABLED_ROTATED,
+ DISABLED_NO_THUMBNAIL})
@Retention(RetentionPolicy.SOURCE)
public @interface ActionsDisabledFlags { }
public static final int DISABLED_SCROLLING = 1 << 0;
public static final int DISABLED_ROTATED = 1 << 1;
+ public static final int DISABLED_NO_THUMBNAIL = 1 << 2;
private static final int INDEX_CONTENT_ALPHA = 0;
private static final int INDEX_VISIBILITY_ALPHA = 1;
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 9c25b24..49a9db9 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
@@ -55,11 +55,8 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
-import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Point;
@@ -81,6 +78,7 @@
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
@@ -132,6 +130,7 @@
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.SplitScreenBounds;
import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
@@ -215,10 +214,11 @@
protected RecentsOrientedState mOrientationState;
protected final BaseActivityInterface mSizeStrategy;
protected RecentsAnimationController mRecentsAnimationController;
- protected RecentsAnimationTargets mRecentsAnimationTargets;
protected SurfaceTransactionApplier mSyncTransactionApplier;
protected int mTaskWidth;
protected int mTaskHeight;
+ protected final TransformParams mLiveTileParams = new TransformParams();
+ protected final TaskViewSimulator mLiveTileTaskViewSimulator;
protected boolean mEnableDrawingLiveTile = false;
protected final Rect mTempRect = new Rect();
private final PointF mTempPointF = new PointF();
@@ -379,7 +379,7 @@
mOrientationState.setMultiWindowMode(inMultiWindowMode);
setLayoutRotation(mOrientationState.getTouchRotation(),
mOrientationState.getDisplayRotation());
- rotateAllChildTasks();
+ updateChildTaskOrientations();
}
if (!inMultiWindowMode && mOverviewStateEnabled) {
// TODO: Re-enable layout transitions for addition of the unpinned task
@@ -396,7 +396,8 @@
mActivity = BaseActivity.fromContext(context);
mOrientationState = new RecentsOrientedState(
context, mSizeStrategy, this::animateRecentsRotationInPlace);
- mOrientationState.setActivityConfiguration(context.getResources().getConfiguration());
+ final int rotation = mActivity.getDisplay().getRotation();
+ mOrientationState.setRecentsRotation(rotation);
mFastFlingVelocity = getResources()
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
@@ -432,6 +433,12 @@
// Initialize quickstep specific cache params here, as this is constructed only once
mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
+
+ mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy());
+ mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
+ mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(),
+ getPagedViewOrientedState().getDisplayRotation());
+ mLiveTileTaskViewSimulator.setRecentsRotation(rotation);
}
public OverScroller getScroller() {
@@ -516,6 +523,7 @@
mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
mSyncTransactionApplier = new SurfaceTransactionApplier(this);
+ mLiveTileParams.setSyncTransactionApplier(mSyncTransactionApplier);
RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
mIdp.addOnChangeListener(this);
mIPinnedStackAnimationListener.setActivity(mActivity);
@@ -533,6 +541,7 @@
mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
mSyncTransactionApplier = null;
+ mLiveTileParams.setSyncTransactionApplier(null);
RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
mIdp.removeOnChangeListener(this);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
@@ -651,6 +660,16 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
+
+ TaskView taskView = getCurrentPageTaskView();
+ if (taskView != null) {
+ TouchDelegate mChildTouchDelegate = taskView.getIconTouchDelegate(ev);
+ if (mChildTouchDelegate != null && mChildTouchDelegate.onTouchEvent(ev)) {
+ // Keep consuming events to pass to delegate
+ return true;
+ }
+ }
+
final int x = (int) ev.getX();
final int y = (int) ev.getY();
switch (ev.getAction()) {
@@ -850,6 +869,7 @@
public void setInsets(Rect insets) {
mInsets.set(insets);
resetPaddingFromTaskSize();
+ mLiveTileTaskViewSimulator.setDp(mActivity.getDeviceProfile());
}
private void resetPaddingFromTaskSize() {
@@ -891,6 +911,12 @@
// Update the high res thumbnail loader state
mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
+
+ mLiveTileTaskViewSimulator.setScroll(getScrollOffset());
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
+ && mLiveTileParams.getTargetSet() != null) {
+ redrawLiveTile();
+ }
return scrolling;
}
@@ -998,7 +1024,7 @@
mTaskListChangeId = -1;
mRecentsAnimationController = null;
- mRecentsAnimationTargets = null;
+ mLiveTileParams.setTargetSet(null);
unloadVisibleTaskData();
setCurrentPage(0);
@@ -1080,7 +1106,7 @@
pa.addListener(AnimationSuccessListener.forRunnable(() -> {
setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
mActivity.getDragLayer().recreateControllers();
- rotateAllChildTasks();
+ updateChildTaskOrientations();
setRecentsChangedOrientation(false).start();
}));
pa.start();
@@ -1101,7 +1127,7 @@
}
- private void rotateAllChildTasks() {
+ private void updateChildTaskOrientations() {
for (int i = 0; i < getTaskViewCount(); i++) {
getTaskViewAt(i).setOrientationState(mOrientationState);
}
@@ -1651,15 +1677,20 @@
super.setVisibility(visibility);
if (mActionsView != null) {
mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE);
+ if (visibility != VISIBLE) {
+ mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
+ }
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- if (mOrientationState.setActivityConfiguration(newConfig)) {
+ final int rotation = mActivity.getDisplay().getRotation();
+ if (mOrientationState.setRecentsRotation(rotation)) {
updateOrientationHandler();
}
+ mLiveTileTaskViewSimulator.setRecentsRotation(rotation);
}
public void setLayoutRotation(int touchRotation, int displayRotation) {
@@ -1683,10 +1714,13 @@
|| mOrientationState.getRecentsActivityRotation() != ROTATION_0;
mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
!mOrientationState.canRecentsActivityRotate() && isInLandscape);
+ updateChildTaskOrientations();
resetPaddingFromTaskSize();
requestLayout();
// Reapply the current page to update page scrolls.
setCurrentPage(mCurrentPage);
+ mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(),
+ getPagedViewOrientedState().getDisplayRotation());
}
public RecentsOrientedState getPagedViewOrientedState() {
@@ -1997,19 +2031,12 @@
protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
}
- public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
-
protected void onTaskLaunchAnimationEnd(boolean success) {
if (success) {
resetTaskVisuals();
}
}
- /**
- * Called when task activity is launched
- */
- public void onTaskLaunched(Task task){ }
-
@Override
protected void notifyPageSwitchListener(int prevPage) {
super.notifyPageSwitchListener(prevPage);
@@ -2070,13 +2097,23 @@
mEnableDrawingLiveTile = enableDrawingLiveTile;
}
- public void redrawLiveTile(boolean mightNeedToRefill) { }
+ public void redrawLiveTile() {
+ mLiveTileTaskViewSimulator.apply(mLiveTileParams);
+ }
+
+ public TaskViewSimulator getLiveTileTaskViewSimulator() {
+ return mLiveTileTaskViewSimulator;
+ }
// TODO: To be removed in a follow up CL
public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
RecentsAnimationTargets recentsAnimationTargets) {
mRecentsAnimationController = recentsAnimationController;
- mRecentsAnimationTargets = recentsAnimationTargets;
+ if (recentsAnimationTargets != null) {
+ mLiveTileTaskViewSimulator.setPreview(
+ recentsAnimationTargets.apps[recentsAnimationTargets.apps.length - 1]);
+ mLiveTileParams.setTargetSet(recentsAnimationTargets);
+ }
}
public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
@@ -2212,11 +2249,6 @@
};
}
- public TransformParams getLiveTileParams(
- boolean mightNeedToRefill) {
- return null;
- }
-
private void updateEnabledOverlays() {
int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
int taskCount = getTaskViewCount();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
index ef66b7a..9b2048e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
@@ -169,7 +169,9 @@
}
if (mIsOpen) {
mOptionLayout.removeAllViews();
- populateAndLayoutMenu();
+ if (!populateAndLayoutMenu()) {
+ close(false);
+ }
}
}
@@ -186,14 +188,22 @@
}
mActivity.getDragLayer().addView(this);
mTaskView = taskView;
- populateAndLayoutMenu();
+ if (!populateAndLayoutMenu()) {
+ return false;
+ }
post(this::animateOpen);
return true;
}
- private void populateAndLayoutMenu() {
+ /** @return true if successfully able to populate task view menu, false otherwise */
+ private boolean populateAndLayoutMenu() {
+ if (mTaskView.getTask().icon == null) {
+ // Icon may not be loaded
+ return false;
+ }
addMenuOptions(mTaskView);
orientAroundTaskView(mTaskView);
+ return true;
}
private void addMenuOptions(TaskView taskView) {
@@ -240,8 +250,10 @@
setLayoutParams(params);
setScaleX(taskView.getScaleX());
setScaleY(taskView.getScaleY());
+ boolean canActivityRotate = taskView.getRecentsView()
+ .mOrientationState.canRecentsActivityRotate();
mOptionLayout.setOrientation(orientationHandler
- .getTaskMenuLayoutOrientation(mOptionLayout));
+ .getTaskMenuLayoutOrientation(canActivityRotate, mOptionLayout));
setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top,
taskView.getPagedOrientationHandler());
}
@@ -270,6 +282,7 @@
mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
setVisibility(VISIBLE);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index b2f937f..a8d6442 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -59,7 +59,6 @@
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ConfigurationCompat;
/**
* A task in the Recents view.
@@ -357,7 +356,7 @@
}
private void updateOverlay() {
- if (mOverlayEnabled && mBitmapShader != null && mThumbnailData != null) {
+ if (mOverlayEnabled) {
mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
mPreviewPositionHelper.mIsOrientationChanged);
} else {
@@ -385,8 +384,8 @@
if (mBitmapShader != null && mThumbnailData != null) {
mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
mThumbnailData.thumbnail.getHeight());
- int currentRotation = ConfigurationCompat.getWindowConfigurationRotation(
- mActivity.getResources().getConfiguration());
+ int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
+ .getRecentsActivityRotation();
mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
currentRotation);
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 222f6e6..27d84e6 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
@@ -22,16 +22,19 @@
import static android.view.Gravity.END;
import static android.view.Gravity.START;
import static android.view.Gravity.TOP;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static android.widget.Toast.LENGTH_SHORT;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.comp;
+import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
- .LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
import android.animation.Animator;
@@ -53,7 +56,9 @@
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
+import android.view.MotionEvent;
import android.view.Surface;
+import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -78,6 +83,7 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.TransformingTouchDelegate;
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskIconCache;
@@ -122,6 +128,13 @@
public static final long SCALE_ICON_DURATION = 120;
private static final long DIM_ANIM_DURATION = 700;
+ /**
+ * This technically can be a vanilla {@link TouchDelegate} class, however that class requires
+ * setting the touch bounds at construction, so we'd repeatedly be created many instances
+ * unnecessarily as scrolling occurs, whereas {@link TransformingTouchDelegate} allows touch
+ * delegated bounds only to be updated.
+ */
+ private TransformingTouchDelegate mIconTouchDelegate;
private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
Collections.singletonList(new Rect());
@@ -186,6 +199,7 @@
private int mStackHeight;
private View mContextualChipWrapper;
private View mContextualChip;
+ private final float[] mIconCenterCoords = new float[2];
public TaskView(Context context) {
this(context, null);
@@ -246,6 +260,26 @@
super.onFinishInflate();
mSnapshotView = findViewById(R.id.snapshot);
mIconView = findViewById(R.id.icon);
+ mIconTouchDelegate = new TransformingTouchDelegate(mIconView);
+ }
+
+ public TouchDelegate getIconTouchDelegate(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ computeAndSetIconTouchDelegate();
+ }
+ return mIconTouchDelegate;
+ }
+
+ private void computeAndSetIconTouchDelegate() {
+ float iconHalfSize = mIconView.getWidth() / 2f;
+ mIconCenterCoords[0] = mIconCenterCoords[1] = iconHalfSize;
+ getDescendantCoordRelativeToAncestor(mIconView, mActivity.getDragLayer(), mIconCenterCoords,
+ false);
+ mIconTouchDelegate.setBounds(
+ (int) (mIconCenterCoords[0] - iconHalfSize),
+ (int) (mIconCenterCoords[1] - iconHalfSize),
+ (int) (mIconCenterCoords[0] + iconHalfSize),
+ (int) (mIconCenterCoords[1] + iconHalfSize));
}
/**
@@ -385,7 +419,6 @@
}
}, resultCallbackHandler);
}
- getRecentsView().onTaskLaunched(mTask);
}
}
@@ -468,18 +501,18 @@
int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
switch (orientationHandler.getRotation()) {
- case Surface.ROTATION_90:
+ case ROTATION_90:
iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
iconParams.rightMargin = -thumbnailPadding;
iconParams.leftMargin = 0;
iconParams.topMargin = snapshotParams.topMargin / 2;
break;
- case Surface.ROTATION_180:
+ case ROTATION_180:
iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
iconParams.bottomMargin = -thumbnailPadding;
iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0;
break;
- case Surface.ROTATION_270:
+ case ROTATION_270:
iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
iconParams.leftMargin = -thumbnailPadding;
iconParams.rightMargin = 0;
diff --git a/quickstep/res/drawable/bg_circle.xml b/quickstep/res/drawable/bg_circle.xml
new file mode 100644
index 0000000..506177b
--- /dev/null
+++ b/quickstep/res/drawable/bg_circle.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid android:color="#FFFFFFFF" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index 459d65f..43bf0ea 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -24,6 +24,14 @@
android:layout_height="match_parent"
android:background="@drawable/gesture_tutorial_ripple"/>
+ <com.android.launcher3.views.ClipIconView
+ android:id="@+id/gesture_tutorial_fake_icon_view"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:background="@drawable/bg_circle"
+ android:backgroundTint="@color/gesture_tutorial_fake_task_view_color"
+ android:visibility="invisible" />
+
<View
android:id="@+id/gesture_tutorial_fake_task_view"
android:layout_width="match_parent"
@@ -41,81 +49,81 @@
android:id="@+id/gesture_tutorial_fragment_close_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:padding="18dp"
- android:layout_marginTop="30dp"
- android:layout_marginStart="4dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
- android:background="@android:color/transparent"
+ android:layout_marginStart="4dp"
+ android:layout_marginTop="30dp"
android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_titles_container"
+ android:background="@android:color/transparent"
android:contentDescription="@string/gesture_tutorial_close_button_content_description"
- android:tint="?android:attr/textColorPrimary"
- android:src="@drawable/gesture_tutorial_close_button"/>
+ android:padding="18dp"
+ android:src="@drawable/gesture_tutorial_close_button"
+ android:tint="?android:attr/textColorPrimary"/>
<LinearLayout
android:id="@+id/gesture_tutorial_fragment_titles_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="70dp"
android:layout_alignParentTop="true"
+ android:layout_marginTop="70dp"
android:focusable="true"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/gesture_tutorial_fragment_title_view"
+ style="@style/TextAppearance.GestureTutorial.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/gesture_tutorial_title_margin_start_end"
- android:layout_marginEnd="@dimen/gesture_tutorial_title_margin_start_end"
- style="@style/TextAppearance.GestureTutorial.Title"/>
+ android:layout_marginEnd="@dimen/gesture_tutorial_title_margin_start_end"/>
<TextView
android:id="@+id/gesture_tutorial_fragment_subtitle_view"
+ style="@style/TextAppearance.GestureTutorial.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
android:layout_marginStart="@dimen/gesture_tutorial_subtitle_margin_start_end"
- android:layout_marginEnd="@dimen/gesture_tutorial_subtitle_margin_start_end"
- style="@style/TextAppearance.GestureTutorial.Subtitle"/>
+ android:layout_marginTop="10dp"
+ android:layout_marginEnd="@dimen/gesture_tutorial_subtitle_margin_start_end"/>
</LinearLayout>
<TextView
android:id="@+id/gesture_tutorial_fragment_feedback_view"
+ style="@style/TextAppearance.GestureTutorial.Feedback"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="10dp"
- android:layout_centerHorizontal="true"
android:layout_above="@id/gesture_tutorial_fragment_action_button"
+ android:layout_centerHorizontal="true"
android:layout_marginStart="@dimen/gesture_tutorial_feedback_margin_start_end"
android:layout_marginEnd="@dimen/gesture_tutorial_feedback_margin_start_end"
- style="@style/TextAppearance.GestureTutorial.Feedback"/>
+ android:layout_marginBottom="10dp"/>
<!-- android:stateListAnimator="@null" removes shadow and normal on click behavior (increase
of elevation and shadow) which is replaced by ripple effect in android:foreground -->
<Button
android:id="@+id/gesture_tutorial_fragment_action_button"
+ style="@style/TextAppearance.GestureTutorial.ButtonLabel"
android:layout_width="142dp"
android:layout_height="49dp"
- android:layout_marginEnd="@dimen/gesture_tutorial_button_margin_start_end"
- android:layout_marginBottom="48dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
- android:stateListAnimator="@null"
+ android:layout_marginEnd="@dimen/gesture_tutorial_button_margin_start_end"
+ android:layout_marginBottom="48dp"
android:background="@drawable/gesture_tutorial_action_button_background"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
- style="@style/TextAppearance.GestureTutorial.ButtonLabel"/>
+ android:stateListAnimator="@null"/>
<Button
android:id="@+id/gesture_tutorial_fragment_action_text_button"
+ style="@style/TextAppearance.GestureTutorial.TextButtonLabel"
android:layout_width="142dp"
android:layout_height="49dp"
- android:layout_marginStart="@dimen/gesture_tutorial_button_margin_start_end"
- android:layout_marginBottom="48dp"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
- android:stateListAnimator="@null"
+ android:layout_marginStart="@dimen/gesture_tutorial_button_margin_start_end"
+ android:layout_marginBottom="48dp"
android:background="@null"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
- style="@style/TextAppearance.GestureTutorial.TextButtonLabel"/>
+ android:stateListAnimator="@null"/>
</RelativeLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index e05688e..258f24a 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -17,9 +17,7 @@
<com.android.quickstep.views.OverviewActionsView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/overview_actions_height"
- android:layout_gravity="center_horizontal|bottom"
- android:layout_marginLeft="@dimen/overview_actions_horizontal_margin"
- android:layout_marginRight="@dimen/overview_actions_horizontal_margin">
+ android:layout_gravity="center_horizontal|bottom">
<LinearLayout
android:id="@+id/action_buttons"
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 9d70316..6737c5f 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -76,6 +76,10 @@
<dimen name="gestures_assistant_drag_threshold">55dp</dimen>
<dimen name="gestures_assistant_fling_threshold">55dp</dimen>
+ <!-- One-Handed Mode -->
+ <!-- Threshold for draging distance to enable one-handed mode -->
+ <dimen name="gestures_onehanded_drag_threshold">20dp</dimen>
+
<!-- Distance to move elements when swiping up to go home from launcher -->
<dimen name="home_pullback_distance">28dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 6b941be..235df42 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -111,6 +111,13 @@
}
@Override
+ protected void handleGestureContract(Intent intent) {
+ if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) {
+ super.handleGestureContract(intent);
+ }
+ }
+
+ @Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
RecentsModel.INSTANCE.get(this).onTrimMemory(level);
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index bdeb3a6..0ae386f 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -20,7 +20,7 @@
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.RECENTS_ATTACH_DURATION;
+import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
import static com.android.quickstep.SysUINavigationMode.getMode;
import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
@@ -150,7 +150,7 @@
return deviceState.isInDeferredGestureRegion(ev);
}
- public abstract void onExitOverview(RecentsAnimationDeviceState deviceState,
+ public abstract void onExitOverview(RotationTouchHelper deviceState,
Runnable exitRunnable);
/**
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index ec720d5..67711c0 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -35,6 +35,7 @@
int TYPE_RESET_GESTURE = 1 << 8;
int TYPE_OVERSCROLL = 1 << 9;
int TYPE_SYSUI_OVERLAY = 1 << 10;
+ int TYPE_ONE_HANDED = 1 << 11;
String[] NAMES = new String[] {
"TYPE_NO_OP", // 0
@@ -47,7 +48,8 @@
"TYPE_OVERVIEW_WITHOUT_FOCUS", // 7
"TYPE_RESET_GESTURE", // 8
"TYPE_OVERSCROLL", // 9
- "TYPE_SYSUI_OVERLAY" // 10
+ "TYPE_SYSUI_OVERLAY", // 10
+ "TYPE_ONE_HANDED", // 11
};
InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 1081548..0710a0a 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -63,7 +63,9 @@
private SparseArray<OrientationRectF> mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS);
private final RectF mAssistantLeftRegion = new RectF();
private final RectF mAssistantRightRegion = new RectF();
+ private final RectF mOneHandedModeRegion = new RectF();
private int mCurrentDisplayRotation;
+ private int mNavBarGesturalHeight;
private boolean mEnableMultipleRegions;
private Resources mResources;
private OrientationRectF mLastRectTouched;
@@ -103,18 +105,33 @@
mResources = resources;
mMode = mode;
mContractInfo = contractInfo;
+ mNavBarGesturalHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
}
- void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info) {
+ private void refreshTouchRegion(DefaultDisplay.Info info, Resources newRes) {
+ // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
+ // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
+ // It tries to cache and reuse swipe regions whenever possible based only on rotation
+ mResources = newRes;
+ mSwipeTouchRegions.clear();
+ resetSwipeRegions(info);
+ }
+
+ void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info,
+ Resources newRes) {
if (mMode == newMode) {
return;
}
this.mMode = newMode;
- // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
- // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
- // It tries to cache and reuse swipe regions whenever possible based only on rotation
- mSwipeTouchRegions.clear();
- resetSwipeRegions(info);
+ refreshTouchRegion(info, newRes);
+ }
+
+ void setGesturalHeight(int newGesturalHeight, DefaultDisplay.Info info, Resources newRes) {
+ if (mNavBarGesturalHeight == newGesturalHeight) {
+ return;
+ }
+ mNavBarGesturalHeight = newGesturalHeight;
+ refreshTouchRegion(info, newRes);
}
/**
@@ -216,10 +233,10 @@
Point size = display.realSize;
int rotation = display.rotation;
+ int touchHeight = mNavBarGesturalHeight;
OrientationRectF orientationRectF =
new OrientationRectF(0, 0, size.x, size.y, rotation);
if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
- int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
orientationRectF.top = orientationRectF.bottom - touchHeight;
updateAssistantRegions(orientationRectF);
} else {
@@ -235,10 +252,11 @@
+ getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
break;
default:
- orientationRectF.top = orientationRectF.bottom
- - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+ orientationRectF.top = orientationRectF.bottom - touchHeight;
}
}
+ // One handed gestural only active on portrait mode
+ mOneHandedModeRegion.set(0, orientationRectF.bottom - touchHeight, size.x, size.y);
return orientationRectF;
}
@@ -264,6 +282,10 @@
}
+ boolean touchInOneHandedModeRegion(MotionEvent ev) {
+ return mOneHandedModeRegion.contains(ev.getX(), ev.getY());
+ }
+
private int getNavbarSize(String resName) {
return ResourceUtils.getNavbarSize(resName, mResources);
}
@@ -355,6 +377,8 @@
regions.append(rectF.mRotation).append(" ");
}
pw.println(regions.toString());
+ pw.println(" mNavBarGesturalHeight=" + mNavBarGesturalHeight);
+ pw.println(" mOneHandedModeRegion=" + mOneHandedModeRegion);
}
private class OrientationRectF extends RectF {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 4e9aa61..51f5e5d 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -15,49 +15,30 @@
*/
package com.android.quickstep;
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.util.Preconditions;
import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.util.function.Consumer;
-import java.util.function.Supplier;
/**
* Wrapper around RecentsAnimationControllerCompat to help with some synchronization
*/
public class RecentsAnimationController {
- private static final String TAG = "RecentsAnimationController";
-
private final RecentsAnimationControllerCompat mController;
private final Consumer<RecentsAnimationController> mOnFinishedListener;
private final boolean mAllowMinimizeSplitScreen;
- private InputConsumerController mInputConsumerController;
- private Supplier<InputConsumer> mInputProxySupplier;
- private InputConsumer mInputConsumer;
private boolean mUseLauncherSysBarFlags = false;
private boolean mSplitScreenMinimized = false;
- private boolean mTouchInProgress;
- private boolean mDisableInputProxyPending;
public RecentsAnimationController(RecentsAnimationControllerCompat controller,
boolean allowMinimizeSplitScreen,
@@ -136,12 +117,12 @@
@UiThread
public void finishAnimationToHome() {
- finishAndDisableInputProxy(true /* toRecents */, null, false /* sendUserLeaveHint */);
+ finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
}
@UiThread
public void finishAnimationToApp() {
- finishAndDisableInputProxy(false /* toRecents */, null, false /* sendUserLeaveHint */);
+ finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
}
/** See {@link #finish(boolean, Runnable, boolean)} */
@@ -160,18 +141,6 @@
@UiThread
public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
Preconditions.assertUIThread();
- if (toRecents && mTouchInProgress) {
- // Finish the controller as requested, but don't disable input proxy yet.
- mDisableInputProxyPending = true;
- finishController(toRecents, onFinishComplete, sendUserLeaveHint);
- } else {
- finishAndDisableInputProxy(toRecents, onFinishComplete, sendUserLeaveHint);
- }
- }
-
- private void finishAndDisableInputProxy(boolean toRecents, Runnable onFinishComplete,
- boolean sendUserLeaveHint) {
- disableInputProxy();
finishController(toRecents, onFinishComplete, sendUserLeaveHint);
}
@@ -179,7 +148,6 @@
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
mOnFinishedListener.accept(this);
UI_HELPER_EXECUTOR.execute(() -> {
- mController.setInputConsumerEnabled(false);
mController.finish(toRecents, sendUserLeaveHint);
if (callback != null) {
MAIN_EXECUTOR.execute(callback);
@@ -197,75 +165,8 @@
});
}
- public void enableInputProxy(InputConsumerController inputConsumerController,
- Supplier<InputConsumer> inputProxySupplier) {
- mInputProxySupplier = inputProxySupplier;
- mInputConsumerController = inputConsumerController;
- mInputConsumerController.setInputListener(this::onInputConsumerEvent);
- }
-
/** @return wrapper controller. */
public RecentsAnimationControllerCompat getController() {
return mController;
}
-
- private void disableInputProxy() {
- if (mInputConsumer != null && mTouchInProgress) {
- long now = SystemClock.uptimeMillis();
- MotionEvent dummyCancel = MotionEvent.obtain(now, now, ACTION_CANCEL, 0, 0, 0);
- mInputConsumer.onMotionEvent(dummyCancel);
- dummyCancel.recycle();
- }
- if (mInputConsumerController != null) {
- mInputConsumerController.setInputListener(null);
- }
- mInputProxySupplier = null;
- }
-
- private boolean onInputConsumerEvent(InputEvent ev) {
- if (ev instanceof MotionEvent) {
- onInputConsumerMotionEvent((MotionEvent) ev);
- } else if (ev instanceof KeyEvent) {
- if (mInputConsumer == null) {
- mInputConsumer = mInputProxySupplier.get();
- }
- mInputConsumer.onKeyEvent((KeyEvent) ev);
- return true;
- }
- return false;
- }
-
- private boolean onInputConsumerMotionEvent(MotionEvent ev) {
- int action = ev.getAction();
-
- // Just to be safe, verify that ACTION_DOWN comes before any other action,
- // and ignore any ACTION_DOWN after the first one (though that should not happen).
- if (!mTouchInProgress && action != ACTION_DOWN) {
- Log.w(TAG, "Received non-down motion before down motion: " + action);
- return false;
- }
- if (mTouchInProgress && action == ACTION_DOWN) {
- Log.w(TAG, "Received down motion while touch was already in progress");
- return false;
- }
-
- if (action == ACTION_DOWN) {
- mTouchInProgress = true;
- if (mInputConsumer == null) {
- mInputConsumer = mInputProxySupplier.get();
- }
- } else if (action == ACTION_CANCEL || action == ACTION_UP) {
- // Finish any pending actions
- mTouchInProgress = false;
- if (mDisableInputProxyPending) {
- mDisableInputProxyPending = false;
- disableInputProxy();
- }
- }
- if (mInputConsumer != null) {
- mInputConsumer.onMotionEvent(ev);
- }
-
- return true;
- }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 0a70bd6..2868b1b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -16,11 +16,9 @@
package com.android.quickstep;
import static android.content.Intent.ACTION_USER_UNLOCKED;
-import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@@ -31,6 +29,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -45,27 +44,27 @@
import android.content.res.Resources;
import android.graphics.Region;
import android.os.Process;
+import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.DisplayMetrics;
import android.view.MotionEvent;
-import android.view.OrientationEventListener;
+import android.view.Surface;
import androidx.annotation.BinderThread;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DefaultDisplay;
import com.android.launcher3.util.SecureSettingsObserver;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
import com.android.quickstep.util.NavBarPosition;
-import com.android.quickstep.util.RecentsOrientedState;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
-import com.android.systemui.shared.system.TaskStackChangeListener;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -77,13 +76,14 @@
*/
public class RecentsAnimationDeviceState implements
NavigationModeChangeListener,
- DefaultDisplay.DisplayInfoChangeListener {
+ DefaultDisplay.DisplayInfoChangeListener,
+ OneHandedModeChangeListener {
private final Context mContext;
private final SysUINavigationMode mSysUiNavMode;
private final DefaultDisplay mDefaultDisplay;
private final int mDisplayId;
- private int mDisplayRotation;
+ private final RotationTouchHelper mRotationTouchHelper;
private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
@@ -94,6 +94,8 @@
private final Region mDeferredGestureRegion = new Region();
private boolean mAssistantAvailable;
private float mAssistantVisibility;
+ private boolean mIsOneHandedModeEnabled;
+ private boolean mIsSwipeToNotificationEnabled;
private boolean mIsUserUnlocked;
private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
@@ -107,76 +109,10 @@
}
};
- private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
- @Override
- public void onRecentTaskListFrozenChanged(boolean frozen) {
- mTaskListFrozen = frozen;
- if (frozen || mInOverview) {
- return;
- }
- enableMultipleRegions(false);
- }
-
- @Override
- public void onActivityRotation(int displayId) {
- super.onActivityRotation(displayId);
- // This always gets called before onDisplayInfoChanged() so we know how to process
- // the rotation in that method. This is done to avoid having a race condition between
- // the sensor readings and onDisplayInfoChanged() call
- if (displayId != mDisplayId) {
- return;
- }
-
- mPrioritizeDeviceRotation = true;
- if (mInOverview) {
- // reset, launcher must be rotating
- mExitOverviewRunnable.run();
- }
- }
- };
-
- private Runnable mExitOverviewRunnable = new Runnable() {
- @Override
- public void run() {
- mInOverview = false;
- enableMultipleRegions(false);
- }
- };
-
- private OrientationTouchTransformer mOrientationTouchTransformer;
- /**
- * Used to listen for when the device rotates into the orientation of the current
- * foreground app. For example, if a user quickswitches from a portrait to a fixed landscape
- * app and then rotates rotates the device to match that orientation, this triggers calls to
- * sysui to adjust the navbar.
- */
- private OrientationEventListener mOrientationListener;
- private int mSensorRotation = ROTATION_0;
- /**
- * This is the configuration of the foreground app or the app that will be in the foreground
- * once a quickstep gesture finishes.
- */
- private int mCurrentAppRotation = -1;
- /**
- * This flag is set to true when the device physically changes orientations. When true,
- * we will always report the current rotation of the foreground app whenever the display
- * changes, as it would indicate the user's intention to rotate the foreground app.
- */
- private boolean mPrioritizeDeviceRotation = false;
-
private Region mExclusionRegion;
private SystemGestureExclusionListenerCompat mExclusionListener;
private final List<ComponentName> mGestureBlockedActivities;
- private Runnable mOnDestroyFrozenTaskRunnable;
- /**
- * Set to true when user swipes to recents. In recents, we ignore the state of the recents
- * task list being frozen or not to allow the user to keep interacting with nav bar rotation
- * they went into recents with as opposed to defaulting to the default display rotation.
- * TODO: (b/156984037) For when user rotates after entering overview
- */
- private boolean mInOverview;
- private boolean mTaskListFrozen;
private boolean mIsUserSetupComplete;
@@ -186,6 +122,8 @@
mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
mDisplayId = mDefaultDisplay.getInfo().id;
runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this));
+ mRotationTouchHelper = new RotationTouchHelper(context);
+ runOnDestroy(mRotationTouchHelper::destroy);
// Register for user unlocked if necessary
mIsUserUnlocked = context.getSystemService(UserManager.class)
@@ -207,10 +145,6 @@
};
runOnDestroy(mExclusionListener::unregister);
- Resources resources = mContext.getResources();
- mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
- () -> QuickStepContract.getWindowCornerRadius(resources));
-
// Register for navigation mode changes
onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
@@ -231,6 +165,22 @@
}
}
+ if (SystemProperties.getBoolean("ro.support_one_handed_mode", false)) {
+ SecureSettingsObserver oneHandedEnabledObserver =
+ SecureSettingsObserver.newOneHandedSettingsObserver(
+ mContext, enabled -> mIsOneHandedModeEnabled = enabled);
+ oneHandedEnabledObserver.register();
+ oneHandedEnabledObserver.dispatchOnChange();
+ runOnDestroy(oneHandedEnabledObserver::unregister);
+ }
+
+ SecureSettingsObserver swipeBottomEnabledObserver =
+ SecureSettingsObserver.newSwipeToNotificationSettingsObserver(
+ mContext, enabled -> mIsSwipeToNotificationEnabled = enabled);
+ swipeBottomEnabledObserver.register();
+ swipeBottomEnabledObserver.dispatchOnChange();
+ runOnDestroy(swipeBottomEnabledObserver::unregister);
+
SecureSettingsObserver userSetupObserver = new SecureSettingsObserver(
context.getContentResolver(),
e -> mIsUserSetupComplete = e,
@@ -241,38 +191,6 @@
userSetupObserver.register();
runOnDestroy(userSetupObserver::unregister);
}
-
- mOrientationListener = new OrientationEventListener(context) {
- @Override
- public void onOrientationChanged(int degrees) {
- int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
- mSensorRotation);
- if (newRotation == mSensorRotation) {
- return;
- }
-
- mSensorRotation = newRotation;
- mPrioritizeDeviceRotation = true;
-
- if (newRotation == mCurrentAppRotation) {
- // When user rotates device to the orientation of the foreground app after
- // quickstepping
- toggleSecondaryNavBarsForRotation();
- }
- }
- };
- }
-
- private void setupOrientationSwipeHandler() {
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
- mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
- .unregisterTaskStackListener(mFrozenTaskListener);
- runOnDestroy(mOnDestroyFrozenTaskRunnable);
- }
-
- private void destroyOrientationSwipeHandlerCallback() {
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
- mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
}
private void runOnDestroy(Runnable action) {
@@ -297,6 +215,15 @@
runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener));
}
+ /**
+ * Adds a listener for the one handed mode change,
+ * guaranteed to be called after the device state's mode has changed.
+ */
+ public void addOneHandedModeChangedCallback(OneHandedModeChangeListener listener) {
+ listener.onOneHandedModeChanged(mSysUiNavMode.addOneHandedOverlayChangeListener(listener));
+ runOnDestroy(() -> mSysUiNavMode.removeOneHandedOverlayChangeListener(listener));
+ }
+
@Override
public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
mDefaultDisplay.removeChangeListener(this);
@@ -310,14 +237,6 @@
}
mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
-
- mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo());
- if (!mMode.hasGestures && newMode.hasGestures) {
- setupOrientationSwipeHandler();
- } else if (mMode.hasGestures && !newMode.hasGestures){
- destroyOrientationSwipeHandlerCallback();
- }
-
mMode = newMode;
}
@@ -328,28 +247,15 @@
return;
}
- mDisplayRotation = info.rotation;
-
if (!mMode.hasGestures) {
return;
}
mNavBarPosition = new NavBarPosition(mMode, info);
- updateGestureTouchRegions();
- mOrientationTouchTransformer.createOrAddTouchRegion(info);
- mCurrentAppRotation = mDisplayRotation;
+ }
- /* Update nav bars on the following:
- * a) if this is coming from an activity rotation OR
- * aa) we launch an app in the orientation that user is already in
- * b) We're not in overview, since overview will always be portrait (w/o home rotation)
- * c) We're actively in quickswitch mode
- */
- if ((mPrioritizeDeviceRotation
- || mCurrentAppRotation == mSensorRotation) // switch to an app of orientation user is in
- && !mInOverview
- && mTaskListFrozen) {
- toggleSecondaryNavBarsForRotation();
- }
+ @Override
+ public void onOneHandedModeChanged(int newGesturalHeight) {
+ mRotationTouchHelper.setGesturalHeight(newGesturalHeight);
}
/**
@@ -464,7 +370,7 @@
*/
public boolean canStartSystemGesture() {
boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
- || mTaskListFrozen;
+ || mRotationTouchHelper.isTaskListFrozen();
return canStartWithNavHidden
&& (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
@@ -537,30 +443,10 @@
}
/**
- * Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
+ * @return whether screen pinning is enabled and active
*/
- public void updateGestureTouchRegions() {
- if (!mMode.hasGestures) {
- return;
- }
-
- mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
- }
-
- /**
- * @return whether the coordinates of the {@param event} is in the swipe up gesture region.
- */
- public boolean isInSwipeUpTouchRegion(MotionEvent event) {
- return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
- }
-
- /**
- * @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
- * is in the swipe up gesture region.
- */
- public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
- return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
- event.getY(pointerIndex));
+ public boolean isOneHandedModeActive() {
+ return (mSystemUiStateFlags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
}
/**
@@ -620,101 +506,39 @@
public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) {
return mAssistantAvailable
&& !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
- && mOrientationTouchTransformer.touchInAssistantRegion(ev)
+ && mRotationTouchHelper.touchInAssistantRegion(ev)
&& !isLockToAppActive()
&& !isGestureBlockedActivity(task);
}
/**
- * *May* apply a transform on the motion event if it lies in the nav bar region for another
- * orientation that is currently being tracked as a part of quickstep
+ * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
+ *
+ * @param ev The touch screen motion event.
+ * @return whether the given motion event can trigger the one handed mode.
*/
- void setOrientationTransformIfNeeded(MotionEvent event) {
- // negative coordinates bug b/143901881
- if (event.getX() < 0 || event.getY() < 0) {
- event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
+ public boolean canTriggerOneHandedAction(MotionEvent ev) {
+ if (!mIsOneHandedModeEnabled && !mIsSwipeToNotificationEnabled) {
+ return false;
}
- mOrientationTouchTransformer.transform(event);
+
+ final DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo();
+ return (mRotationTouchHelper.touchInOneHandedModeRegion(ev)
+ && displayInfo.rotation != Surface.ROTATION_90
+ && displayInfo.rotation != Surface.ROTATION_270
+ && displayInfo.metrics.densityDpi < DisplayMetrics.DENSITY_600);
}
- private void enableMultipleRegions(boolean enable) {
- mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
- notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
- if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
- // Clear any previous state from sensor manager
- mSensorRotation = mCurrentAppRotation;
- mOrientationListener.enable();
- } else {
- mOrientationListener.disable();
- }
+ public boolean isOneHandedModeEnabled() {
+ return mIsOneHandedModeEnabled;
}
- public void onStartGesture() {
- if (mTaskListFrozen) {
- // Prioritize whatever nav bar user touches once in quickstep
- // This case is specifically when user changes what nav bar they are using mid
- // quickswitch session before tasks list is unfrozen
- notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
- }
+ public boolean isSwipeToNotificationEnabled() {
+ return mIsSwipeToNotificationEnabled;
}
- void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
- BaseActivityInterface activityInterface) {
- if (endTarget == GestureState.GestureEndTarget.RECENTS) {
- mInOverview = true;
- if (!mTaskListFrozen) {
- // If we're in landscape w/o ever quickswitching, show the navbar in landscape
- enableMultipleRegions(true);
- }
- activityInterface.onExitOverview(this, mExitOverviewRunnable);
- } else if (endTarget == GestureState.GestureEndTarget.HOME) {
- enableMultipleRegions(false);
- } else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
- if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
- // First gesture to start quickswitch
- enableMultipleRegions(true);
- } else {
- notifySysuiOfCurrentRotation(
- mOrientationTouchTransformer.getCurrentActiveRotation());
- }
-
- // A new gesture is starting, reset the current device rotation
- // This is done under the assumption that the user won't rotate the phone and then
- // quickswitch in the old orientation.
- mPrioritizeDeviceRotation = false;
- } else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
- if (!mTaskListFrozen) {
- // touched nav bar but didn't go anywhere and not quickswitching, do nothing
- return;
- }
- notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
- }
- }
-
- private void notifySysuiOfCurrentRotation(int rotation) {
- UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
- .onQuickSwitchToNewTask(rotation));
- }
-
- /**
- * Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
- * notifies system UI of the primary rotation the user is interacting with
- */
- private void toggleSecondaryNavBarsForRotation() {
- mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
- notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
- }
-
- public int getCurrentActiveRotation() {
- if (!mMode.hasGestures) {
- // touch rotation should always match that of display for 3 button
- return mDisplayRotation;
- }
- return mOrientationTouchTransformer.getCurrentActiveRotation();
- }
-
- public int getDisplayRotation() {
- return mDisplayRotation;
+ public RotationTouchHelper getRotationTouchHelper() {
+ return mRotationTouchHelper;
}
public void dump(PrintWriter pw) {
@@ -726,9 +550,9 @@
pw.println(" assistantAvailable=" + mAssistantAvailable);
pw.println(" assistantDisabled="
+ QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
- pw.println(" currentActiveRotation=" + getCurrentActiveRotation());
- pw.println(" displayRotation=" + getDisplayRotation());
pw.println(" isUserUnlocked=" + mIsUserUnlocked);
- mOrientationTouchTransformer.dump(pw);
+ pw.println(" isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
+ pw.println(" isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
+ mRotationTouchHelper.dump(pw);
}
}
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
new file mode 100644
index 0000000..2b5e42a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -0,0 +1,376 @@
+/*
+ * 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;
+
+import static android.view.Surface.ROTATION_0;
+
+import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
+import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.quickstep.util.RecentsOrientedState;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class RotationTouchHelper implements
+ SysUINavigationMode.NavigationModeChangeListener,
+ DefaultDisplay.DisplayInfoChangeListener {
+
+ private final OrientationTouchTransformer mOrientationTouchTransformer;
+ private final DefaultDisplay mDefaultDisplay;
+ private final SysUINavigationMode mSysUiNavMode;
+ private final int mDisplayId;
+ private int mDisplayRotation;
+
+ private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
+
+ private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
+
+ private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
+ @Override
+ public void onRecentTaskListFrozenChanged(boolean frozen) {
+ mTaskListFrozen = frozen;
+ if (frozen || mInOverview) {
+ return;
+ }
+ enableMultipleRegions(false);
+ }
+
+ @Override
+ public void onActivityRotation(int displayId) {
+ super.onActivityRotation(displayId);
+ // This always gets called before onDisplayInfoChanged() so we know how to process
+ // the rotation in that method. This is done to avoid having a race condition between
+ // the sensor readings and onDisplayInfoChanged() call
+ if (displayId != mDisplayId) {
+ return;
+ }
+
+ mPrioritizeDeviceRotation = true;
+ if (mInOverview) {
+ // reset, launcher must be rotating
+ mExitOverviewRunnable.run();
+ }
+ }
+ };
+
+ private Runnable mExitOverviewRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mInOverview = false;
+ enableMultipleRegions(false);
+ }
+ };
+
+ /**
+ * Used to listen for when the device rotates into the orientation of the current foreground
+ * app. For example, if a user quickswitches from a portrait to a fixed landscape app and then
+ * rotates rotates the device to match that orientation, this triggers calls to sysui to adjust
+ * the navbar.
+ */
+ private OrientationEventListener mOrientationListener;
+ private int mSensorRotation = ROTATION_0;
+ /**
+ * This is the configuration of the foreground app or the app that will be in the foreground
+ * once a quickstep gesture finishes.
+ */
+ private int mCurrentAppRotation = -1;
+ /**
+ * This flag is set to true when the device physically changes orientations. When true, we will
+ * always report the current rotation of the foreground app whenever the display changes, as it
+ * would indicate the user's intention to rotate the foreground app.
+ */
+ private boolean mPrioritizeDeviceRotation = false;
+ private Runnable mOnDestroyFrozenTaskRunnable;
+ /**
+ * Set to true when user swipes to recents. In recents, we ignore the state of the recents
+ * task list being frozen or not to allow the user to keep interacting with nav bar rotation
+ * they went into recents with as opposed to defaulting to the default display rotation.
+ * TODO: (b/156984037) For when user rotates after entering overview
+ */
+ private boolean mInOverview;
+ private boolean mTaskListFrozen;
+
+
+ private final Context mContext;
+
+ public RotationTouchHelper(Context context) {
+ mContext = context;
+ Resources resources = mContext.getResources();
+ mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
+ mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
+ mDisplayId = mDefaultDisplay.getInfo().id;
+
+ mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
+ () -> QuickStepContract.getWindowCornerRadius(resources));
+
+ // Register for navigation mode changes
+ onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
+ runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
+
+ mOrientationListener = new OrientationEventListener(context) {
+ @Override
+ public void onOrientationChanged(int degrees) {
+ int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
+ mSensorRotation);
+ if (newRotation == mSensorRotation) {
+ return;
+ }
+
+ mSensorRotation = newRotation;
+ mPrioritizeDeviceRotation = true;
+
+ if (newRotation == mCurrentAppRotation) {
+ // When user rotates device to the orientation of the foreground app after
+ // quickstepping
+ toggleSecondaryNavBarsForRotation();
+ }
+ }
+ };
+ }
+
+ private void setupOrientationSwipeHandler() {
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
+ mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
+ .unregisterTaskStackListener(mFrozenTaskListener);
+ runOnDestroy(mOnDestroyFrozenTaskRunnable);
+ }
+
+ private void destroyOrientationSwipeHandlerCallback() {
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
+ mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
+ }
+
+ private void runOnDestroy(Runnable action) {
+ mOnDestroyActions.add(action);
+ }
+
+ /**
+ * Cleans up all the registered listeners and receivers.
+ */
+ public void destroy() {
+ for (Runnable r : mOnDestroyActions) {
+ r.run();
+ }
+ }
+
+ public boolean isTaskListFrozen() {
+ return mTaskListFrozen;
+ }
+
+ public boolean touchInAssistantRegion(MotionEvent ev) {
+ return mOrientationTouchTransformer.touchInAssistantRegion(ev);
+ }
+
+ public boolean touchInOneHandedModeRegion(MotionEvent ev) {
+ return mOrientationTouchTransformer.touchInOneHandedModeRegion(ev);
+ }
+
+ /**
+ * Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
+ */
+ public void updateGestureTouchRegions() {
+ if (!mMode.hasGestures) {
+ return;
+ }
+
+ mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
+ }
+
+ /**
+ * @return whether the coordinates of the {@param event} is in the swipe up gesture region.
+ */
+ public boolean isInSwipeUpTouchRegion(MotionEvent event) {
+ return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
+ }
+
+ /**
+ * @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
+ * is in the swipe up gesture region.
+ */
+ public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
+ return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
+ event.getY(pointerIndex));
+ }
+
+
+ @Override
+ public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+ mDefaultDisplay.removeChangeListener(this);
+ mDefaultDisplay.addChangeListener(this);
+ onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
+
+ mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo(),
+ mContext.getResources());
+ if (!mMode.hasGestures && newMode.hasGestures) {
+ setupOrientationSwipeHandler();
+ } else if (mMode.hasGestures && !newMode.hasGestures){
+ destroyOrientationSwipeHandlerCallback();
+ }
+
+ mMode = newMode;
+ }
+
+ public int getDisplayRotation() {
+ return mDisplayRotation;
+ }
+
+ @Override
+ public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+ if (info.id != mDisplayId|| flags == CHANGE_FRAME_DELAY) {
+ // ignore displays that aren't running launcher and frame refresh rate changes
+ return;
+ }
+
+ mDisplayRotation = info.rotation;
+
+ if (!mMode.hasGestures) {
+ return;
+ }
+ updateGestureTouchRegions();
+ mOrientationTouchTransformer.createOrAddTouchRegion(info);
+ mCurrentAppRotation = mDisplayRotation;
+
+ /* Update nav bars on the following:
+ * a) if this is coming from an activity rotation OR
+ * aa) we launch an app in the orientation that user is already in
+ * b) We're not in overview, since overview will always be portrait (w/o home rotation)
+ * c) We're actively in quickswitch mode
+ */
+ if ((mPrioritizeDeviceRotation
+ || mCurrentAppRotation == mSensorRotation) // switch to an app of orientation user is in
+ && !mInOverview
+ && mTaskListFrozen) {
+ toggleSecondaryNavBarsForRotation();
+ }
+ }
+
+ /**
+ * Sets the gestural height.
+ */
+ void setGesturalHeight(int newGesturalHeight) {
+ mOrientationTouchTransformer.setGesturalHeight(newGesturalHeight, mDefaultDisplay.getInfo(),
+ mContext.getResources());
+ }
+
+ /**
+ * *May* apply a transform on the motion event if it lies in the nav bar region for another
+ * orientation that is currently being tracked as a part of quickstep
+ */
+ void setOrientationTransformIfNeeded(MotionEvent event) {
+ // negative coordinates bug b/143901881
+ if (event.getX() < 0 || event.getY() < 0) {
+ event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
+ }
+ mOrientationTouchTransformer.transform(event);
+ }
+
+ private void enableMultipleRegions(boolean enable) {
+ mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
+ notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
+ if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
+ // Clear any previous state from sensor manager
+ mSensorRotation = mCurrentAppRotation;
+ mOrientationListener.enable();
+ } else {
+ mOrientationListener.disable();
+ }
+ }
+
+ public void onStartGesture() {
+ if (mTaskListFrozen) {
+ // Prioritize whatever nav bar user touches once in quickstep
+ // This case is specifically when user changes what nav bar they are using mid
+ // quickswitch session before tasks list is unfrozen
+ notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+ }
+ }
+
+ void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
+ BaseActivityInterface activityInterface) {
+ if (endTarget == GestureState.GestureEndTarget.RECENTS) {
+ mInOverview = true;
+ if (!mTaskListFrozen) {
+ // If we're in landscape w/o ever quickswitching, show the navbar in landscape
+ enableMultipleRegions(true);
+ }
+ activityInterface.onExitOverview(this, mExitOverviewRunnable);
+ } else if (endTarget == GestureState.GestureEndTarget.HOME) {
+ enableMultipleRegions(false);
+ } else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
+ if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
+ // First gesture to start quickswitch
+ enableMultipleRegions(true);
+ } else {
+ notifySysuiOfCurrentRotation(
+ mOrientationTouchTransformer.getCurrentActiveRotation());
+ }
+
+ // A new gesture is starting, reset the current device rotation
+ // This is done under the assumption that the user won't rotate the phone and then
+ // quickswitch in the old orientation.
+ mPrioritizeDeviceRotation = false;
+ } else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
+ if (!mTaskListFrozen) {
+ // touched nav bar but didn't go anywhere and not quickswitching, do nothing
+ return;
+ }
+ notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+ }
+ }
+
+ private void notifySysuiOfCurrentRotation(int rotation) {
+ UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
+ .onQuickSwitchToNewTask(rotation));
+ }
+
+ /**
+ * Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
+ * notifies system UI of the primary rotation the user is interacting with
+ */
+ private void toggleSecondaryNavBarsForRotation() {
+ mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
+ notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+ }
+
+ public int getCurrentActiveRotation() {
+ if (!mMode.hasGestures) {
+ // touch rotation should always match that of display for 3 button
+ return mDisplayRotation;
+ }
+ return mOrientationTouchTransformer.getCurrentActiveRotation();
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("RotationTouchHelper:");
+ pw.println(" currentActiveRotation=" + getCurrentActiveRotation());
+ pw.println(" displayRotation=" + getDisplayRotation());
+ mOrientationTouchTransformer.dump(pw);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 05ce2a2..6b50218 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -16,14 +16,19 @@
package com.android.quickstep;
+import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_2_BUTTON;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_3_BUTTON;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.util.Log;
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.MainThreadInitializedObject;
@@ -37,16 +42,18 @@
public class SysUINavigationMode {
public enum Mode {
- THREE_BUTTONS(false, 0),
- TWO_BUTTONS(true, 1),
- NO_BUTTON(true, 2);
+ THREE_BUTTONS(false, 0, LAUNCHER_NAVIGATION_MODE_3_BUTTON),
+ TWO_BUTTONS(true, 1, LAUNCHER_NAVIGATION_MODE_2_BUTTON),
+ NO_BUTTON(true, 2, LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON);
public final boolean hasGestures;
public final int resValue;
+ public final LauncherEvent launcherEvent;
- Mode(boolean hasGestures, int resValue) {
+ Mode(boolean hasGestures, int resValue, LauncherEvent launcherEvent) {
this.hasGestures = hasGestures;
this.resValue = resValue;
+ this.launcherEvent = launcherEvent;
}
}
@@ -58,15 +65,19 @@
new MainThreadInitializedObject<>(SysUINavigationMode::new);
private static final String TAG = "SysUINavigationMode";
-
private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
"config_navBarInteractionMode";
+ private static final String TARGET_OVERLAY_PACKAGE = "android";
private final Context mContext;
private Mode mMode;
+ private int mNavBarGesturalHeight;
+
private final List<NavigationModeChangeListener> mChangeListeners = new ArrayList<>();
+ private final List<OneHandedModeChangeListener> mOneHandedOverlayChangeListeners =
+ new ArrayList<>();
public SysUINavigationMode(Context context) {
mContext = context;
@@ -76,8 +87,9 @@
@Override
public void onReceive(Context context, Intent intent) {
updateMode();
+ updateGesturalHeight();
}
- }, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
+ }, getPackageFilter(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED));
}
/** Updates navigation mode when needed. */
@@ -89,9 +101,35 @@
}
}
+ private void updateGesturalHeight() {
+ int newGesturalHeight = ResourceUtils.getDimenByName(
+ ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mContext.getResources(),
+ INVALID_RESOURCE_HANDLE);
+
+ if (newGesturalHeight == INVALID_RESOURCE_HANDLE) {
+ Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+ return;
+ }
+
+ if (mNavBarGesturalHeight != newGesturalHeight) {
+ mNavBarGesturalHeight = newGesturalHeight;
+ dispatchOneHandedOverlayChange();
+ }
+ }
+
private void initializeMode() {
- int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME);
- for(Mode m : Mode.values()) {
+ int modeInt = ResourceUtils.getIntegerByName(NAV_BAR_INTERACTION_MODE_RES_NAME,
+ mContext.getResources(), INVALID_RESOURCE_HANDLE);
+ mNavBarGesturalHeight = ResourceUtils.getDimenByName(
+ ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mContext.getResources(),
+ INVALID_RESOURCE_HANDLE);
+
+ if (modeInt == INVALID_RESOURCE_HANDLE) {
+ Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+ return;
+ }
+
+ for (Mode m : Mode.values()) {
if (m.resValue == modeInt) {
mMode = m;
}
@@ -104,6 +142,12 @@
}
}
+ private void dispatchOneHandedOverlayChange() {
+ for (OneHandedModeChangeListener listener : mOneHandedOverlayChangeListeners) {
+ listener.onOneHandedModeChanged(mNavBarGesturalHeight);
+ }
+ }
+
public Mode addModeChangeListener(NavigationModeChangeListener listener) {
mChangeListeners.add(listener);
return mMode;
@@ -113,20 +157,17 @@
mChangeListeners.remove(listener);
}
- public Mode getMode() {
- return mMode;
+ public int addOneHandedOverlayChangeListener(OneHandedModeChangeListener listener) {
+ mOneHandedOverlayChangeListeners.add(listener);
+ return mNavBarGesturalHeight;
}
- private static int getSystemIntegerRes(Context context, String resName) {
- Resources res = context.getResources();
- int resId = res.getIdentifier(resName, "integer", "android");
+ public void removeOneHandedOverlayChangeListener(OneHandedModeChangeListener listener) {
+ mOneHandedOverlayChangeListeners.remove(listener);
+ }
- if (resId != 0) {
- return res.getInteger(resId);
- } else {
- Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
- return -1;
- }
+ public Mode getMode() {
+ return mMode;
}
/** @return Whether we can remove the shelf from overview. */
@@ -144,10 +185,14 @@
public void dump(PrintWriter pw) {
pw.println("SysUINavigationMode:");
pw.println(" mode=" + mMode.name());
+ pw.println(" mNavBarGesturalHeight=:" + mNavBarGesturalHeight);
}
public interface NavigationModeChangeListener {
-
void onNavigationModeChanged(Mode newMode);
}
+
+ public interface OneHandedModeChangeListener {
+ void onOneHandedModeChanged(int newGesturalHeight);
+ }
}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 299e9e5..e4b05ae 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -352,10 +352,43 @@
if (mSystemUiProxy != null) {
try {
mSystemUiProxy.handleImageBundleAsScreenshot(screenImageBundle, locationInScreen,
- visibleInsets, task);
+ visibleInsets, task);
} catch (RemoteException e) {
Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
}
}
}
+
+ @Override
+ public void startOneHandedMode() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.startOneHandedMode();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call startOneHandedMode", e);
+ }
+ }
+ }
+
+ @Override
+ public void stopOneHandedMode() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.stopOneHandedMode();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call stopOneHandedMode", e);
+ }
+ }
+ }
+
+ @Override
+ public void expandNotificationPanel() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.expandNotificationPanel();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call expandNotificationPanel", e);
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index 0e2312b..81c4d0c 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -16,6 +16,7 @@
package com.android.quickstep.interaction;
import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_COMPLETED;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_BAD_ANGLE;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT;
@@ -48,6 +49,7 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.NavBarPosition;
import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
import com.android.systemui.shared.system.QuickStepContract;
@@ -74,6 +76,7 @@
private final PointF mAssistantStartDragPos = new PointF();
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
+ private final MotionPauseDetector mMotionPauseDetector;
private boolean mTouchCameFromAssistantCorner;
private boolean mTouchCameFromNavBar;
private boolean mPassedAssistantSlop;
@@ -100,6 +103,7 @@
new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
new NavBarPosition(Mode.NO_BUTTON, displayRotation),
null /*onInterceptTouch*/, this);
+ mMotionPauseDetector = new MotionPauseDetector(context);
final Resources resources = context.getResources();
mBottomGestureHeight =
@@ -177,12 +181,14 @@
}
mLaunchedAssistant = false;
mSwipeUpTouchTracker.init();
+ mMotionPauseDetector.clear();
+ mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
break;
case MotionEvent.ACTION_MOVE:
+ mLastPos.set(event.getX(), event.getY());
if (!mAssistantGestureActive) {
break;
}
- mLastPos.set(event.getX(), event.getY());
if (!mPassedAssistantSlop) {
// Normal gesture, ensure we pass the slop before we start tracking the gesture
@@ -213,6 +219,8 @@
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
+ mMotionPauseDetector.clear();
+ mMotionPauseDetector.setOnMotionPauseListener(null);
if (mGestureCallback != null && !intercepted && mTouchCameFromNavBar) {
mGestureCallback.onNavBarGestureAttempted(
HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, new PointF());
@@ -239,9 +247,17 @@
}
mSwipeUpTouchTracker.onMotionEvent(event);
mAssistantGestureDetector.onTouchEvent(event);
+ mMotionPauseDetector.addPosition(event);
+ mMotionPauseDetector.setDisallowPause(mLastPos.y >= mDisplaySize.y - mBottomGestureHeight);
return intercepted;
}
+ protected void onMotionPauseChanged(boolean isPaused) {
+ if (isPaused) {
+ VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+ }
+ }
+
/**
* Determine if angle is larger than threshold for assistant detection
*/
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 14e00dc..044e010 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -17,7 +17,8 @@
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
+import static com.android.quickstep.AbsSwipeUpHandler.MAX_SWIPE_DURATION;
import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
@@ -110,6 +111,7 @@
AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
+ mFakeIconView.setVisibility(View.INVISIBLE);
mFakeTaskView.setVisibility(View.INVISIBLE);
mFakeTaskView.setAlpha(1);
mRunningWindowAnim = null;
@@ -131,6 +133,7 @@
});
} else {
anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
+ anim.setViewAlpha(mFakeIconView, 0, ACCEL);
anim.addListener(resetTaskView);
}
if (onEndRunnable != null) {
@@ -202,7 +205,7 @@
// derivative of the scroll interpolator at zero, ie. 2.
long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
- HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) {
+ HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory() {
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
@@ -218,6 +221,24 @@
fakeHomeIconLeft + fakeHomeIconSizePx,
fakeHomeIconTop + fakeHomeIconSizePx);
}
+
+ @Override
+ public void update(RectF rect, float progress, float radius) {
+ mFakeIconView.setVisibility(View.VISIBLE);
+ mFakeIconView.update(rect, progress,
+ 1f - SHAPE_PROGRESS_DURATION /* shapeProgressStart */,
+ radius,
+ false, /* isOpening */
+ mFakeIconView, mDp,
+ false /* isVerticalBarLayout */);
+ mFakeIconView.setAlpha(1);
+ mFakeTaskView.setAlpha(getWindowAlpha(progress));
+ }
+
+ @Override
+ public void onCancel() {
+ mFakeIconView.setVisibility(View.INVISIBLE);
+ }
};
RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory);
windowAnim.start(mContext, velocityPxPerMs);
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index c1918c2..73f1f8c 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -28,6 +28,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.R;
+import com.android.launcher3.views.ClipIconView;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
@@ -46,6 +47,7 @@
final TextView mTitleTextView;
final TextView mSubtitleTextView;
final TextView mFeedbackView;
+ final ClipIconView mFakeIconView;
final View mFakeTaskView;
final View mRippleView;
final RippleDrawable mRippleDrawable;
@@ -66,6 +68,7 @@
mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view);
mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view);
mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
+ mFakeIconView = rootView.findViewById(R.id.gesture_tutorial_fake_icon_view);
mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view);
mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
mRippleDrawable = (RippleDrawable) mRippleView.getBackground();
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index eac45e9..059d158 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,6 +16,10 @@
package com.android.quickstep.logging;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.formatElapsedTime;
+
+import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
@@ -24,15 +28,18 @@
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
+import static java.lang.System.currentTimeMillis;
+
import android.content.Context;
import android.util.Log;
-import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.FolderContainer.ParentContainerCase;
import com.android.launcher3.logger.LauncherAtom.FolderIcon;
import com.android.launcher3.logger.LauncherAtom.FromState;
import com.android.launcher3.logger.LauncherAtom.ToState;
@@ -52,24 +59,26 @@
import com.android.systemui.shared.system.SysUiStatsLog;
import java.util.ArrayList;
+import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* This class calls StatsLog compile time generated methods.
*
* To see if the logs are properly sent to statsd, execute following command.
+ * <ul>
* $ wwdebug (to turn on the logcat printout)
* $ wwlogcat (see logcat with grep filter on)
* $ statsd_testdrive (see how ww is writing the proto to statsd buffer)
+ * </ul>
*/
public class StatsLogCompatManager extends StatsLogManager {
private static final String TAG = "StatsLog";
private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG);
-
- private static Context sContext;
-
+ private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
// LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
// from nano to lite, bake constant to prevent robo test failure.
@@ -77,8 +86,13 @@
private static final int FOLDER_HIERARCHY_OFFSET = 100;
private static final int SEARCH_RESULT_HIERARCHY_OFFSET = 200;
+ public static final CopyOnWriteArrayList<StatsLogConsumer> LOGS_CONSUMER =
+ new CopyOnWriteArrayList<>();
+
+ private final Context mContext;
+
public StatsLogCompatManager(Context context) {
- sContext = context;
+ mContext = context;
}
@Override
@@ -87,36 +101,40 @@
}
/**
- * Logs a ranking event and accompanying {@link InstanceId} and package name.
+ * Logs impression of the current workspace with additional launcher events.
*/
@Override
- public void log(EventEnum rankingEvent, InstanceId instanceId, @Nullable String packageName,
- int position) {
- SysUiStatsLog.write(SysUiStatsLog.RANKING_SELECTED,
- rankingEvent.getId() /* event_id = 1; */,
- packageName /* package_name = 2; */,
- instanceId.getId() /* instance_id = 3; */,
- position /* position_picked = 4; */);
- }
-
- /**
- * Logs the workspace layout information on the model thread.
- */
- @Override
- public void logSnapshot() {
- LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
- new SnapshotWorker());
+ public void logSnapshot(List<EventEnum> extraEvents) {
+ LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
+ new SnapshotWorker(extraEvents));
}
private class SnapshotWorker extends BaseModelUpdateTask {
private final InstanceId mInstanceId;
- SnapshotWorker() {
- mInstanceId = new InstanceIdSequence(
- 1 << 20 /*InstanceId.INSTANCE_ID_MAX*/).newInstanceId();
+ private final List<EventEnum> mExtraEvents;
+
+ SnapshotWorker(List<EventEnum> extraEvents) {
+ mInstanceId = new InstanceIdSequence(1 << 20 /*InstanceId.INSTANCE_ID_MAX*/)
+ .newInstanceId();
+ this.mExtraEvents = extraEvents;
}
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ long lastSnapshotTimeMillis = getDevicePrefs(mContext)
+ .getLong(LAST_SNAPSHOT_TIME_MILLIS, 0);
+ // Log snapshot only if previous snapshot was older than a day
+ if (currentTimeMillis() - lastSnapshotTimeMillis < DAY_IN_MILLIS) {
+ if (IS_VERBOSE) {
+ String elapsedTime = formatElapsedTime(
+ (currentTimeMillis() - lastSnapshotTimeMillis) / 1000);
+ Log.d(TAG, String.format(
+ "Skipped snapshot logging since previous snapshot was %s old.",
+ elapsedTime));
+ }
+ return;
+ }
+
IntSparseArrayMap<FolderInfo> folders = dataModel.folders.clone();
ArrayList<ItemInfo> workspaceItems = (ArrayList) dataModel.workspaceItems.clone();
ArrayList<LauncherAppWidgetInfo> appWidgets = (ArrayList) dataModel.appWidgets.clone();
@@ -132,16 +150,22 @@
LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo);
writeSnapshot(atomInfo, mInstanceId);
}
- } catch (Exception e) { }
+ } catch (Exception e) {
+ }
}
for (ItemInfo info : appWidgets) {
LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
writeSnapshot(atomInfo, mInstanceId);
}
+ mExtraEvents
+ .forEach(eventName -> logger().withInstanceId(mInstanceId).log(eventName));
+
+ getDevicePrefs(mContext).edit()
+ .putLong(LAST_SNAPSHOT_TIME_MILLIS, currentTimeMillis()).apply();
}
}
- private static void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
+ private void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
if (IS_VERBOSE) {
Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
}
@@ -175,6 +199,7 @@
private static class StatsCompatLogger implements StatsLogger {
private static final ItemInfo DEFAULT_ITEM_INFO = new ItemInfo();
+
private ItemInfo mItemInfo = DEFAULT_ITEM_INFO;
private InstanceId mInstanceId = DEFAULT_INSTANCE_ID;
private OptionalInt mRank = OptionalInt.empty();
@@ -253,36 +278,35 @@
return;
}
- if (mItemInfo.container < 0) {
- // Item is not within a folder. Write to StatsLog in same thread.
- write(event, mInstanceId, applyOverwrites(mItemInfo.buildProto()), mSrcState,
- mDstState);
+ LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+ if (mItemInfo.container < 0 || appState == null) {
+ // Write log on the model thread so that logs do not go out of order
+ // (for eg: drop comes after drag)
+ Executors.MODEL_EXECUTOR.execute(
+ () -> write(event, applyOverwrites(mItemInfo.buildProto())));
} else {
// Item is inside the folder, fetch folder info in a BG thread
// and then write to StatsLog.
- LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
+ appState.getModel().enqueueModelUpdateTask(
new BaseModelUpdateTask() {
@Override
public void execute(LauncherAppState app, BgDataModel dataModel,
AllAppsList apps) {
FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container);
- write(event, mInstanceId,
- applyOverwrites(mItemInfo.buildProto(folderInfo)),
- mSrcState, mDstState);
+ write(event, applyOverwrites(mItemInfo.buildProto(folderInfo)));
}
});
}
}
private LauncherAtom.ItemInfo applyOverwrites(LauncherAtom.ItemInfo atomInfo) {
- LauncherAtom.ItemInfo.Builder itemInfoBuilder =
- (LauncherAtom.ItemInfo.Builder) atomInfo.toBuilder();
+ LauncherAtom.ItemInfo.Builder itemInfoBuilder = atomInfo.toBuilder();
mRank.ifPresent(itemInfoBuilder::setRank);
mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
if (mFromState.isPresent() || mToState.isPresent() || mEditText.isPresent()) {
- FolderIcon.Builder folderIconBuilder = (FolderIcon.Builder) itemInfoBuilder
+ FolderIcon.Builder folderIconBuilder = itemInfoBuilder
.getFolderIcon()
.toBuilder();
mFromState.ifPresent(folderIconBuilder::setFromLabelState);
@@ -293,8 +317,11 @@
return itemInfoBuilder.build();
}
- private void write(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo,
- int srcState, int dstState) {
+ @WorkerThread
+ private void write(EventEnum event, LauncherAtom.ItemInfo atomInfo) {
+ InstanceId instanceId = mInstanceId;
+ int srcState = mSrcState;
+ int dstState = mDstState;
if (IS_VERBOSE) {
String name = (event instanceof Enum) ? ((Enum) event).name() :
event.getId() + "";
@@ -307,6 +334,10 @@
atomInfo));
}
+ for (StatsLogConsumer consumer : LOGS_CONSUMER) {
+ consumer.consume(event, atomInfo);
+ }
+
SysUiStatsLog.write(
SysUiStatsLog.LAUNCHER_EVENT,
SysUiStatsLog.LAUNCHER_UICHANGED__ACTION__DEFAULT_ACTION /* deprecated */,
@@ -337,7 +368,7 @@
}
private static int getCardinality(LauncherAtom.ItemInfo info) {
- switch (info.getContainerInfo().getContainerCase()){
+ switch (info.getContainerInfo().getContainerCase()) {
case PREDICTED_HOTSEAT_CONTAINER:
return info.getContainerInfo().getPredictedHotseatContainer().getCardinality();
case SEARCH_RESULT_CONTAINER:
@@ -402,9 +433,16 @@
}
private static int getPageId(LauncherAtom.ItemInfo info) {
+ if (info.hasTask()) {
+ return info.getTask().getIndex();
+ }
switch (info.getContainerInfo().getContainerCase()) {
case FOLDER:
return info.getContainerInfo().getFolder().getPageIndex();
+ case HOTSEAT:
+ return info.getContainerInfo().getHotseat().getIndex();
+ case PREDICTED_HOTSEAT_CONTAINER:
+ return info.getContainerInfo().getPredictedHotseatContainer().getIndex();
default:
return info.getContainerInfo().getWorkspace().getPageIndex();
}
@@ -413,6 +451,10 @@
private static int getParentPageId(LauncherAtom.ItemInfo info) {
switch (info.getContainerInfo().getContainerCase()) {
case FOLDER:
+ if (info.getContainerInfo().getFolder().getParentContainerCase()
+ == ParentContainerCase.HOTSEAT) {
+ return info.getContainerInfo().getFolder().getHotseat().getIndex();
+ }
return info.getContainerInfo().getFolder().getWorkspace().getPageIndex();
case SEARCH_RESULT_CONTAINER:
return info.getContainerInfo().getSearchResultContainer().getWorkspace()
@@ -446,7 +488,16 @@
return "ALLAPPS";
default:
return "INVALID";
-
}
}
+
+
+ /**
+ * Interface to get stats log while it is dispatched to the system
+ */
+ public interface StatsLogConsumer {
+
+ @WorkerThread
+ void consume(EventEnum event, LauncherAtom.ItemInfo atomInfo);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index d822b6c..81d24d7 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -33,7 +33,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Matrix;
@@ -48,7 +47,6 @@
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
@@ -58,7 +56,6 @@
import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.SysUINavigationMode;
-import com.android.systemui.shared.system.ConfigurationCompat;
import java.lang.annotation.Retention;
import java.util.function.IntConsumer;
@@ -91,6 +88,7 @@
private @SurfaceRotation int mTouchRotation = ROTATION_0;
private @SurfaceRotation int mDisplayRotation = ROTATION_0;
private @SurfaceRotation int mRecentsActivityRotation = ROTATION_0;
+ private @SurfaceRotation int mRecentsRotation = ROTATION_0 - 1;
// Launcher activity supports multiple orientation, but fallback activity does not
private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY = 1 << 0;
@@ -133,8 +131,6 @@
private int mFlags;
private int mPreviousRotation = ROTATION_0;
- @Nullable private Configuration mActivityConfiguration;
-
/**
* @param rotationChangeListener Callback for receiving rotation events when rotation watcher
* is enabled
@@ -170,11 +166,11 @@
}
/**
- * Sets the configuration for the recents activity, which could affect the activity's rotation
+ * Sets the rotation for the recents activity, which could affect the appearance of task view.
* @see #update(int, int)
*/
- public boolean setActivityConfiguration(Configuration activityConfiguration) {
- mActivityConfiguration = activityConfiguration;
+ public boolean setRecentsRotation(@SurfaceRotation int recentsRotation) {
+ mRecentsRotation = recentsRotation;
return update(mTouchRotation, mDisplayRotation);
}
@@ -231,9 +227,7 @@
@SurfaceRotation
private int inferRecentsActivityRotation(@SurfaceRotation int displayRotation) {
if (isRecentsActivityRotationAllowed()) {
- return mActivityConfiguration == null
- ? displayRotation
- : ConfigurationCompat.getWindowConfigurationRotation(mActivityConfiguration);
+ return mRecentsRotation < ROTATION_0 ? displayRotation : mRecentsRotation;
} else {
return ROTATION_0;
}
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 40265c4..0f6671d 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -188,7 +188,7 @@
SYS_UI_NAVIGATION_MODE.removeModeChangeListener(listener));
Wait.atMost(() -> "Navigation mode didn't change to " + expectedMode,
- () -> currentSysUiNavigationMode() == expectedMode, 60000 /* b/148422894 */,
+ () -> currentSysUiNavigationMode() == expectedMode, WAIT_TIME_MS,
launcher);
// b/139137636
// assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
@@ -202,7 +202,7 @@
Wait.atMost(() -> "Switching nav mode: "
+ launcher.getNavigationModeMismatchError(),
() -> launcher.getNavigationModeMismatchError() == null,
- 60000 /* b/148422894 */, launcher);
+ WAIT_TIME_MS, launcher);
AbstractLauncherUiTest.checkDetectedLeaks(launcher);
return true;
}
diff --git a/res/layout/floating_surface_view.xml b/res/layout/floating_surface_view.xml
new file mode 100644
index 0000000..434e84f
--- /dev/null
+++ b/res/layout/floating_surface_view.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.launcher3.views.FloatingSurfaceView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
diff --git a/res/layout/home_settings.xml b/res/layout/home_settings.xml
new file mode 100644
index 0000000..0f2461a
--- /dev/null
+++ b/res/layout/home_settings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <EditText
+ android:id="@+id/filter_box"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/developer_options_filter_margins"
+ android:hint="@string/developer_options_filter_hint"
+ android:visibility="gone"
+ />
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@android:id/list_container"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 947e635..969765f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -247,6 +247,9 @@
<dimen name="snackbar_min_text_size">12sp</dimen>
<dimen name="snackbar_max_text_size">14sp</dimen>
+<!-- Developer Options -->
+ <dimen name="developer_options_filter_margins">10dp</dimen>
+
<!-- Theming related -->
<dimen name="default_dialog_corner_radius">8dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 935bb40..80b511a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -348,7 +348,8 @@
<!-- content description for paused work apps list -->
<string name="work_apps_paused_content_description">Work profile is paused. Work apps can\’t send you notifications, use your battery, or access your location</string>
-
+ <!-- A hint shown in launcher settings develop options filter box -->
+ <string name="developer_options_filter_hint">Filter</string>
<!-- A tip shown pointing at work toggle -->
<string name="work_switch_tip">Pause work apps and notifications</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 25f21f3..3b9532e 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -149,6 +149,15 @@
<style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:navigationBarColor">@android:color/transparent</item>
+ <item name="preferenceTheme">@style/HomeSettingsPreferenceTheme</item>
+ </style>
+
+ <style name="HomeSettingsPreferenceTheme" parent="@style/PreferenceThemeOverlay.v14.Material">
+ <item name="preferenceFragmentCompatStyle">@style/HomeSettingsFragmentCompatStyle</item>
+ </style>
+
+ <style name="HomeSettingsFragmentCompatStyle" parent="@style/PreferenceFragment.Material">
+ <item name="android:layout">@layout/home_settings</item>
</style>
<!--
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index cd27a2d..ce37a30 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -62,7 +62,8 @@
TYPE_ALL_APPS_EDU,
TYPE_TASK_MENU,
- TYPE_OPTIONS_POPUP
+ TYPE_OPTIONS_POPUP,
+ TYPE_ICON_SURFACE
})
@Retention(RetentionPolicy.SOURCE)
public @interface FloatingViewType {}
@@ -80,16 +81,18 @@
// Popups related to quickstep UI
public static final int TYPE_TASK_MENU = 1 << 10;
public static final int TYPE_OPTIONS_POPUP = 1 << 11;
+ public static final int TYPE_ICON_SURFACE = 1 << 12;
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
- | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU;
+ | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU
+ | TYPE_ICON_SURFACE;
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
- | TYPE_ALL_APPS_EDU;
+ | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE;
// Usually we show the back button when a floating view is open. Instead, hide for these types.
public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 9cb8cf2..112126b 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -45,7 +45,6 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
-import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
@@ -82,7 +81,7 @@
super.onCreate(savedInstanceState);
- mIsSafeModeEnabled = TraceHelper.whitelistIpcs("isSafeMode",
+ mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> getPackageManager().isSafeMode());
DefaultDisplay.INSTANCE.get(this).addChangeListener(this);
@@ -154,8 +153,7 @@
public abstract ActivityOptions getActivityLaunchOptions(View v);
- public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item,
- @Nullable String sourceContainer) {
+ public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item) {
if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) {
Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
return false;
@@ -176,17 +174,13 @@
&& !((WorkspaceItemInfo) item).isPromise();
if (isShortcut) {
// Shortcuts need some special checks due to legacy reasons.
- startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
+ startShortcutIntentSafely(intent, optsBundle, item);
} else if (user == null || user.equals(Process.myUserHandle())) {
// Could be launching some bookkeeping activity
startActivity(intent, optsBundle);
- AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
- Process.myUserHandle(), sourceContainer);
} else {
getSystemService(LauncherApps.class).startMainActivity(
intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
- AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), user,
- sourceContainer);
}
getUserEventDispatcher().logAppLaunch(v, intent, user);
if (item != null) {
@@ -206,8 +200,7 @@
.log(LAUNCHER_APP_LAUNCH_TAP);
}
- private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info,
- @Nullable String sourceContainer) {
+ private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
try {
StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
try {
@@ -221,8 +214,6 @@
String id = ((WorkspaceItemInfo) info).getDeepShortcutId();
String packageName = intent.getPackage();
startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user);
- AppLaunchTracker.INSTANCE.get(this).onStartShortcut(packageName, id, info.user,
- sourceContainer);
} else {
// Could be launching some bookkeeping activity
startActivity(intent, optsBundle);
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 41eeb78..c55b46b 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -16,11 +16,8 @@
package com.android.launcher3;
-import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
-
import android.content.Context;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -29,8 +26,6 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.RecyclerViewFastScroller;
@@ -183,10 +178,6 @@
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onScrollStateChanged: " + state);
- }
-
if (state == SCROLL_STATE_IDLE) {
AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
}
@@ -196,23 +187,5 @@
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (isLayoutSuppressed()) info.setScrollable(false);
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS,
- "onInitializeAccessibilityNodeInfo, scrollable: " + info.isScrollable());
- }
- }
-
- @Override
- public void setLayoutFrozen(boolean frozen) {
- final boolean changing = frozen != isLayoutSuppressed();
- super.setLayoutFrozen(frozen);
- if (changing) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "setLayoutFrozen " + frozen
- + " @ " + Log.getStackTraceString(new Throwable()));
- ActivityContext.lookupContext(getContext()).getDragLayer()
- .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
- }
- }
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 60b6da6..198f13d 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -331,10 +331,7 @@
public boolean onTouchEvent(MotionEvent event) {
// ignore events if they happen in padding area
if (event.getAction() == MotionEvent.ACTION_DOWN
- && (event.getY() < getPaddingTop()
- || event.getX() < getPaddingLeft()
- || event.getY() > getHeight() - getPaddingBottom()
- || event.getX() > getWidth() - getPaddingRight())) {
+ && shouldIgnoreTouchDown(event.getX(), event.getY())) {
return false;
}
if (isLongClickable()) {
@@ -347,6 +344,16 @@
}
}
+ /**
+ * Returns true if the touch down at the provided position be ignored
+ */
+ protected boolean shouldIgnoreTouchDown(float x, float y) {
+ return y < getPaddingTop()
+ || x < getPaddingLeft()
+ || y > getHeight() - getPaddingBottom()
+ || x > getWidth() - getPaddingRight();
+ }
+
void setStayPressed(boolean stayPressed) {
mStayPressed = stayPressed;
refreshDrawableState();
@@ -607,6 +614,9 @@
@Override
public void setIconVisible(boolean visible) {
mIsIconVisible = visible;
+ if (!mIsIconVisible) {
+ resetIconScale();
+ }
Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
applyCompoundDrawables(icon);
}
@@ -746,11 +756,14 @@
@Override
public SafeCloseable prepareDrawDragView() {
- if (getIcon() instanceof FastBitmapDrawable) {
- FastBitmapDrawable icon = (FastBitmapDrawable) getIcon();
- icon.setScale(1f);
- }
+ resetIconScale();
setForceHideDot(true);
return () -> { };
}
+
+ private void resetIconScale() {
+ if (mIcon instanceof FastBitmapDrawable) {
+ ((FastBitmapDrawable) mIcon).setScale(1f);
+ }
+ }
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index e3cd0bd..49caa93 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -369,8 +369,10 @@
if (allAppsHasDifferentNumColumns()) {
allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, mInfo.metrics);
allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, mInfo.metrics);
- allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y;
allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+ // We use 4 below to ensure labels are closer to their corresponding icon.
+ allAppsCellHeightPx = Math.round(allAppsIconSizePx + allAppsIconTextSizePx
+ + (4 * allAppsIconDrawablePaddingPx));
} else {
allAppsIconSizePx = iconSizePx;
allAppsIconTextSizePx = iconTextSizePx;
diff --git a/src/com/android/launcher3/GestureNavContract.java b/src/com/android/launcher3/GestureNavContract.java
new file mode 100644
index 0000000..2a7e629
--- /dev/null
+++ b/src/com/android/launcher3/GestureNavContract.java
@@ -0,0 +1,101 @@
+/*
+ * 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;
+
+import static android.content.Intent.EXTRA_COMPONENT_NAME;
+import static android.content.Intent.EXTRA_USER;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.SurfaceControl;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Class to encapsulate the handshake protocol between Launcher and gestureNav.
+ */
+public class GestureNavContract {
+
+ private static final String TAG = "GestureNavContract";
+
+ public static final String EXTRA_GESTURE_CONTRACT = "gesture_nav_contract_v1";
+ public static final String EXTRA_ICON_POSITION = "gesture_nav_contract_icon_position";
+ public static final String EXTRA_ICON_SURFACE = "gesture_nav_contract_surface_control";
+ public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
+
+ public final ComponentName componentName;
+ public final UserHandle user;
+
+ private final Message mCallback;
+
+ public GestureNavContract(ComponentName componentName, UserHandle user, Message callback) {
+ this.componentName = componentName;
+ this.user = user;
+ this.mCallback = callback;
+ }
+
+ /**
+ * Sends the position information to the receiver
+ */
+ @TargetApi(Build.VERSION_CODES.R)
+ public void sendEndPosition(RectF position, @Nullable SurfaceControl surfaceControl) {
+ Bundle result = new Bundle();
+ result.putParcelable(EXTRA_ICON_POSITION, position);
+ result.putParcelable(EXTRA_ICON_SURFACE, surfaceControl);
+
+ Message callback = Message.obtain();
+ callback.copyFrom(mCallback);
+ callback.setData(result);
+
+ try {
+ callback.replyTo.send(callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending icon position", e);
+ }
+ }
+
+ /**
+ * Clears and returns the GestureNavContract if it was present in the intent.
+ */
+ public static GestureNavContract fromIntent(Intent intent) {
+ if (!Utilities.ATLEAST_R) {
+ return null;
+ }
+ Bundle extras = intent.getBundleExtra(EXTRA_GESTURE_CONTRACT);
+ if (extras == null) {
+ return null;
+ }
+ intent.removeExtra(EXTRA_GESTURE_CONTRACT);
+
+ ComponentName componentName = extras.getParcelable(EXTRA_COMPONENT_NAME);
+ UserHandle userHandle = extras.getParcelable(EXTRA_USER);
+ Message callback = extras.getParcelable(EXTRA_REMOTE_CALLBACK);
+
+ if (componentName != null && userHandle != null && callback != null
+ && callback.replyTo != null) {
+ return new GestureNavContract(componentName, userHandle, callback);
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d06ae7a..e49c455 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -21,6 +21,7 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
import static com.android.launcher3.InstallShortcutReceiver.FLAG_DRAG_AND_DROP;
@@ -168,6 +169,7 @@
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.FloatingSurfaceView;
import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.views.ScrimView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -509,6 +511,7 @@
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE);
+ AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE);
}
@Override
@@ -814,7 +817,7 @@
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- startActivitySafely(v, intent, null, null);
+ startActivitySafely(v, intent, null);
} else {
// TODO: Show a snack bar with link to settings
Toast.makeText(this, getString(R.string.msg_no_phone_permission,
@@ -1450,6 +1453,7 @@
mLauncherCallbacks.onHomeIntent(internalStateHandled);
}
mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
+ handleGestureContract(intent);
} else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
getStateManager().goToState(ALL_APPS, alreadyOnHome);
}
@@ -1458,6 +1462,17 @@
}
/**
+ * Handles gesture nav contract
+ */
+ protected void handleGestureContract(Intent intent) {
+ GestureNavContract gnc = GestureNavContract.fromIntent(intent);
+ if (gnc != null) {
+ AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE);
+ FloatingSurfaceView.show(this, gnc);
+ }
+ }
+
+ /**
* Hides the keyboard if visible
*/
public void hideKeyboard() {
@@ -1535,7 +1550,6 @@
mOverlayManager.onActivityDestroyed(this);
mAppTransitionManager.unregisterRemoteAnimations();
mUserChangedCallbackCloseable.close();
- mAllAppsController.onActivityDestroyed();
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1862,13 +1876,12 @@
}
@Override
- public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
- @Nullable String sourceContainer) {
+ public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
if (!hasBeenResumed()) {
// Workaround an issue where the WM launch animation is clobbered when finishing the
// recents animation into launcher. Defer launching the activity until Launcher is
// next resumed.
- addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
+ addOnResumeCallback(() -> startActivitySafely(v, intent, item));
if (mOnDeferredActivityLaunchCallback != null) {
mOnDeferredActivityLaunchCallback.run();
mOnDeferredActivityLaunchCallback = null;
@@ -1876,7 +1889,7 @@
return true;
}
- boolean success = super.startActivitySafely(v, intent, item, sourceContainer);
+ boolean success = super.startActivitySafely(v, intent, item);
if (success && v instanceof BubbleTextView) {
// This is set to the view that launched the activity that navigated the user away
// from launcher. Since there is no callback for when the activity has finished
@@ -2481,7 +2494,7 @@
* @param updated list of shortcuts which have changed.
*/
@Override
- public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) {
+ public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
if (!updated.isEmpty()) {
mWorkspace.updateShortcuts(updated);
}
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index 403d779..c9fb75a 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -22,6 +22,7 @@
public class ResourceUtils {
public static final int DEFAULT_NAVBAR_VALUE = 48;
+ public static final int INVALID_RESOURCE_HANDLE = -1;
public static final String NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE = "navigation_bar_width";
public static final String NAVBAR_BOTTOM_GESTURE_SIZE = "navigation_bar_gesture_height";
@@ -51,7 +52,13 @@
return val;
}
+ public static int getIntegerByName(String resName, Resources res, int defaultValue) {
+ int resId = res.getIdentifier(resName, "integer", "android");
+ return resId != 0 ? res.getInteger(resId) : defaultValue;
+ }
+
public static int pxFromDp(float size, DisplayMetrics metrics) {
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
+ return size < 0 ? INVALID_RESOURCE_HANDLE : Math.round(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
}
}
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 499b54f..56875bb 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -39,7 +39,7 @@
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.LoggerUtils;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -218,13 +218,16 @@
public void onDrop(DragObject d, DragOptions options) {
// Defer onComplete
d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
+
super.onDrop(d, options);
+ StatsLogger logger = mStatsLogManager.logger().withInstanceId(d.logInstanceId);
+ if (d.originalDragInfo != null) {
+ logger.withItemInfo(d.originalDragInfo);
+ }
if (mCurrentAccessibilityAction == UNINSTALL) {
- mStatsLogManager.logger().withInstanceId(d.logInstanceId)
- .log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL);
+ logger.log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL);
} else if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
- mStatsLogManager.logger().withInstanceId(d.logInstanceId)
- .log(LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST);
+ logger.log(LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST);
}
}
@@ -283,8 +286,7 @@
return null;
}
if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
- AppLaunchTracker.INSTANCE.get(getContext()).onDismissApp(info.getTargetComponent(),
- info.user, AppLaunchTracker.CONTAINER_PREDICTIONS);
+ // We sent the log event, nothing else left to do
return null;
}
// else: mCurrentAccessibilityAction == UNINSTALL
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 1441e0b..15e0daa 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -115,6 +115,7 @@
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.List;
import java.util.function.Predicate;
/**
@@ -309,7 +310,9 @@
// In portrait, we want the pages spaced such that there is no
// overhang of the previous / next page into the current page viewport.
// We assume symmetrical padding in portrait mode.
- setPageSpacing(Math.max(grid.edgeMarginPx, padding.left + 1));
+ int maxInsets = Math.max(insets.left, insets.right);
+ int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1);
+ setPageSpacing(Math.max(maxInsets, maxPadding));
}
@@ -3085,7 +3088,7 @@
return false;
}
- void updateShortcuts(ArrayList<WorkspaceItemInfo> shortcuts) {
+ void updateShortcuts(List<WorkspaceItemInfo> shortcuts) {
final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
ItemOperator op = (info, v) -> {
if (v instanceof BubbleTextView && updates.contains(info)) {
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 8ec4d27..d653160 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -41,7 +41,6 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
-import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.PackageManagerHelper;
@@ -270,7 +269,7 @@
View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
parent, false);
searchMarketView.setOnClickListener(v -> mLauncher.startActivitySafely(
- v, mMarketSearchIntent, null, AppLaunchTracker.CONTAINER_SEARCH));
+ v, mMarketSearchIntent, null));
return new ViewHolder(searchMarketView);
case VIEW_TYPE_ALL_APPS_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
diff --git a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
new file mode 100644
index 0000000..5af9113
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
@@ -0,0 +1,186 @@
+/*
+ * 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.allapps;
+
+import android.graphics.Insets;
+import android.os.Build;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowInsetsAnimationController;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.core.os.BuildCompat;
+
+/**
+ * Handles IME over all apps to be synchronously transitioning along with the passed in
+ * root inset.
+ */
+public class AllAppsInsetTransitionController {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "AllAppsInsetTransitionController";
+ private static final Interpolator LINEAR = new LinearInterpolator();
+
+ private WindowInsetsAnimationController mAnimationController;
+ private WindowInsetsAnimationControlListener mCurrentRequest;
+
+ private float mAllAppsHeight;
+
+ private int mDownInsetBottom;
+ private boolean mShownAtDown;
+
+ private int mHiddenInsetBottom;
+ private int mShownInsetBottom;
+
+ private float mDown, mCurrent;
+ private View mApps;
+
+ public AllAppsInsetTransitionController(float allAppsHeight, View appsView) {
+ mAllAppsHeight = allAppsHeight;
+ mApps = appsView;
+ }
+
+ /**
+ * Initializes member variables and requests for the {@link WindowInsetsAnimationController}
+ * object.
+ *
+ * @param progress value between 0..1
+ */
+ @RequiresApi(api = Build.VERSION_CODES.R)
+ public void onDragStart(float progress) {
+ if (!BuildCompat.isAtLeastR()) return;
+ onAnimationEnd(progress);
+
+ mDown = progress * mAllAppsHeight;
+
+ // Below two values are sometimes incorrect. Possibly a platform bug
+ mDownInsetBottom = mApps.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom;
+ mShownAtDown = mApps.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
+
+ // override this value based on what it should actually be.
+ mShownAtDown = Float.compare(progress, 1f) == 0 ? false : true;
+ mDownInsetBottom = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
+ if (DEBUG) {
+ Log.d(TAG, "\nonDragStart mDownInsets=" + mDownInsetBottom
+ + " mShownAtDown =" + mShownAtDown);
+ }
+
+ mApps.getWindowInsetsController().controlWindowInsetsAnimation(
+ WindowInsets.Type.ime(), -1 /* no predetermined duration */, LINEAR, null,
+ mCurrentRequest = new WindowInsetsAnimationControlListener() {
+
+ @Override
+ public void onReady(WindowInsetsAnimationController controller, int types) {
+ if (DEBUG) {
+ Log.d(TAG, "Listener.onReady " + (mCurrentRequest == this));
+ }
+ if (mCurrentRequest == this) {
+ mAnimationController = controller;
+ } else {
+ controller.finish(mShownAtDown);
+ }
+ }
+
+ @Override
+ public void onFinished(WindowInsetsAnimationController controller) {
+ // when screen lock happens, then this method get called
+ mAnimationController.finish(false);
+ mAnimationController = null;
+ if (DEBUG) {
+ Log.d(TAG, "Listener.onFinished ctrl=" + controller);
+ }
+ }
+
+ @Override
+ public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
+ mAnimationController = null;
+ if (controller != null) {
+ controller.finish(mShownAtDown);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Listener.onCancelled ctrl=" + controller);
+ }
+ }
+ });
+ }
+
+ /**
+ * Handles the translation using the progress.
+ *
+ * @param progress value between 0..1
+ */
+ @RequiresApi(api = 30)
+ public void setProgress(float progress) {
+ if (!BuildCompat.isAtLeastR()) return;
+ // progress that equals to 0 or 1 is error prone. Do not use them.
+ // Instead use onDragStart and onAnimationEnd
+ if (mAnimationController == null || progress <= 0f || progress >= 1f) return;
+
+ mCurrent = progress * mAllAppsHeight;
+ mHiddenInsetBottom = mAnimationController.getHiddenStateInsets().bottom; // 0
+ mShownInsetBottom = mAnimationController.getShownStateInsets().bottom; // 1155
+
+ int shift = mShownAtDown ? 0 : (int) (mAllAppsHeight - mShownInsetBottom);
+
+ int inset = (int) (mDownInsetBottom + (mDown - mCurrent) - shift);
+
+ if (DEBUG) {
+ Log.d(TAG, "updateInset mCurrent=" + mCurrent + " mDown="
+ + mDown + " hidden=" + mHiddenInsetBottom
+ + " shown=" + mShownInsetBottom
+ + " mDownInsets.bottom=" + mDownInsetBottom + " inset:" + inset
+ + " shift: " + shift);
+ }
+ final int start = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
+ final int end = mShownAtDown ? mHiddenInsetBottom : mShownInsetBottom;
+ inset = Math.max(inset, mHiddenInsetBottom);
+ inset = Math.min(inset, mShownInsetBottom);
+ Log.d(TAG, "updateInset inset:" + inset);
+
+ mAnimationController.setInsetsAndAlpha(
+ Insets.of(0, 0, 0, inset),
+ 1f, (inset - start) / (float) (end - start));
+ }
+
+ /**
+ * Report to the animation controller that we no longer plan to translate anymore.
+ *
+ * @param progress value between 0..1
+ */
+ @RequiresApi(api = 30)
+ public void onAnimationEnd(float progress) {
+ if (DEBUG) {
+ Log.d(TAG, "endTranslation progress=" + progress
+ + " mAnimationController=" + mAnimationController);
+ }
+
+ if (mAnimationController == null) return;
+
+ if (Float.compare(progress, 1f) == 0 /* bottom */) {
+ mAnimationController.finish(false /* gone */);
+ }
+ if (Float.compare(progress, 0f) == 0 /* top */) {
+ mAnimationController.finish(true /* show */);
+ }
+ mAnimationController = null;
+ mCurrentRequest = null;
+ }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a9b030e..5b00631 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.launcher3.allapps;
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
@@ -14,31 +29,28 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
-import android.content.Context;
import android.util.FloatProperty;
import android.view.View;
-import android.view.ViewGroup;
import android.view.animation.Interpolator;
-import android.widget.EditText;
+
+import androidx.core.os.BuildCompat;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.views.ScrimView;
-import com.android.systemui.plugins.AllAppsSearchPlugin;
-import com.android.systemui.plugins.PluginListener;
/**
* Handles AllApps view transition.
@@ -51,7 +63,7 @@
* closer to top or closer to the page indicator.
*/
public class AllAppsTransitionController implements StateHandler<LauncherState>,
- OnDeviceProfileChangeListener, PluginListener<AllAppsSearchPlugin> {
+ OnDeviceProfileChangeListener {
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@@ -85,10 +97,7 @@
private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent
private float mScrollRangeDelta = 0;
-
- // plugin related variables
- private AllAppsSearchPlugin mPlugin;
- private View mPluginContent;
+ private AllAppsInsetTransitionController mInsetController;
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
@@ -103,6 +112,10 @@
return mShiftRange;
}
+ public AllAppsInsetTransitionController getInsetController() {
+ return mInsetController;
+ }
+
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
mIsVerticalLayout = dp.isVerticalBarLayout();
@@ -130,8 +143,8 @@
float shiftCurrent = progress * mShiftRange;
mAppsView.setTranslationY(shiftCurrent);
- if (mPlugin != null) {
- mPlugin.setProgress(progress);
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+ mInsetController.setProgress(progress);
}
}
@@ -201,16 +214,12 @@
Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
Interpolator headerFade = config.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
- if (mPlugin == null) {
- setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
- setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
- mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
- hasAllAppsContent, setter, headerFade, allAppsFade);
- } else {
- setter.setViewAlpha(mPluginContent, hasAllAppsContent ? 1 : 0, allAppsFade);
- setter.setViewAlpha(mAppsView.getContentView(), 0, allAppsFade);
- setter.setViewAlpha(mAppsView.getScrollBar(), 0, allAppsFade);
- }
+
+ setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
+ setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
+ mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
+ hasAllAppsContent, setter, headerFade, allAppsFade);
+
mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
@@ -228,8 +237,12 @@
public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) {
mAppsView = appsView;
mScrimView = scrimView;
- PluginManagerWrapper.INSTANCE.get(mLauncher)
- .addPluginListener(this, AllAppsSearchPlugin.class, false);
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
+ mInsetController = new AllAppsInsetTransitionController(mShiftRange, mAppsView);
+ mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS,
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
}
/**
@@ -252,47 +265,11 @@
if (Float.compare(mProgress, 1f) == 0) {
mAppsView.reset(false /* animate */);
}
- updatePluginAnimationEnd();
- }
-
- @Override
- public void onPluginConnected(AllAppsSearchPlugin plugin, Context context) {
- mPlugin = plugin;
- mPluginContent = mLauncher.getLayoutInflater().inflate(
- R.layout.all_apps_content_layout, mAppsView, false);
- mAppsView.addView(mPluginContent);
- mPluginContent.setAlpha(0f);
- mPlugin.setup((ViewGroup) mPluginContent, mLauncher, mShiftRange);
- }
-
- @Override
- public void onPluginDisconnected(AllAppsSearchPlugin plugin) {
- mPlugin = null;
- mAppsView.removeView(mPluginContent);
- }
-
- public void onActivityDestroyed() {
- PluginManagerWrapper.INSTANCE.get(mLauncher).removePluginListener(this);
- }
-
- /** Used for the plugin to signal when drag starts happens
- * @param toAllApps*/
- public void onDragStart(boolean toAllApps) {
- if (mPlugin == null) return;
-
- if (toAllApps) {
- EditText editText = mAppsView.getSearchUiManager().setTextSearchEnabled(true);
- mPlugin.setEditText(editText);
- }
- mPlugin.onDragStart(toAllApps ? 1f : 0f);
- }
-
- private void updatePluginAnimationEnd() {
- if (mPlugin == null) return;
- mPlugin.onAnimationEnd(mProgress);
- if (Float.compare(mProgress, 1f) == 0) {
- mAppsView.getSearchUiManager().setTextSearchEnabled(false);
- mPlugin.setEditText(null);
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
+ mInsetController.onAnimationEnd(mProgress);
+ if (Float.compare(mProgress, 1f) == 0) {
+ mAppsView.getSearchUiManager().setTextSearchEnabled(true).requestFocus();
+ }
}
}
}
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index ed45749..df1cd26 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -28,7 +28,6 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Utilities;
-import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
@@ -112,8 +111,8 @@
return false;
}
return mLauncher.startActivitySafely(v,
- PackageManagerHelper.getMarketSearchIntent(mLauncher, query), null,
- AppLaunchTracker.CONTAINER_SEARCH);
+ PackageManagerHelper.getMarketSearchIntent(mLauncher, query), null
+ );
}
@Override
diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java
index eabd283..69716ea 100644
--- a/src/com/android/launcher3/anim/AlphaUpdateListener.java
+++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java
@@ -46,7 +46,8 @@
}
@Override
- public void onAnimationStart(Animator arg0) {
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
// We want the views to be visible for animation, so fade-in/out is visible
mView.setVisibility(View.VISIBLE);
}
diff --git a/src/com/android/launcher3/anim/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java
index 9905e81..b83417c 100644
--- a/src/com/android/launcher3/anim/AnimationSuccessListener.java
+++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java
@@ -19,6 +19,8 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import androidx.annotation.CallSuper;
+
/**
* Extension of {@link AnimatorListenerAdapter} for listening for non-cancelled animations
*/
@@ -27,6 +29,12 @@
protected boolean mCancelled = false;
@Override
+ @CallSuper
+ public void onAnimationStart(Animator animation) {
+ mCancelled = false;
+ }
+
+ @Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index ea0ff8b..31e0418 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -335,6 +335,7 @@
@Override
public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
mCancelled = false;
mDispatched = false;
}
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index d86bb17..1d32d1d 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -75,9 +75,6 @@
}
public static void sendScrollFinishedEventToTest(Context context) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendScrollFinishedEventToTest");
- }
final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
if (accessibilityManager == null) return;
@@ -97,9 +94,6 @@
AccessibilityEvent.TYPE_ANNOUNCEMENT);
e.setClassName(eventTag);
e.setParcelableData(data);
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendEventToTest " + e);
- }
accessibilityManager.sendAccessibilityEvent(e);
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 5c4a492..0ab74af 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -94,6 +94,10 @@
public static final BooleanFlag ENABLE_SUGGESTED_ACTIONS_OVERVIEW = new DeviceFlag(
"ENABLE_SUGGESTED_ACTIONS_OVERVIEW", false, "Show chip hints on the overview screen");
+
+ public static final BooleanFlag ENABLE_DEVICE_SEARCH = getDebugFlag(
+ "ENABLE_DEVICE_SEARCH", false, "Allows on device search in all apps");
+
public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
"FOLDER_NAME_SUGGEST", true,
"Suggests folder names instead of blank text.");
@@ -177,6 +181,10 @@
public static final BooleanFlag USER_EVENT_DISPATCHER = new DeviceFlag(
"USER_EVENT_DISPATCHER", true, "User event dispatcher collects logs.");
+ public static final BooleanFlag ENABLE_MINIMAL_DEVICE = new DeviceFlag(
+ "ENABLE_MINIMAL_DEVICE", false,
+ "Allow user to toggle minimal device mode in launcher.");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 1c18402..b91d1c3 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -741,7 +741,8 @@
@Override
protected View getAccessibilityInitialFocusView() {
- return mContent.getFirstItem();
+ View firstItem = mContent.getFirstItem();
+ return firstItem != null ? firstItem : super.getAccessibilityInitialFocusView();
}
private void closeComplete(boolean wasAnimated) {
@@ -1134,6 +1135,9 @@
* Rearranges the children based on their rank.
*/
public void rearrangeChildren() {
+ if (!mContent.areViewsBound()) {
+ return;
+ }
mContent.arrangeChildren(getIconsInReadingOrder());
mItemsInvalidated = true;
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 75275b2..32d061c 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -129,6 +129,8 @@
private float mDotScale;
private Animator mDotScaleAnim;
+ private Rect mTouchArea = new Rect();
+
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
private float mScaleForReorderBounce = 1f;
@@ -711,6 +713,11 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN
+ && shouldIgnoreTouchDown(event.getX(), event.getY())) {
+ return false;
+ }
+
// Call the superclass onTouchEvent first, because sometimes it changes the state to
// isPressed() on an ACTION_UP
super.onTouchEvent(event);
@@ -719,6 +726,15 @@
return true;
}
+ /**
+ * Returns true if the touch down at the provided position be ignored
+ */
+ protected boolean shouldIgnoreTouchDown(float x, float y) {
+ mTouchArea.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
+ getHeight() - getPaddingBottom());
+ return !mTouchArea.contains((int) x, (int) y);
+ }
+
@Override
public void cancelLongPress() {
super.cancelLongPress();
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 32531c0..a08dd30 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -500,6 +500,9 @@
* Reorders the items such that the {@param empty} spot moves to {@param target}
*/
public void realTimeReorder(int empty, int target) {
+ if (!mViewsBound) {
+ return;
+ }
completePendingPageChanges();
int delay = 0;
float delayAmount = START_VIEW_REORDER_DELAY;
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 885fb66..a424f84 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -28,6 +28,7 @@
import android.annotation.TargetApi;
import android.app.Fragment;
import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -56,6 +57,7 @@
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -83,6 +85,7 @@
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.uioverrides.PredictedAppIconInflater;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.views.ActivityContext;
@@ -123,7 +126,7 @@
*/
public static class PreviewContext extends ContextWrapper {
- private static final Set<MainThreadInitializedObject> WHITELIST = new HashSet<>(
+ private final Set<MainThreadInitializedObject> mAllowedObjects = new HashSet<>(
Arrays.asList(UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE));
@@ -157,7 +160,7 @@
*/
public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject,
MainThreadInitializedObject.ObjectProvider<T> provider) {
- if (!WHITELIST.contains(mainThreadInitializedObject)) {
+ if (!mAllowedObjects.contains(mainThreadInitializedObject)) {
throw new IllegalStateException("Leaking unknown objects");
}
if (mainThreadInitializedObject == LauncherAppState.INSTANCE) {
@@ -339,14 +342,33 @@
addInScreenFromBind(folderIcon, info);
}
+ private void inflateAndAddWidgets(LauncherAppWidgetInfo info,
+ Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
+ if (widgetProviderInfoMap == null) {
+ return;
+ }
+ AppWidgetProviderInfo providerInfo = widgetProviderInfoMap.get(
+ new ComponentKey(info.providerName, info.user));
+ if (providerInfo == null) {
+ return;
+ }
+ inflateAndAddWidgets(info, LauncherAppWidgetProviderInfo.fromProviderInfo(
+ getApplicationContext(), providerInfo));
+ }
+
private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
info.providerName);
if (widgetItem == null) {
return;
}
+ inflateAndAddWidgets(info, widgetItem.widgetInfo);
+ }
+
+ private void inflateAndAddWidgets(LauncherAppWidgetInfo info,
+ LauncherAppWidgetProviderInfo providerInfo) {
AppWidgetHostView view = new AppWidgetHostView(mContext);
- view.setAppWidget(-1, widgetItem.widgetInfo);
+ view.setAppWidget(-1, providerInfo);
view.updateAppWidget(null);
view.setTag(info);
addInScreenFromBind(view, info);
@@ -433,8 +455,13 @@
switch (itemInfo.itemType) {
case Favorites.ITEM_TYPE_APPWIDGET:
case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
- inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
- workspaceResult.mWidgetsModel);
+ if (mMigrated) {
+ inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
+ workspaceResult.mWidgetProvidersMap);
+ } else {
+ inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
+ workspaceResult.mWidgetsModel);
+ }
break;
default:
break;
@@ -542,7 +569,7 @@
}
return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
- mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel);
+ mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel, null);
}
}
@@ -569,9 +596,8 @@
public WorkspaceResult call() throws Exception {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI);
- mBgDataModel.widgetsModel.update(mApp, null);
return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
- mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel);
+ mBgDataModel.cachedPredictedItems, null, mWidgetProvidersMap);
}
}
@@ -593,14 +619,17 @@
private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
private final ArrayList<AppInfo> mCachedPredictedItems;
private final WidgetsModel mWidgetsModel;
+ private final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;
private WorkspaceResult(ArrayList<ItemInfo> workspaceItems,
ArrayList<LauncherAppWidgetInfo> appWidgets,
- ArrayList<AppInfo> cachedPredictedItems, WidgetsModel widgetsModel) {
+ ArrayList<AppInfo> cachedPredictedItems, WidgetsModel widgetsModel,
+ Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
mWorkspaceItems = workspaceItems;
mAppWidgets = appWidgets;
mCachedPredictedItems = cachedPredictedItems;
mWidgetsModel = widgetsModel;
+ mWidgetProvidersMap = widgetProviderInfoMap;
}
}
}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 8e23b65..de72534 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.logging;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_DOWN;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_OPEN_UP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
@@ -23,8 +22,6 @@
import android.content.Context;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.R;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logger.LauncherAtom.FromState;
@@ -33,11 +30,16 @@
import com.android.launcher3.userevent.LauncherLogProto;
import com.android.launcher3.util.ResourceBasedOverride;
+import java.util.List;
+
/**
* Handles the user event logging in R+.
+ *
+ * <pre>
* All of the event ids are defined here.
* Most of the methods are dummy methods for Launcher3
* Actual call happens only for Launcher variant that implements QuickStep.
+ * </pre>
*/
public class StatsLogManager implements ResourceBasedOverride {
@@ -49,8 +51,8 @@
public static final int LAUNCHER_STATE_UNCHANGED = 5;
/**
- * Returns proper launcher state enum for {@link StatsLogManager}
- * (to be removed during UserEventDispatcher cleanup)
+ * Returns proper launcher state enum for {@link StatsLogManager}(to be removed during
+ * UserEventDispatcher cleanup)
*/
public static int containerTypeToAtomState(int containerType) {
switch (containerType) {
@@ -67,9 +69,8 @@
}
/**
- * Returns event enum based on the two {@link ContainerType} transition information when
- * swipe gesture happens.
- * (to be removed during UserEventDispatcher cleanup)
+ * Returns event enum based on the two {@link ContainerType} transition information when swipe
+ * gesture happens(to be removed during UserEventDispatcher cleanup).
*/
public static EventEnum getLauncherAtomEvent(int startContainerType,
int targetContainerType, EventEnum fallbackEvent) {
@@ -273,7 +274,46 @@
LAUNCHER_SELECT_MODE_CLOSE(583),
@UiEvent(doc = "User tapped on the highlight items in select mode")
- LAUNCHER_SELECT_MODE_ITEM(584);
+ LAUNCHER_SELECT_MODE_ITEM(584),
+
+ @UiEvent(doc = "Notification dot on app icon enabled.")
+ LAUNCHER_NOTIFICATION_DOT_ENABLED(611),
+
+ @UiEvent(doc = "Notification dot on app icon disabled.")
+ LAUNCHER_NOTIFICATION_DOT_DISABLED(612),
+
+ @UiEvent(doc = "For new apps, add app icons to home screen enabled.")
+ LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_ENABLED(613),
+
+ @UiEvent(doc = "For new apps, add app icons to home screen disabled.")
+ LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_DISABLED(614),
+
+ @UiEvent(doc = "Home screen rotation is enabled when phone is rotated.")
+ LAUNCHER_HOME_SCREEN_ROTATION_ENABLED(615),
+
+ @UiEvent(doc = "Home screen rotation is disabled when phone is rotated.")
+ LAUNCHER_HOME_SCREEN_ROTATION_DISABLED(616),
+
+ @UiEvent(doc = "Suggestions in all apps list enabled.")
+ LAUNCHER_ALL_APPS_SUGGESTIONS_ENABLED(619),
+
+ @UiEvent(doc = "Suggestions in all apps list disabled.")
+ LAUNCHER_ALL_APPS_SUGGESTIONS_DISABLED(620),
+
+ @UiEvent(doc = "Suggestions on home screen is enabled.")
+ LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED(621),
+
+ @UiEvent(doc = "Suggestions on home screen is disabled.")
+ LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED(622),
+
+ @UiEvent(doc = "System navigation is 3 button mode.")
+ LAUNCHER_NAVIGATION_MODE_3_BUTTON(623),
+
+ @UiEvent(doc = "System navigation mode is 2 button mode.")
+ LAUNCHER_NAVIGATION_MODE_2_BUTTON(624),
+
+ @UiEvent(doc = "System navigation mode is 0 button mode/gesture navigation mode .")
+ LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON(625);
// ADD MORE
@@ -404,21 +444,8 @@
}
/**
- * Log an event with ranked-choice information along with package. Does nothing if event.getId()
- * <= 0.
- *
- * @param rankingEvent an enum implementing EventEnum interface.
- * @param instanceId An identifier obtained from an InstanceIdSequence.
- * @param packageName the package name of the relevant app, if known (null otherwise).
- * @param position the position picked.
+ * Logs impression of the current workspace with additional launcher events.
*/
- public void log(EventEnum rankingEvent, InstanceId instanceId, @Nullable String packageName,
- int position) {
- }
-
- /**
- * Logs snapshot, or impression of the current workspace.
- */
- public void logSnapshot() {
+ public void logSnapshot(List<EventEnum> additionalEvents) {
}
}
diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java
index 629a0ee..a93c0dd 100644
--- a/src/com/android/launcher3/model/AppLaunchTracker.java
+++ b/src/com/android/launcher3/model/AppLaunchTracker.java
@@ -17,13 +17,7 @@
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-import android.content.ComponentName;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
import com.android.launcher3.R;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -32,28 +26,8 @@
*/
public class AppLaunchTracker implements ResourceBasedOverride {
- /**
- * Derived from LauncherEvent proto.
- * TODO: Use proper descriptive constants
- */
- public static final String CONTAINER_DEFAULT = Integer.toString(ContainerType.WORKSPACE);
- public static final String CONTAINER_ALL_APPS = Integer.toString(ContainerType.ALLAPPS);
- public static final String CONTAINER_PREDICTIONS = Integer.toString(ContainerType.PREDICTION);
- public static final String CONTAINER_SEARCH = Integer.toString(ContainerType.SEARCHRESULT);
- public static final String CONTAINER_OVERVIEW = Integer.toString(ContainerType.OVERVIEW);
-
-
public static final MainThreadInitializedObject<AppLaunchTracker> INSTANCE =
forOverride(AppLaunchTracker.class, R.string.app_launch_tracker_class);
- public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
- @Nullable String container) { }
-
- public void onStartApp(ComponentName componentName, UserHandle user,
- @Nullable String container) { }
-
- public void onDismissApp(ComponentName componentName, UserHandle user,
- @Nullable String container){}
-
public void onReturnedToHome() { }
}
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 9013cba..d1e5017 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -22,7 +22,9 @@
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -30,7 +32,10 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
/**
* Extension of {@link ModelUpdateTask} with some utility methods
@@ -88,11 +93,27 @@
return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */);
}
-
- public void bindUpdatedWorkspaceItems(final ArrayList<WorkspaceItemInfo> updatedShortcuts) {
- if (!updatedShortcuts.isEmpty()) {
- scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(updatedShortcuts));
+ public void bindUpdatedWorkspaceItems(List<WorkspaceItemInfo> allUpdates) {
+ // Bind workspace items
+ List<WorkspaceItemInfo> workspaceUpdates = allUpdates.stream()
+ .filter(info -> info.id != ItemInfo.NO_ID)
+ .collect(Collectors.toList());
+ if (!workspaceUpdates.isEmpty()) {
+ scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(workspaceUpdates));
}
+
+ // Bind extra items if any
+ allUpdates.stream()
+ .mapToInt(info -> info.container)
+ .distinct()
+ .mapToObj(mDataModel.extraItems::get)
+ .filter(Objects::nonNull)
+ .forEach(this::bindExtraContainerItems);
+ }
+
+ public void bindExtraContainerItems(FixedContainerItems item) {
+ FixedContainerItems copy = item.clone();
+ scheduleCallbackTask(c -> c.bindExtraContainerItems(copy));
}
public void bindDeepShortcuts(BgDataModel dataModel) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 7524920..dfdc138 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -15,19 +15,25 @@
*/
package com.android.launcher3.model;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
+
import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.mapping;
+
import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
-import android.util.MutableInt;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
@@ -36,8 +42,10 @@
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.PromiseAppInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -50,14 +58,16 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.function.BiConsumer;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* All the data stored in-memory and managed by the LauncherModel
@@ -89,9 +99,9 @@
public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>();
/**
- * Map of ShortcutKey to the number of times it is pinned.
+ * Extra container based items
*/
- public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
+ public final IntSparseArrayMap<FixedContainerItems> extraItems = new IntSparseArrayMap<>();
/**
* List of all cached predicted items visible on home screen
@@ -128,8 +138,8 @@
appWidgets.clear();
folders.clear();
itemsIdMap.clear();
- pinnedShortcutCounts.clear();
deepShortcutMap.clear();
+ extraItems.clear();
}
/**
@@ -182,6 +192,7 @@
}
public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
+ ArraySet<UserHandle> updatedDeepShortcuts = new ArraySet<>();
for (ItemInfo item : items) {
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
@@ -200,14 +211,7 @@
workspaceItems.remove(item);
break;
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
- // Decrement pinned shortcut count
- ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
- MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
- if ((count == null || --count.value == 0)
- && !InstallShortcutReceiver.getPendingShortcuts(context)
- .contains(pinnedShortcut)) {
- unpinShortcut(context, pinnedShortcut);
- }
+ updatedDeepShortcuts.add(item.user);
// Fall through.
}
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
@@ -221,6 +225,7 @@
}
itemsIdMap.remove(item.id);
}
+ updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user));
}
public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
@@ -230,23 +235,7 @@
folders.put(item.id, (FolderInfo) item);
workspaceItems.add(item);
break;
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
- // Increment the count for the given shortcut
- ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
- MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
- if (count == null) {
- count = new MutableInt(1);
- pinnedShortcutCounts.put(pinnedShortcut, count);
- } else {
- count.value++;
- }
-
- // Since this is a new item, pin the shortcut in the system server.
- if (newItem && count.value == 1) {
- updatePinnedShortcuts(context, pinnedShortcut, List::add);
- }
- // Fall through
- }
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
@@ -271,36 +260,87 @@
appWidgets.add((LauncherAppWidgetInfo) item);
break;
}
+ if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ updateShortcutPinnedState(context, item.user);
+ }
}
/**
- * Removes the given shortcut from the current list of pinned shortcuts.
- * (Runs on background thread)
+ * Updates the deep shortucts state in system to match out internal model, pinning any missing
+ * shortcuts and unpinning any extra shortcuts.
*/
- public void unpinShortcut(Context context, ShortcutKey key) {
- updatePinnedShortcuts(context, key, List::remove);
+ public void updateShortcutPinnedState(Context context) {
+ for (UserHandle user : UserCache.INSTANCE.get(context).getUserProfiles()) {
+ updateShortcutPinnedState(context, user);
+ }
}
- private void updatePinnedShortcuts(Context context, ShortcutKey key,
- BiConsumer<List<String>, String> idOp) {
+ /**
+ * Updates the deep shortucts state in system to match out internal model, pinning any missing
+ * shortcuts and unpinning any extra shortcuts.
+ */
+ public synchronized void updateShortcutPinnedState(Context context, UserHandle user) {
if (GO_DISABLE_WIDGETS) {
return;
}
- String packageName = key.componentName.getPackageName();
- String id = key.getId();
- UserHandle user = key.user;
- List<String> pinnedIds = new ShortcutRequest(context, user)
- .forPackage(packageName)
- .query(PINNED)
- .stream()
- .map(ShortcutInfo::getId)
- .collect(Collectors.toCollection(ArrayList::new));
- idOp.accept(pinnedIds, id);
- try {
- context.getSystemService(LauncherApps.class).pinShortcuts(packageName, pinnedIds, user);
- } catch (SecurityException | IllegalStateException e) {
- Log.w(TAG, "Failed to pin shortcut", e);
+
+ // Collect all system shortcuts
+ QueryResult result = new ShortcutRequest(context, user)
+ .query(PINNED | FLAG_GET_KEY_FIELDS_ONLY);
+ if (!result.wasSuccess()) {
+ return;
}
+ // Map of packageName to shortcutIds that are currently in the system
+ Map<String, Set<String>> systemMap = result.stream()
+ .collect(groupingBy(ShortcutInfo::getPackage,
+ mapping(ShortcutInfo::getId, Collectors.toSet())));
+
+ // Collect all model shortcuts
+ Stream.Builder<WorkspaceItemInfo> itemStream = Stream.builder();
+ forAllWorkspaceItemInfos(user, itemStream::accept);
+ // Map of packageName to shortcutIds that are currently in our model
+ Map<String, Set<String>> modelMap = Stream.concat(
+ // Model shortcuts
+ itemStream.build()
+ .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+ .map(ShortcutKey::fromItemInfo),
+ // Pending shortcuts
+ InstallShortcutReceiver.getPendingShortcuts(context)
+ .stream().filter(si -> si.user.equals(user)))
+ .collect(groupingBy(ShortcutKey::getPackageName,
+ mapping(ShortcutKey::getId, Collectors.toSet())));
+
+ // Check for diff
+ for (Map.Entry<String, Set<String>> entry : modelMap.entrySet()) {
+ Set<String> modelShortcuts = entry.getValue();
+ Set<String> systemShortcuts = systemMap.remove(entry.getKey());
+ if (systemShortcuts == null) {
+ systemShortcuts = Collections.emptySet();
+ }
+
+ // Do not use .equals as it can vary based on the type of set
+ if (systemShortcuts.size() != modelShortcuts.size()
+ || !systemShortcuts.containsAll(modelShortcuts)) {
+ // Update system state for this package
+ try {
+ context.getSystemService(LauncherApps.class).pinShortcuts(
+ entry.getKey(), new ArrayList<>(modelShortcuts), user);
+ } catch (SecurityException | IllegalStateException e) {
+ Log.w(TAG, "Failed to pin shortcut", e);
+ }
+ }
+ }
+
+ // If there are any extra pinned shortcuts, remove them
+ systemMap.keySet().forEach(packageName -> {
+ // Update system state
+ try {
+ context.getSystemService(LauncherApps.class).pinShortcuts(
+ packageName, Collections.emptyList(), user);
+ } catch (SecurityException | IllegalStateException e) {
+ Log.w(TAG, "Failed to unpin shortcut", e);
+ }
+ });
}
/**
@@ -360,8 +400,40 @@
op.accept((WorkspaceItemInfo) info);
}
}
+
+ for (int i = extraItems.size() - 1; i >= 0; i--) {
+ for (ItemInfo info : extraItems.valueAt(i).items) {
+ if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) {
+ op.accept((WorkspaceItemInfo) info);
+ }
+ }
+ }
}
+ /**
+ * An object containing items corresponding to a fixed container
+ */
+ public static class FixedContainerItems {
+
+ public final int containerId;
+ public final List<ItemInfo> items;
+
+ public FixedContainerItems(int containerId) {
+ this(containerId, new ArrayList<>());
+ }
+
+ public FixedContainerItems(int containerId, List<ItemInfo> items) {
+ this.containerId = containerId;
+ this.items = items;
+ }
+
+ @Override
+ public FixedContainerItems clone() {
+ return new FixedContainerItems(containerId, Collections.unmodifiableList(items));
+ }
+ }
+
+
public interface Callbacks {
// If the launcher has permission to access deep shortcuts.
int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0;
@@ -384,7 +456,7 @@
void bindAppsAdded(IntArray newScreens,
ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
void bindPromiseAppProgressUpdated(PromiseAppInfo app);
- void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated);
+ void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated);
void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
void bindRestoreItemsChange(HashSet<ItemInfo> updates);
void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
@@ -393,6 +465,11 @@
void executeOnNextDraw(ViewOnDrawExecutor executor);
void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
+ /**
+ * Binds extra item provided any external source
+ */
+ default void bindExtraContainerItems(FixedContainerItems item) { }
+
void bindAllApplications(AppInfo[] apps, int flags);
/**
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index ebdfa8c..79467d3 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -406,7 +406,7 @@
* to speed up the search.
*/
private boolean findPlacement(DbEntry entry) {
- for (int y = mNextStartY; y > 0; y--) {
+ for (int y = mNextStartY; y >= (mScreenId == 0 ? 1 /* smartspace */ : 0); y--) {
for (int x = mNextStartX; x < mTrgX; x++) {
boolean fits = mOccupied.isRegionVacant(x, y, entry.spanX, entry.spanY);
boolean minFits = mOccupied.isRegionVacant(x, y, entry.minSpanX,
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 102ec31..bea0086 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -46,12 +46,10 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.LongSparseArray;
-import android.util.MutableInt;
import android.util.TimingLogger;
import androidx.annotation.WorkerThread;
-import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
@@ -94,7 +92,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CancellationException;
@@ -126,6 +123,8 @@
private final UserManagerState mUserManagerState = new UserManagerState();
+ protected Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;
+
private boolean mStopped;
public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
@@ -342,8 +341,6 @@
contentResolver.query(contentUri, null, null, null, null), contentUri, mApp,
mUserManagerState);
- Map<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
-
try {
final int appWidgetIdIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.APPWIDGET_ID);
@@ -650,10 +647,11 @@
final boolean wasProviderReady = !c.hasRestoreFlag(
LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
- if (widgetProvidersMap == null) {
- widgetProvidersMap = WidgetManagerHelper.getAllProvidersMap(context);
+ if (mWidgetProvidersMap == null) {
+ mWidgetProvidersMap = WidgetManagerHelper.getAllProvidersMap(
+ context);
}
- final AppWidgetProviderInfo provider = widgetProvidersMap.get(
+ final AppWidgetProviderInfo provider = mWidgetProvidersMap.get(
new ComponentKey(component, c.user));
final boolean isProviderReady = isValidProvider(provider);
@@ -793,17 +791,8 @@
LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
}
- // Unpin shortcuts that don't exist on the workspace.
- HashSet<ShortcutKey> pendingShortcuts =
- InstallShortcutReceiver.getPendingShortcuts(context);
- for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
- MutableInt numTimesPinned = mBgDataModel.pinnedShortcutCounts.get(key);
- if ((numTimesPinned == null || numTimesPinned.value == 0)
- && !pendingShortcuts.contains(key)) {
- // Shortcut is pinned but doesn't exist on the workspace; unpin it.
- mBgDataModel.unpinShortcut(context, key);
- }
- }
+ // Update pinned state of model shortcuts
+ mBgDataModel.updateShortcutPinnedState(context);
// Sort the folder items, update ranks, and make sure all preview items are high res.
FolderGridOrganizer verifier =
diff --git a/src/com/android/launcher3/model/PredictionModel.java b/src/com/android/launcher3/model/PredictionModel.java
index 1429843..cb3903d 100644
--- a/src/com/android/launcher3/model/PredictionModel.java
+++ b/src/com/android/launcher3/model/PredictionModel.java
@@ -14,6 +14,7 @@
* limitations under the License.
*/
package com.android.launcher3.model;
+
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.ComponentName;
@@ -24,6 +25,7 @@
import androidx.annotation.AnyThread;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.pm.UserCache;
@@ -64,18 +66,28 @@
mUserCache = UserCache.INSTANCE.get(mContext);
}
+
/**
* Formats and stores a list of component key in device preferences.
*/
@AnyThread
public void cachePredictionComponentKeys(List<ComponentKey> componentKeys) {
MODEL_EXECUTOR.execute(() -> {
+ LauncherAppState appState = LauncherAppState.getInstance(mContext);
StringBuilder builder = new StringBuilder();
int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS);
for (int i = 0; i < count; i++) {
builder.append(serializeComponentKeyToString(componentKeys.get(i)));
builder.append("\n");
}
+ if (componentKeys.isEmpty() /* should invalidate loader items */) {
+ appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
+ @Override
+ public void execute(LauncherAppState app, BgDataModel model, AllAppsList apps) {
+ model.cachedPredictedItems.clear();
+ }
+ });
+ }
mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply();
});
}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 0d3ddad..59233cd 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -53,6 +53,7 @@
import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ContentWriter;
import java.util.Optional;
@@ -285,6 +286,13 @@
.orElse(LauncherAtom.Application.newBuilder()));
break;
case ITEM_TYPE_DEEP_SHORTCUT:
+ itemBuilder
+ .setShortcut(nullableComponent
+ .map(component -> LauncherAtom.Shortcut.newBuilder()
+ .setShortcutName(component.flattenToShortString())
+ .setShortcutId(ShortcutKey.fromItemInfo(this).getId()))
+ .orElse(LauncherAtom.Shortcut.newBuilder()));
+ break;
case ITEM_TYPE_SHORTCUT:
itemBuilder
.setShortcut(nullableComponent
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index fd292a3..7998488 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -174,7 +174,7 @@
public void onClick(View view) {
Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
mItemInfo.getTargetComponent().getPackageName());
- mTarget.startActivitySafely(view, intent, mItemInfo, null);
+ mTarget.startActivitySafely(view, intent, mItemInfo);
AbstractFloatingView.closeAllOpenViews(mTarget);
}
}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 21ad275..31c3014 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -15,13 +15,12 @@
*/
package com.android.launcher3.secondarydisplay;
-import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.ActivityOptions;
import android.content.Intent;
import android.os.Bundle;
+import android.view.Display;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewAnimationUtils;
@@ -170,7 +169,9 @@
@Override
public ActivityOptions getActivityLaunchOptions(View v) {
- return null;
+ final Display display = getWindow().getDecorView().getDisplay();
+ return display != null ? ActivityOptions.makeBasic().setLaunchDisplayId(
+ display.getDisplayId()) : null;
}
@Override
@@ -224,7 +225,7 @@
}
@Override
- public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) { }
+ public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
@Override
public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
@@ -327,7 +328,7 @@
if (intent == null) {
throw new IllegalArgumentException("Input must have a valid intent");
}
- startActivitySafely(v, intent, item, CONTAINER_ALL_APPS);
+ startActivitySafely(v, intent, item);
}
}
}
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index b12d04f..4baecb7 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -34,17 +34,23 @@
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
+import android.text.Editable;
+import android.text.TextWatcher;
import android.util.ArrayMap;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.widget.EditText;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceDataStore;
import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
@@ -81,6 +87,7 @@
private PreferenceCategory mPluginsCategory;
private FlagTogglerPrefUi mFlagTogglerPrefUi;
+
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
@@ -99,6 +106,50 @@
maybeAddSandboxCategory();
}
+ private void filterPreferences(String query, PreferenceGroup pg) {
+ int count = pg.getPreferenceCount();
+ int hidden = 0;
+ for (int i = 0; i < count; i++) {
+ Preference preference = pg.getPreference(i);
+ if (preference instanceof PreferenceGroup) {
+ filterPreferences(query, (PreferenceGroup) preference);
+ } else {
+ String title = preference.getTitle().toString().toLowerCase().replace("_", " ");
+ if (query.isEmpty() || title.contains(query)) {
+ preference.setVisible(true);
+ } else {
+ preference.setVisible(false);
+ hidden++;
+ }
+ }
+ }
+ pg.setVisible(hidden != count);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ EditText filterBox = view.findViewById(R.id.filter_box);
+ filterBox.setVisibility(View.VISIBLE);
+ filterBox.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ String query = editable.toString().toLowerCase().replace("_", " ");
+ filterPreferences(query, mPreferenceScreen);
+ }
+ });
+ }
+
@Override
public void onDestroy() {
super.onDestroy();
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
index 2daa2fe..eb68592 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
@@ -23,7 +23,6 @@
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.view.MotionEvent;
import android.widget.Toast;
import com.android.launcher3.BubbleTextView;
@@ -106,12 +105,12 @@
}
@Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- // Show toast if user touches the drag handle (long clicks still start the drag).
- mShowInstructionToast = mDragHandleBounds.contains((int) ev.getX(), (int) ev.getY());
- }
- return super.onTouchEvent(ev);
+ protected boolean shouldIgnoreTouchDown(float x, float y) {
+ // Show toast if user touches the drag handle (long clicks still start the drag).
+ mShowInstructionToast = mDragHandleBounds.contains((int) x, (int) y);
+
+ // assume the whole view as clickable
+ return false;
}
@Override
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index 3ca9490..0c6d675 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -30,6 +30,10 @@
return componentName.getClassName();
}
+ public String getPackageName() {
+ return componentName.getPackageName();
+ }
+
/**
* Creates a {@link ShortcutRequest} for this key
*/
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 60b87d9..f1e7f16 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -315,6 +315,7 @@
@Override
public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
// Change the internal state only when the transition actually starts
onStateTransitionStart(state);
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 2644db8..8165627 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -107,5 +107,4 @@
public static final String PAUSE_NOT_DETECTED = "b/139891609";
public static final String OVERIEW_NOT_ALLAPPS = "b/156095088";
public static final String NO_SWIPE_TO_HOME = "b/158017601";
- public static final String NO_SCROLL_END_WIDGETS = "b/160238801";
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 3c78b08..66dbf6a 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -37,6 +37,8 @@
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
+import androidx.core.os.BuildCompat;
+
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherState;
@@ -44,6 +46,7 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.states.StateAnimationConfig;
@@ -265,8 +268,10 @@
mCanBlockFling = mFromState == NORMAL;
mFlingBlockCheck.unblockFling();
// Must be called after all the animation controllers have been paused
- if (mToState == ALL_APPS || mToState == NORMAL) {
- mLauncher.getAllAppsController().onDragStart(mToState == ALL_APPS);
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()
+ && (mToState == ALL_APPS || mToState == NORMAL)) {
+ mLauncher.getAllAppsController().getInsetController().onDragStart(
+ mToState == ALL_APPS ? 0 : 1);
}
}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 8486666..61d6f7d 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
-import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
@@ -37,8 +36,6 @@
import android.view.View.OnClickListener;
import android.widget.Toast;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
@@ -72,13 +69,9 @@
/**
* Instance used for click handling on items
*/
- public static final OnClickListener INSTANCE = getInstance(null);
+ public static final OnClickListener INSTANCE = ItemClickHandler::onClick;
- public static final OnClickListener getInstance(String sourceContainer) {
- return v -> onClick(v, sourceContainer);
- }
-
- private static void onClick(View v, String sourceContainer) {
+ private static void onClick(View v) {
// Make sure that rogue clicks don't get through while allapps is launching, or after the
// view has detached (it's possible for this to happen if the view is removed mid touch).
if (v.getWindowToken() == null) return;
@@ -88,14 +81,14 @@
Object tag = v.getTag();
if (tag instanceof WorkspaceItemInfo) {
- onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher, sourceContainer);
+ onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);
} else if (tag instanceof FolderInfo) {
if (v instanceof FolderIcon) {
onClickFolderIcon(v);
}
} else if (tag instanceof AppInfo) {
- startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher,
- sourceContainer == null ? CONTAINER_ALL_APPS: sourceContainer);
+ startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher
+ );
} else if (tag instanceof LauncherAppWidgetInfo) {
if (v instanceof PendingAppWidgetHostView) {
onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
@@ -191,7 +184,7 @@
// Fallback to using custom market intent.
Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
- launcher.startActivitySafely(v, intent, item, null);
+ launcher.startActivitySafely(v, intent, item);
}
/**
@@ -199,8 +192,7 @@
*
* @param v The view that was clicked. Must be a tagged with a {@link WorkspaceItemInfo}.
*/
- public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher,
- @Nullable String sourceContainer) {
+ public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher) {
if (shortcut.isDisabled()) {
final int disabledFlags = shortcut.runtimeStatusFlags
& WorkspaceItemInfo.FLAG_DISABLED_MASK;
@@ -241,11 +233,10 @@
}
// Start activities
- startAppShortcutOrInfoActivity(v, shortcut, launcher, sourceContainer);
+ startAppShortcutOrInfoActivity(v, shortcut, launcher);
}
- private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher,
- @Nullable String sourceContainer) {
+ private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "start: startAppShortcutOrInfoActivity");
Intent intent;
@@ -274,6 +265,6 @@
// Preload the icon to reduce latency b/w swapping the floating view with the original.
FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
}
- launcher.startActivitySafely(v, intent, item, sourceContainer);
+ launcher.startActivitySafely(v, intent, item);
}
}
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index c2bfb16..ac1ade2 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -239,7 +239,8 @@
}
@Override
- public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
+ public int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate,
+ LinearLayout taskMenuLayout) {
return LinearLayout.HORIZONTAL;
}
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index b650526..d4f5cba 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -94,7 +94,7 @@
float getTaskMenuX(float x, View thumbnailView);
float getTaskMenuY(float y, View thumbnailView);
int getTaskMenuWidth(View view);
- int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout);
+ int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate, LinearLayout taskMenuLayout);
void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp);
int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index e87c887..3341996 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -237,8 +237,9 @@
}
@Override
- public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
- return taskMenuLayout.getOrientation();
+ public int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate,
+ LinearLayout taskMenuLayout) {
+ return canRecentsActivityRotate ? taskMenuLayout.getOrientation() : LinearLayout.VERTICAL;
}
@Override
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index fc9f8f7..f6003dd 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -46,7 +46,7 @@
if (mValue == null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
- mValue = TraceHelper.whitelistIpcs("main.thread.object",
+ mValue = TraceHelper.allowIpcs("main.thread.object",
() -> mProvider.get(context.getApplicationContext()));
} else {
try {
diff --git a/src/com/android/launcher3/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java
index 48aa02b..4b22429 100644
--- a/src/com/android/launcher3/util/SecureSettingsObserver.java
+++ b/src/com/android/launcher3/util/SecureSettingsObserver.java
@@ -29,6 +29,11 @@
/** Hidden field Settings.Secure.NOTIFICATION_BADGING */
public static final String NOTIFICATION_BADGING = "notification_badging";
+ /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
+ public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
+ /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
+ public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
+ "swipe_bottom_to_notification_enabled";
private final ContentResolver mResolver;
private final String mKeySetting;
@@ -79,4 +84,21 @@
return new SecureSettingsObserver(
context.getContentResolver(), listener, NOTIFICATION_BADGING, 1);
}
+
+ public static SecureSettingsObserver newOneHandedSettingsObserver(Context context,
+ OnChangeListener listener) {
+ return new SecureSettingsObserver(
+ context.getContentResolver(), listener, ONE_HANDED_ENABLED, 1);
+ }
+
+ /**
+ * Constructs settings observer for {@link #ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED}
+ * preference.
+ */
+ public static SecureSettingsObserver newSwipeToNotificationSettingsObserver(Context context,
+ OnChangeListener listener) {
+ return new SecureSettingsObserver(
+ context.getContentResolver(), listener,
+ ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
+ }
}
diff --git a/src/com/android/launcher3/util/SystemUiController.java b/src/com/android/launcher3/util/SystemUiController.java
index 275c024..3e48006 100644
--- a/src/com/android/launcher3/util/SystemUiController.java
+++ b/src/com/android/launcher3/util/SystemUiController.java
@@ -33,6 +33,7 @@
public static final int UI_STATE_SCRIM_VIEW = 1;
public static final int UI_STATE_WIDGET_BOTTOM_SHEET = 2;
public static final int UI_STATE_OVERVIEW = 3;
+ public static final int UI_STATE_ALLAPPS = 4;
public static final int FLAG_LIGHT_NAV = 1 << 0;
public static final int FLAG_DARK_NAV = 1 << 1;
@@ -40,7 +41,7 @@
public static final int FLAG_DARK_STATUS = 1 << 3;
private final Window mWindow;
- private final int[] mStates = new int[4];
+ private final int[] mStates = new int[5];
public SystemUiController(Window window) {
mWindow = window;
diff --git a/src/com/android/launcher3/util/TraceHelper.java b/src/com/android/launcher3/util/TraceHelper.java
index 168227d..c23df77 100644
--- a/src/com/android/launcher3/util/TraceHelper.java
+++ b/src/com/android/launcher3/util/TraceHelper.java
@@ -78,7 +78,7 @@
* Temporarily ignore blocking binder calls for the duration of this {@link Supplier}.
*/
@MainThread
- public static <T> T whitelistIpcs(String rpcName, Supplier<T> supplier) {
+ public static <T> T allowIpcs(String rpcName, Supplier<T> supplier) {
Object traceToken = INSTANCE.beginSection(rpcName, FLAG_IGNORE_BINDERS);
try {
return supplier.get();
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 2c75c74..357eeb8 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -292,9 +292,6 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "BaseDragLayer: " + ev);
- }
switch (ev.getAction()) {
case ACTION_DOWN: {
if ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0) {
@@ -605,13 +602,13 @@
*/
private boolean computeAllowSysuiScrims(@Nullable WallpaperInfo newWallpaperInfo) {
if (newWallpaperInfo == null) {
- // New wallpaper is static, not live. Thus, blacklist isn't applicable.
+ // Static wallpapers need scrim unless determined otherwise by wallpaperColors.
return true;
}
ComponentName newWallpaper = newWallpaperInfo.getComponent();
for (String wallpaperWithoutScrim : mWallpapersWithoutSysuiScrims) {
if (newWallpaper.equals(ComponentName.unflattenFromString(wallpaperWithoutScrim))) {
- // New wallpaper is blacklisted from showing a scrim.
+ // New wallpaper does not need a scrim.
return false;
}
}
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 1a8e11b..fab0bd4 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -36,6 +36,7 @@
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewOutlineProvider;
import androidx.annotation.Nullable;
@@ -44,8 +45,6 @@
import androidx.dynamicanimation.animation.SpringForce;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InsettableFrameLayout.LayoutParams;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
@@ -94,7 +93,6 @@
}
};
- private final Launcher mLauncher;
private final int mBlurSizeOutline;
private final boolean mIsRtl;
@@ -128,7 +126,6 @@
public ClipIconView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
mBlurSizeOutline = getResources().getDimensionPixelSize(
R.dimen.blur_size_medium_outline);
mIsRtl = Utilities.isRtl(getResources());
@@ -143,10 +140,41 @@
.setStiffness(SpringForce.STIFFNESS_LOW));
}
- void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
- boolean isOpening, float scale, float minSize, LayoutParams parentLp,
- boolean isVerticalBarLayout) {
- DeviceProfile dp = mLauncher.getDeviceProfile();
+ /**
+ * Update the icon UI to match the provided parameters during an animation frame
+ */
+ public void update(RectF rect, float progress, float shapeProgressStart,
+ float cornerRadius, boolean isOpening, View container,
+ DeviceProfile dp, boolean isVerticalBarLayout) {
+
+ MarginLayoutParams lp = (MarginLayoutParams) container.getLayoutParams();
+
+ float dX = mIsRtl
+ ? rect.left - (dp.widthPx - lp.getMarginStart() - lp.width)
+ : rect.left - lp.getMarginStart();
+ float dY = rect.top - lp.topMargin;
+ container.setTranslationX(dX);
+ container.setTranslationY(dY);
+
+ float minSize = Math.min(lp.width, lp.height);
+ float scaleX = rect.width() / minSize;
+ float scaleY = rect.height() / minSize;
+ float scale = Math.max(1f, Math.min(scaleX, scaleY));
+
+ update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale,
+ minSize, lp, isVerticalBarLayout, dp);
+
+ container.setPivotX(0);
+ container.setPivotY(0);
+ container.setScaleX(scale);
+ container.setScaleY(scale);
+
+ container.invalidate();
+ }
+
+ private void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
+ boolean isOpening, float scale, float minSize, MarginLayoutParams parentLp,
+ boolean isVerticalBarLayout, DeviceProfile dp) {
float dX = mIsRtl
? rect.left - (dp.widthPx - parentLp.getMarginStart() - parentLp.width)
: rect.left - parentLp.getMarginStart();
@@ -228,8 +256,11 @@
}
}
- void setIcon(@Nullable Drawable drawable, int iconOffset, LayoutParams lp, boolean isOpening,
- boolean isVerticalBarLayout) {
+ /**
+ * Sets the icon for this view as part of initial setup
+ */
+ public void setIcon(@Nullable Drawable drawable, int iconOffset, MarginLayoutParams lp,
+ boolean isOpening, boolean isVerticalBarLayout, DeviceProfile dp) {
mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable;
if (mIsAdaptiveIcon) {
boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon;
@@ -264,15 +295,14 @@
Utilities.scaleRectAboutCenter(mStartRevealRect, IconShape.getNormalizationScale());
}
- float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
if (isVerticalBarLayout) {
- lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
+ lp.width = (int) Math.max(lp.width, lp.height * dp.aspectRatio);
} else {
- lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
+ lp.height = (int) Math.max(lp.height, lp.width * dp.aspectRatio);
}
int left = mIsRtl
- ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+ ? dp.widthPx - lp.getMarginStart() - lp.width
: lp.leftMargin;
layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 177aff4..8186dfa 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.Utilities.getFullDrawable;
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -47,7 +48,6 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
@@ -144,32 +144,8 @@
public void update(RectF rect, float alpha, float progress, float shapeProgressStart,
float cornerRadius, boolean isOpening) {
setAlpha(alpha);
-
- InsettableFrameLayout.LayoutParams lp =
- (InsettableFrameLayout.LayoutParams) getLayoutParams();
-
- DeviceProfile dp = mLauncher.getDeviceProfile();
- float dX = mIsRtl
- ? rect.left - (dp.widthPx - lp.getMarginStart() - lp.width)
- : rect.left - lp.getMarginStart();
- float dY = rect.top - lp.topMargin;
- setTranslationX(dX);
- setTranslationY(dY);
-
- float minSize = Math.min(lp.width, lp.height);
- float scaleX = rect.width() / minSize;
- float scaleY = rect.height() / minSize;
- float scale = Math.max(1f, Math.min(scaleX, scaleY));
-
- mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale,
- minSize, lp, mIsVerticalBarLayout);
-
- setPivotX(0);
- setPivotY(0);
- setScaleX(scale);
- setScaleY(scale);
-
- invalidate();
+ mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening,
+ this, mLauncher.getDeviceProfile(), mIsVerticalBarLayout);
}
@Override
@@ -220,13 +196,18 @@
layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
}
+ private static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening,
+ RectF outRect) {
+ getLocationBoundsForView(launcher, v, isOpening, outRect, new Rect());
+ }
+
/**
* Gets the location bounds of a view and returns the overall rotation.
* - For DeepShortcutView, we return the bounds of the icon view.
* - For BubbleTextView, we return the icon bounds.
*/
- private static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening,
- RectF outRect) {
+ public static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening,
+ RectF outRect, Rect outViewBounds) {
boolean ignoreTransform = !isOpening;
if (v instanceof DeepShortcutView) {
v = ((DeepShortcutView) v).getBubbleText();
@@ -239,17 +220,16 @@
return;
}
- Rect iconBounds = new Rect();
if (v instanceof BubbleTextView) {
- ((BubbleTextView) v).getIconBounds(iconBounds);
+ ((BubbleTextView) v).getIconBounds(outViewBounds);
} else if (v instanceof FolderIcon) {
- ((FolderIcon) v).getPreviewBounds(iconBounds);
+ ((FolderIcon) v).getPreviewBounds(outViewBounds);
} else {
- iconBounds.set(0, 0, v.getWidth(), v.getHeight());
+ outViewBounds.set(0, 0, v.getWidth(), v.getHeight());
}
- float[] points = new float[] {iconBounds.left, iconBounds.top, iconBounds.right,
- iconBounds.bottom};
+ float[] points = new float[] {outViewBounds.left, outViewBounds.top, outViewBounds.right,
+ outViewBounds.bottom};
Utilities.getDescendantCoordRelativeToAncestor(v, launcher.getDragLayer(), points,
false, ignoreTransform);
outRect.set(
@@ -336,7 +316,8 @@
final InsettableFrameLayout.LayoutParams lp =
(InsettableFrameLayout.LayoutParams) getLayoutParams();
mBadge = badge;
- mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening, mIsVerticalBarLayout);
+ mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening, mIsVerticalBarLayout,
+ mLauncher.getDeviceProfile());
if (drawable instanceof AdaptiveIconDrawable) {
final int originalHeight = lp.height;
final int originalWidth = lp.width;
@@ -381,7 +362,7 @@
if (mIconLoadResult.isIconLoaded) {
setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
mIconLoadResult.iconOffset);
- hideOriginalView(originalView);
+ setIconAndDotVisible(originalView, false);
} else {
mIconLoadResult.onIconLoaded = () -> {
if (cancellationSignal.isCanceled()) {
@@ -392,22 +373,13 @@
mIconLoadResult.iconOffset);
setVisibility(VISIBLE);
- hideOriginalView(originalView);
+ setIconAndDotVisible(originalView, false);
};
mLoadIconSignal = cancellationSignal;
}
}
}
- private void hideOriginalView(View originalView) {
- if (originalView instanceof IconLabelDotView) {
- ((IconLabelDotView) originalView).setIconVisible(false);
- ((IconLabelDotView) originalView).setForceHideDot(true);
- } else {
- originalView.setVisibility(INVISIBLE);
- }
- }
-
@WorkerThread
@SuppressWarnings("WrongThread")
private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) {
@@ -477,7 +449,7 @@
}
if (!mIsOpening) {
// When closing an app, we want the item on the workspace to be invisible immediately
- hideOriginalView(mOriginalIcon);
+ setIconAndDotVisible(mOriginalIcon, false);
}
}
@@ -573,12 +545,7 @@
if (hideOriginal) {
if (isOpening) {
- if (originalView instanceof BubbleTextView) {
- ((BubbleTextView) originalView).setIconVisible(true);
- ((BubbleTextView) originalView).setForceHideDot(false);
- } else {
- originalView.setVisibility(VISIBLE);
- }
+ setIconAndDotVisible(originalView, true);
view.finish(dragLayer);
} else {
view.mFadeAnimatorSet = view.createFadeAnimation(originalView, dragLayer);
@@ -615,12 +582,10 @@
});
if (originalView instanceof IconLabelDotView) {
- IconLabelDotView view = (IconLabelDotView) originalView;
fade.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- view.setIconVisible(true);
- view.setForceHideDot(false);
+ setIconAndDotVisible(originalView, true);
}
});
}
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
new file mode 100644
index 0000000..040619e
--- /dev/null
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -0,0 +1,241 @@
+/*
+ * 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.views;
+
+import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView;
+import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Picture;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.GestureNavContract;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.Executors;
+
+/**
+ * Similar to {@link FloatingIconView} but displays a surface with the targetIcon. It then passes
+ * the surfaceHandle to the {@link GestureNavContract}.
+ */
+@TargetApi(Build.VERSION_CODES.R)
+public class FloatingSurfaceView extends AbstractFloatingView implements
+ OnGlobalLayoutListener, Insettable, SurfaceHolder.Callback2 {
+
+ private final RectF mTmpPosition = new RectF();
+
+ private final Launcher mLauncher;
+ private final RectF mIconPosition = new RectF();
+
+ private final Rect mIconBounds = new Rect();
+ private final Picture mPicture = new Picture();
+ private final Runnable mRemoveViewRunnable = this::removeViewFromParent;
+
+ private final SurfaceView mSurfaceView;
+
+
+ private View mIcon;
+ private GestureNavContract mContract;
+
+ public FloatingSurfaceView(Context context) {
+ this(context, null);
+ }
+
+ public FloatingSurfaceView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FloatingSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLauncher = Launcher.getLauncher(context);
+
+ mSurfaceView = new SurfaceView(context);
+ mSurfaceView.setZOrderOnTop(true);
+
+ mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+ mSurfaceView.getHolder().addCallback(this);
+ mIsOpen = true;
+ addView(mSurfaceView);
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ setCurrentIconVisible(true);
+ mLauncher.getViewCache().recycleView(R.layout.floating_surface_view, this);
+ mContract = null;
+ mIcon = null;
+ mIsOpen = false;
+
+ // Remove after some time, to avoid flickering
+ Executors.MAIN_EXECUTOR.getHandler().postDelayed(mRemoveViewRunnable,
+ DefaultDisplay.INSTANCE.get(mLauncher).getInfo().singleFrameMs);
+ }
+
+ private void removeViewFromParent() {
+ mPicture.beginRecording(1, 1);
+ mPicture.endRecording();
+ mLauncher.getDragLayer().removeView(this);
+ }
+
+ /**
+ * Shows the surfaceView for the provided contract
+ */
+ public static void show(Launcher launcher, GestureNavContract contract) {
+ FloatingSurfaceView view = launcher.getViewCache().getView(R.layout.floating_surface_view,
+ launcher, launcher.getDragLayer());
+ view.mContract = contract;
+ view.mIsOpen = true;
+
+ // Cancel any pending remove
+ Executors.MAIN_EXECUTOR.getHandler().removeCallbacks(view.mRemoveViewRunnable);
+ view.removeViewFromParent();
+ launcher.getDragLayer().addView(view);
+ }
+
+ @Override
+ public void logActionCommand(int command) { }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ICON_SURFACE) != 0;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ close(false);
+ return false;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ getViewTreeObserver().addOnGlobalLayoutListener(this);
+ updateIconLocation();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ setCurrentIconVisible(true);
+ }
+
+ @Override
+ public void onGlobalLayout() {
+ updateIconLocation();
+ }
+
+ @Override
+ public void setInsets(Rect insets) { }
+
+ private void updateIconLocation() {
+ if (mContract == null) {
+ return;
+ }
+ View icon = mLauncher.getWorkspace().getFirstMatchForAppClose(
+ mContract.componentName.getPackageName(), mContract.user);
+
+ boolean iconChanged = mIcon != icon;
+ if (iconChanged) {
+ setCurrentIconVisible(true);
+ mIcon = icon;
+ setCurrentIconVisible(false);
+ }
+
+ if (icon != null && icon.isAttachedToWindow()) {
+ getLocationBoundsForView(mLauncher, icon, false, mTmpPosition, mIconBounds);
+
+ if (!mTmpPosition.equals(mIconPosition)) {
+ mIconPosition.set(mTmpPosition);
+ sendIconInfo();
+
+ LayoutParams lp = (LayoutParams) mSurfaceView.getLayoutParams();
+ lp.width = Math.round(mIconPosition.width());
+ lp.height = Math.round(mIconPosition.height());
+ lp.leftMargin = Math.round(mIconPosition.left);
+ lp.topMargin = Math.round(mIconPosition.top);
+ }
+ }
+ if (iconChanged && !mIconBounds.isEmpty()) {
+ // Record the icon display
+ setCurrentIconVisible(true);
+ Canvas c = mPicture.beginRecording(mIconBounds.width(), mIconBounds.height());
+ c.translate(-mIconBounds.left, -mIconBounds.top);
+ mIcon.draw(c);
+ mPicture.endRecording();
+ setCurrentIconVisible(false);
+ drawOnSurface();
+ }
+ }
+
+ private void sendIconInfo() {
+ if (mContract != null && !mIconPosition.isEmpty()) {
+ mContract.sendEndPosition(mIconPosition, mSurfaceView.getSurfaceControl());
+ }
+ }
+
+ @Override
+ public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
+ drawOnSurface();
+ sendIconInfo();
+ }
+
+ @Override
+ public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder,
+ int format, int width, int height) {
+ drawOnSurface();
+ }
+
+ @Override
+ public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {}
+
+ @Override
+ public void surfaceRedrawNeeded(@NonNull SurfaceHolder surfaceHolder) {
+ drawOnSurface();
+ }
+
+ private void drawOnSurface() {
+ SurfaceHolder surfaceHolder = mSurfaceView.getHolder();
+
+ Canvas c = surfaceHolder.lockHardwareCanvas();
+ if (c != null) {
+ mPicture.draw(c);
+ surfaceHolder.unlockCanvasAndPost(c);
+ }
+ }
+
+ private void setCurrentIconVisible(boolean isVisible) {
+ if (mIcon != null) {
+ setIconAndDotVisible(mIcon, isVisible);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/views/IconLabelDotView.java b/src/com/android/launcher3/views/IconLabelDotView.java
index 057caaf..e9113cf 100644
--- a/src/com/android/launcher3/views/IconLabelDotView.java
+++ b/src/com/android/launcher3/views/IconLabelDotView.java
@@ -15,10 +15,24 @@
*/
package com.android.launcher3.views;
+import android.view.View;
+
/**
* A view that has an icon, label, and notification dot.
*/
public interface IconLabelDotView {
void setIconVisible(boolean visible);
void setForceHideDot(boolean hide);
+
+ /**
+ * Sets the visibility of icon and dot of the view
+ */
+ static void setIconAndDotVisible(View view, boolean visible) {
+ if (view instanceof IconLabelDotView) {
+ ((IconLabelDotView) view).setIconVisible(visible);
+ ((IconLabelDotView) view).setForceHideDot(!visible);
+ } else {
+ view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
}
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index d558781..049a1ac 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -220,7 +220,7 @@
if (!TextUtils.isEmpty(pickerPackage)) {
intent.setPackage(pickerPackage);
}
- return launcher.startActivitySafely(v, intent, dummyInfo(intent), null);
+ return launcher.startActivitySafely(v, intent, dummyInfo(intent));
}
static WorkspaceItemInfo dummyInfo(Intent intent) {
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 9021d9e..ca47728 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -52,7 +52,6 @@
private static final float MIN_SATUNATION = 0.7f;
private final Rect mRect = new Rect();
- private View mDefaultView;
private OnClickListener mClickListener;
private final LauncherAppWidgetInfo mInfo;
private final int mStartState;
@@ -111,12 +110,11 @@
@Override
protected View getDefaultView() {
- if (mDefaultView == null) {
- mDefaultView = mInflater.inflate(R.layout.appwidget_not_ready, this, false);
- mDefaultView.setOnClickListener(this);
- applyState();
- }
- return mDefaultView;
+ View defaultView = mInflater.inflate(R.layout.appwidget_not_ready, this, false);
+ defaultView.setOnClickListener(this);
+ applyState();
+ invalidate();
+ return defaultView;
}
@Override
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
index c9e80dc..df6e2c3 100644
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -20,10 +20,8 @@
import androidx.recyclerview.widget.RecyclerView;
-import com.android.launcher3.Utilities;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
import java.util.ArrayList;
@@ -34,8 +32,8 @@
* methods accordingly.
*/
public class WidgetsDiffReporter {
- private static final boolean DEBUG = Utilities.IS_RUNNING_IN_TEST_HARNESS; // b/160238801
- private static final String TAG = TestProtocol.NO_SCROLL_END_WIDGETS;
+ private static final boolean DEBUG = false;
+ private static final String TAG = "WidgetsDiffReporter";
private final IconCache mIconCache;
private final RecyclerView.Adapter mListener;
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index ba55f5a..68a3ec5 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -39,10 +38,8 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.TopRoundedCornerView;
@@ -71,14 +68,6 @@
}
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsFullSheet: " + ev);
- }
- return super.dispatchTouchEvent(ev);
- }
-
public WidgetsFullSheet(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 8f81977..69de12b 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -19,14 +19,11 @@
import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -120,9 +117,6 @@
public int getCurrentScrollY() {
// Skip early if widgets are not bound.
if (isModelNotReady() || getChildCount() == 0) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "getCurrentScrollY: -1");
- }
return -1;
}
@@ -131,10 +125,6 @@
int y = (child.getMeasuredHeight() * rowIndex);
int offset = getLayoutManager().getDecoratedTop(child);
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS,
- "getCurrentScrollY: " + (getPaddingTop() + y - offset));
- }
return getPaddingTop() + y - offset;
}
@@ -166,22 +156,13 @@
}
if (mTouchDownOnScroller) {
final boolean result = mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onInterceptTouchEvent 1 " + result);
- }
return result;
}
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onInterceptTouchEvent 2 false");
- }
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsRecyclerView.onTouchEvent");
- }
if (mTouchDownOnScroller) {
mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
}
@@ -189,31 +170,5 @@
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onRequestDisallowInterceptTouchEvent "
- + disallowIntercept);
- }
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- final boolean result = super.dispatchTouchEvent(ev);
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsRecyclerView: state: "
- + getScrollState()
- + " can scroll: " + getLayoutManager().canScrollVertically()
- + " result: " + result
- + " layout suppressed: " + isLayoutSuppressed()
- + " event: " + ev);
- }
- return result;
- }
-
- @Override
- public void stopNestedScroll() {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "stopNestedScroll");
- }
- super.stopNestedScroll();
}
}
\ No newline at end of file
diff --git a/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java b/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java
deleted file mode 100644
index 15a0ffa..0000000
--- a/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2019 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.systemui.plugins;
-
-import android.content.ComponentName;
-import android.os.UserHandle;
-
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-/**
- * Plugin interface which sends app launch events.
- */
-@ProvidesInterface(action = AppLaunchEventsPlugin.ACTION, version = AppLaunchEventsPlugin.VERSION)
-public interface AppLaunchEventsPlugin extends Plugin {
- String ACTION = "com.android.systemui.action.PLUGIN_APP_EVENTS";
- int VERSION = 1;
-
- /**
- * Receives onStartShortcut event from
- * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
- */
- void onStartShortcut(String packageName, String shortcutId, UserHandle user, String container);
-
- /**
- * Receives onStartApp event from
- * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
- */
- void onStartApp(ComponentName componentName, UserHandle user, String container);
-
- /**
- * Receives onDismissApp event from
- * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
- */
- void onDismissApp(ComponentName componentName, UserHandle user, String container);
-
- /**
- * Receives onReturnedToHome event from
- * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
- */
- void onReturnedToHome();
-}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index f243f27..bc6356f 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -98,7 +98,7 @@
<activity
android:name="com.android.launcher3.testcomponent.TestLauncherActivity"
android:clearTaskOnLaunch="true"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:enabled="false"
android:label="Test launcher"
android:launchMode="singleTask"
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index a4aa9f2..fa495f5 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -192,7 +192,7 @@
WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED));
executeOnLauncher(l -> l.getAppWidgetHost().startListening());
verifyWidgetPresent(info);
- assertNull(mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT));
+ assertNull(mLauncher.getWorkspace().tryGetPendingWidget(100));
}
@Test
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
index e4f520f..77546de 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -87,12 +87,6 @@
return 145935261;
}
- if (matches("java\\.lang\\.AssertionError\\: http\\:\\/\\/go\\/tapl \\: want to get "
- + "workspace object; Presence of recents button doesn't match the interaction "
- + "mode, mode\\=ZERO_BUTTON, hasRecents\\=true", exception)) {
- return 148422894;
- }
-
final String logSinceBoot;
try {
final String systemBootTime =
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index ce94a3e..0c4e5a9 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -85,7 +85,7 @@
final LauncherInstrumentation.GestureScope gestureScope =
zeroButtonToOverviewGestureStartsInLauncher()
? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
- : LauncherInstrumentation.GestureScope.OUTSIDE;
+ : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
// b/156044202
mLauncher.log("Hierarchy before swiping up to overview:");
@@ -130,7 +130,7 @@
}
mLauncher.swipeToState(startX, startY, endX, endY, 10, OVERVIEW_STATE_ORDINAL,
- LauncherInstrumentation.GestureScope.OUTSIDE);
+ LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER);
break;
}
@@ -194,7 +194,7 @@
mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState,
launcherWasVisible && isZeroButton
? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
- : LauncherInstrumentation.GestureScope.OUTSIDE);
+ : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER);
break;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index f90dff2..5b55c78 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -112,9 +112,10 @@
public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON}
- // Where the gesture happens: outside of Launcher, inside or from inside to outside.
+ // Where the gesture happens: outside of Launcher, inside or from inside to outside and
+ // whether the gesture recognition triggers pilfer.
public enum GestureScope {
- OUTSIDE, INSIDE, INSIDE_TO_OUTSIDE
+ OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE
}
;
@@ -152,6 +153,7 @@
private static final String WIDGETS_RES_ID = "widgets_list_view";
private static final String CONTEXT_MENU_RES_ID = "deep_shortcuts_container";
public static final int WAIT_TIME_MS = 10000;
+ public static final int LONG_WAIT_TIME_MS = 60000;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
@@ -549,13 +551,7 @@
assertEquals("Unexpected display rotation",
mExpectedRotation, mDevice.getDisplayRotation());
- // b/148422894
- String error = null;
- for (int i = 0; i != 600; ++i) {
- error = getNavigationModeMismatchError();
- if (error == null) break;
- sleep(100);
- }
+ final String error = getNavigationModeMismatchError();
assertTrue(error, error == null);
log("verifyContainerType: " + containerType);
@@ -636,11 +632,9 @@
Parcelable executeAndWaitForEvent(Runnable command,
UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message) {
try {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "executeAndWaitForEvent: before");
final AccessibilityEvent event =
mInstrumentation.getUiAutomation().executeAndWaitForEvent(
- command, eventFilter, WAIT_TIME_MS);
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "executeAndWaitForEvent: after");
+ command, eventFilter, LONG_WAIT_TIME_MS);
assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
final Parcelable parcelableData = event.getParcelableData();
event.recycle();
@@ -696,7 +690,7 @@
ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
launcherWasVisible
? GestureScope.INSIDE_TO_OUTSIDE
- : GestureScope.OUTSIDE);
+ : GestureScope.OUTSIDE_WITH_PILFER);
}
}
} else {
@@ -1097,10 +1091,7 @@
executeAndWaitForEvent(
() -> linearGesture(
startX, startY, endX, endY, steps, slowDown, GestureScope.INSIDE),
- event -> {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "scroll: received event: " + event);
- return TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName());
- },
+ event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
() -> "Didn't receive a scroll end message: " + startX + ", " + startY
+ ", " + endX + ", " + endY);
}
@@ -1165,7 +1156,8 @@
final boolean notLauncher3 = !isLauncher3();
switch (action) {
case MotionEvent.ACTION_DOWN:
- if (gestureScope != GestureScope.OUTSIDE) {
+ if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
+ && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER) {
expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
}
if (notLauncher3 && getNavigationModel() != NavigationModel.THREE_BUTTON) {
@@ -1173,12 +1165,17 @@
}
break;
case MotionEvent.ACTION_UP:
- if (notLauncher3 && gestureScope != GestureScope.INSIDE) {
+ if (notLauncher3 && gestureScope != GestureScope.INSIDE
+ && (gestureScope == GestureScope.OUTSIDE_WITH_PILFER
+ || gestureScope == GestureScope.INSIDE_TO_OUTSIDE)) {
expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
}
- if (gestureScope != GestureScope.OUTSIDE) {
- expectEvent(TestProtocol.SEQUENCE_MAIN, gestureScope == GestureScope.INSIDE
- ? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL);
+ if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
+ && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER) {
+ expectEvent(TestProtocol.SEQUENCE_MAIN,
+ gestureScope == GestureScope.INSIDE
+ || gestureScope == GestureScope.OUTSIDE_WITHOUT_PILFER
+ ? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL);
}
if (notLauncher3 && getNavigationModel() != NavigationModel.THREE_BUTTON) {
expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);