Snap for 7842835 from ea38519fa4eebb50710ee9b7ad55aabda10e0d6f to sc-v2-release
Change-Id: I0c9f86d9ab756baac8a6df2e89ee518fd146e433
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 088009a..216e79b 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -31,19 +31,14 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.app.ActivityOptions;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
-import android.content.ServiceConnection;
import android.graphics.Insets;
import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.IBinder;
-import android.util.Log;
import android.view.View;
import android.view.WindowInsets;
import android.window.SplashScreen;
@@ -76,13 +71,13 @@
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
-import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.util.LauncherUnfoldAnimationController;
import com.android.quickstep.util.ProxyScreenStatusProvider;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -103,11 +98,6 @@
public abstract class BaseQuickstepLauncher extends Launcher
implements NavigationModeChangeListener {
- private static final long BACKOFF_MILLIS = 1000;
-
- // Max backoff caps at 5 mins
- private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
-
private DepthController mDepthController = new DepthController(this);
private QuickstepTransitionManager mAppTransitionManager;
@@ -120,45 +110,12 @@
private OverviewActionsView mActionsView;
+ private TISBindHelper mTISBindHelper;
private @Nullable TaskbarManager mTaskbarManager;
private @Nullable OverviewCommandHelper mOverviewCommandHelper;
private @Nullable LauncherTaskbarUIController mTaskbarUIController;
- private final ServiceConnection mTisBinderConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
- if (!(iBinder instanceof TISBinder)) {
- // Seems like there can be a race condition when user unlocks, which kills the TIS
- // process and re-starts it. I guess in the meantime service can be connected to
- // a killed TIS? Either way, unbind and try to re-connect in that case.
- internalUnbindToTIS();
- mHandler.postDelayed(mConnectionRunnable, BACKOFF_MILLIS);
- return;
- }
- mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager();
- mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
-
- Log.d(TAG, "TIS service connected");
- resetServiceBindRetryState();
-
- mOverviewCommandHelper = ((TISBinder) iBinder).getOverviewCommandHelper();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName componentName) { }
-
- @Override
- public void onBindingDied(ComponentName name) {
- Log.w(TAG, "TIS binding died");
- internalBindToTIS();
- }
- };
-
- private final Runnable mConnectionRunnable = this::internalBindToTIS;
- private short mConnectionAttempts;
private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this);
- private final Handler mHandler = new Handler();
- private boolean mTisServiceBound;
// Will be updated when dragging from taskbar.
private @Nullable DragOptions mNextWorkspaceDragOptions = null;
@@ -201,11 +158,10 @@
SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
- internalUnbindToTIS();
+ mTISBindHelper.onDestroy();
if (mTaskbarManager != null) {
mTaskbarManager.clearLauncher(this);
}
- resetServiceBindRetryState();
if (mLauncherUnfoldAnimationController != null) {
mLauncherUnfoldAnimationController.onDestroy();
@@ -357,42 +313,13 @@
mAppTransitionManager.registerRemoteAnimations();
mAppTransitionManager.registerRemoteTransitions();
- internalBindToTIS();
+ mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
}
- /**
- * Binds {@link #mTisBinderConnection} to {@link TouchInteractionService}. If the binding fails,
- * attempts to retry via {@link #mConnectionRunnable}.
- * Unbind via {@link #internalUnbindToTIS()}
- */
- private void internalBindToTIS() {
- mTisServiceBound = bindService(new Intent(this, TouchInteractionService.class),
- mTisBinderConnection, 0);
- if (mTisServiceBound) {
- resetServiceBindRetryState();
- return;
- }
-
- Log.w(TAG, "Retrying TIS Binder connection attempt: " + mConnectionAttempts);
- final long timeoutMs = (long) Math.min(
- Math.scalb(BACKOFF_MILLIS, mConnectionAttempts), MAX_BACKOFF_MILLIS);
- mHandler.postDelayed(mConnectionRunnable, timeoutMs);
- mConnectionAttempts++;
- }
-
- /** See {@link #internalBindToTIS()} */
- private void internalUnbindToTIS() {
- if (mTisServiceBound) {
- unbindService(mTisBinderConnection);
- mTisServiceBound = false;
- }
- }
-
- private void resetServiceBindRetryState() {
- if (mHandler.hasCallbacks(mConnectionRunnable)) {
- mHandler.removeCallbacks(mConnectionRunnable);
- }
- mConnectionAttempts = 0;
+ private void onTISConnected(TISBinder binder) {
+ mTaskbarManager = binder.getTaskbarManager();
+ mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
+ mOverviewCommandHelper = binder.getOverviewCommandHelper();
}
private void initUnfoldTransitionProgressProvider() {
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index 63e7390..680012c 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -30,6 +30,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.FolderInfo;
@@ -298,7 +299,7 @@
Log.e(TAG, "Unable to find suitable view for ArrowTip");
return false;
}
- Rect bounds = mLauncher.getViewBounds(tipTargetView);
+ Rect bounds = Utilities.getViewBounds(tipTargetView);
new ArrowTipView(mLauncher).show(message, Gravity.END, bounds.centerX(), bounds.top);
return true;
}
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index 154b78b..e489cb3 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -377,7 +377,7 @@
/**
* Shortcut factory for generating wellbeing action
*/
- public static final SystemShortcut.Factory SHORTCUT_FACTORY =
+ public static final SystemShortcut.Factory<BaseDraggingActivity> SHORTCUT_FACTORY =
(activity, info) -> (info.getTargetComponent() == null) ? null : INSTANCE.get(activity)
.getShortcutForApp(
info.getTargetComponent().getPackageName(), info.user.getIdentifier(),
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 9d10e1e..71a93d1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -19,7 +19,6 @@
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
-import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_APP_SETUP;
import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
@@ -255,10 +254,6 @@
TaskbarStashController stashController = mControllers.taskbarStashController;
stashController.updateStateForFlag(FLAG_IN_APP, !isResumed);
- if (isResumed) {
- // Launcher is resumed, meaning setup must be finished.
- stashController.updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, false);
- }
stashController.applyState(duration);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index f2ca711..b768d60 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -41,7 +41,6 @@
import android.graphics.Region;
import android.graphics.Region.Op;
import android.graphics.drawable.AnimatedVectorDrawable;
-import android.provider.Settings;
import android.util.Property;
import android.view.Gravity;
import android.view.View;
@@ -59,7 +58,6 @@
import com.android.launcher3.taskbar.contextual.RotationButton;
import com.android.launcher3.taskbar.contextual.RotationButtonController;
import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.Themes;
import com.android.quickstep.AnimatedFloat;
@@ -118,9 +116,10 @@
/**
* Initializes the controller
*/
- public void init(TaskbarControllers controllers) {
+ public void init(TaskbarControllers controllers, TaskbarSharedState sharedState) {
mControllers = controllers;
mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
+ parseSystemUiFlags(sharedState.sysuiStateFlags);
mA11yLongClickListener = view -> {
mControllers.navButtonController.onButtonClick(BUTTON_A11Y_LONG_CLICK);
@@ -151,8 +150,7 @@
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, AnimatedFloat.VALUE, 1, 0));
// Force nav buttons (specifically back button) to be visible during setup wizard.
- boolean isInSetup = !SettingsCache.INSTANCE.get(mContext).getValue(
- Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
+ boolean isInSetup = !mContext.isUserSetupComplete();
if (isThreeButtonNav || isInSetup) {
initButtons(mNavButtonContainer, mEndContextualContainer,
mControllers.navButtonController);
@@ -252,23 +250,14 @@
mA11yButton.setOnLongClickListener(mA11yLongClickListener);
}
- public void updateStateForSysuiFlags(int systemUiStateFlags, boolean forceUpdate) {
- boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
- boolean isImeSwitcherShowing = (systemUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
- boolean a11yVisible = (systemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
- boolean a11yLongClickable =
- (systemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
- boolean isHomeDisabled =
- (systemUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
- boolean isRecentsDisabled =
- (systemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
- boolean isBackDisabled =
- (systemUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0;
-
- if (!forceUpdate && systemUiStateFlags == mSysuiStateFlags) {
- return;
- }
- mSysuiStateFlags = systemUiStateFlags;
+ private void parseSystemUiFlags(int sysUiStateFlags) {
+ mSysuiStateFlags = sysUiStateFlags;
+ boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
+ boolean isImeSwitcherShowing = (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
+ boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+ boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
+ boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
+ boolean isBackDisabled = (sysUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0;
// TODO(b/202218289) we're getting IME as not visible on lockscreen from system
updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
@@ -280,8 +269,17 @@
if (mA11yButton != null) {
// Only used in 3 button
+ boolean a11yLongClickable =
+ (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
mA11yButton.setLongClickable(a11yLongClickable);
}
+ }
+
+ public void updateStateForSysuiFlags(int systemUiStateFlags) {
+ if (systemUiStateFlags == mSysuiStateFlags) {
+ return;
+ }
+ parseSystemUiFlags(systemUiStateFlags);
applyState();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 973c52b..f1d7d41 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -35,6 +35,7 @@
import android.graphics.Rect;
import android.os.Process;
import android.os.SystemProperties;
+import android.provider.Settings;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.Display;
@@ -61,6 +62,7 @@
import com.android.launcher3.taskbar.contextual.RotationButtonController;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.ViewCache;
@@ -102,6 +104,7 @@
private final ViewCache mViewCache = new ViewCache();
private final boolean mIsSafeModeEnabled;
+ private final boolean mIsUserSetupComplete;
private boolean mIsDestroyed = false;
public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
@@ -113,6 +116,8 @@
mNavMode = SysUINavigationMode.getMode(windowContext);
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> getPackageManager().isSafeMode());
+ mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue(
+ Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
mDeviceProfile.updateIconSize(1, getResources());
@@ -155,7 +160,7 @@
new TaskbarEduController(this));
}
- public void init() {
+ public void init(TaskbarSharedState sharedState) {
mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
mWindowLayoutParams = new WindowManager.LayoutParams(
MATCH_PARENT,
@@ -184,7 +189,7 @@
getDefaultTaskbarWindowHeight() - mDeviceProfile.taskbarSize, 0, 0);
// Initialize controllers after all are constructed.
- mControllers.init();
+ mControllers.init(sharedState);
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
}
@@ -303,6 +308,13 @@
}
/**
+ * Sets the flag indicating setup UI is visible
+ */
+ public void setSetupUIVisible(boolean isVisible) {
+ mControllers.taskbarStashController.setSetupUIVisible(isVisible);
+ }
+
+ /**
* Called when this instance of taskbar is no longer needed
*/
public void onDestroy() {
@@ -312,9 +324,8 @@
mWindowManager.removeViewImmediate(mDragLayer);
}
- public void updateSysuiStateFlags(int systemUiStateFlags, boolean forceUpdate) {
- mControllers.navbarButtonsViewController.updateStateForSysuiFlags(
- systemUiStateFlags, forceUpdate);
+ public void updateSysuiStateFlags(int systemUiStateFlags) {
+ mControllers.navbarButtonsViewController.updateStateForSysuiFlags(systemUiStateFlags);
mControllers.taskbarViewController.setImeIsVisible(
mControllers.navbarButtonsViewController.isImeVisible());
boolean panelExpanded = (systemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0;
@@ -467,4 +478,8 @@
public void startTaskbarUnstashHint(boolean animateForward) {
mControllers.taskbarStashController.startUnstashHint(animateForward);
}
+
+ protected boolean isUserSetupComplete() {
+ return mIsUserSetupComplete;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index e13f849..8684c29 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -74,8 +74,8 @@
* TaskbarControllers instance, but should be careful to only access things that were created
* in constructors for now, as some controllers may still be waiting for init().
*/
- public void init() {
- navbarButtonsViewController.init(this);
+ public void init(TaskbarSharedState sharedState) {
+ navbarButtonsViewController.init(this, sharedState);
if (taskbarActivityContext.isThreeButtonNav()) {
rotationButtonController.init();
}
@@ -85,7 +85,7 @@
taskbarUnfoldAnimationController.init(this);
taskbarKeyguardController.init(navbarButtonsViewController);
stashedHandleViewController.init(this);
- taskbarStashController.init(this);
+ taskbarStashController.init(this, sharedState);
taskbarEduController.init(this);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 9d88956..63d07f3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -76,7 +76,7 @@
* Cache a copy here so we can initialize state whenever taskbar is recreated, since
* this class does not get re-initialized w/ new taskbars.
*/
- private int mSysuiStateFlags;
+ private final TaskbarSharedState mSharedState = new TaskbarSharedState();
private static final int CHANGE_FLAGS =
CHANGE_ACTIVE_SCREEN | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS;
@@ -192,22 +192,27 @@
mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp.copy(mContext),
mNavButtonController, mUnfoldProgressProvider);
- mTaskbarActivityContext.init();
+ mTaskbarActivityContext.init(mSharedState);
if (mLauncher != null) {
mTaskbarActivityContext.setUIController(
new LauncherTaskbarUIController(mLauncher, mTaskbarActivityContext));
}
- onSysuiFlagsChangedInternal(mSysuiStateFlags, true /* forceUpdate */);
}
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
- onSysuiFlagsChangedInternal(systemUiStateFlags, false /* forceUpdate */);
+ mSharedState.sysuiStateFlags = systemUiStateFlags;
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.updateSysuiStateFlags(systemUiStateFlags);
+ }
}
- private void onSysuiFlagsChangedInternal(int systemUiStateFlags, boolean forceUpdate) {
- mSysuiStateFlags = systemUiStateFlags;
+ /**
+ * Sets the flag indicating setup UI is visible
+ */
+ public void setSetupUIVisible(boolean isVisible) {
+ mSharedState.setupUIVisible = isVisible;
if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.updateSysuiStateFlags(systemUiStateFlags, forceUpdate);
+ mTaskbarActivityContext.setSetupUIVisible(isVisible);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
new file mode 100644
index 0000000..23beef0
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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.taskbar;
+
+/**
+ * State shared across different taskbar instance
+ */
+public class TaskbarSharedState {
+
+ public int sysuiStateFlags;
+
+ public boolean setupUIVisible = false;
+
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 7a89224..0dd4ef1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -25,21 +25,15 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.ComponentName;
import android.content.SharedPreferences;
import android.content.res.Resources;
-import android.provider.Settings;
import android.view.ViewConfiguration;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
-import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.interaction.AllSetActivity;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.util.function.IntPredicate;
@@ -136,7 +130,7 @@
mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize;
}
- public void init(TaskbarControllers controllers) {
+ public void init(TaskbarControllers controllers, TaskbarSharedState sharedState) {
mControllers = controllers;
TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
@@ -157,7 +151,8 @@
boolean isManuallyStashedInApp = supportsManualStashing()
&& mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
- updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup());
+ updateStateForFlag(FLAG_STASHED_IN_APP_SETUP,
+ !mActivity.isUserSetupComplete() || sharedState.setupUIVisible);
applyState();
SystemUiProxy.INSTANCE.get(mActivity)
@@ -186,20 +181,12 @@
}
/**
- * Returns whether we are in Setup Wizard or the corresponding AllSetActivity that follows it.
+ * Sets the flag indicating setup UI is visible
*/
- private boolean isInSetup() {
- boolean isInSetup = !SettingsCache.INSTANCE.get(mActivity).getValue(
- Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
- if (isInSetup) {
- return true;
- }
- ActivityManager.RunningTaskInfo runningTask =
- ActivityManagerWrapper.getInstance().getRunningTask();
- if (runningTask == null || runningTask.baseActivity == null) {
- return false;
- }
- return runningTask.baseActivity.equals(new ComponentName(mActivity, AllSetActivity.class));
+ protected void setSetupUIVisible(boolean isVisible) {
+ updateStateForFlag(FLAG_STASHED_IN_APP_SETUP,
+ isVisible || !mActivity.isUserSetupComplete());
+ applyState();
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index ff04799..d14622b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -88,7 +88,10 @@
mTaskbarIconScaleForStash.updateValue(1f);
mModelCallbacks.init(controllers);
- LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks);
+ if (mActivity.isUserSetupComplete()) {
+ // Only load the callbacks if user setup is completed
+ LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks);
+ }
mTaskbarNavButtonTranslationY =
controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 6ccb152..95ab62f 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -63,6 +63,17 @@
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, focusedTaskRect.height());
return response;
}
+
+ case TestProtocol.REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET: {
+ if (!mDeviceProfile.isTablet) {
+ return null;
+ }
+ Rect gridTaskRect = new Rect();
+ LauncherActivityInterface.INSTANCE.calculateGridTaskSize(mContext, mDeviceProfile,
+ gridTaskRect, PagedOrientationHandler.PORTRAIT);
+ response.putParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD, gridTaskRect);
+ return response;
+ }
}
return super.call(method, arg);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 8a9bf7c..e2441ed 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -84,7 +84,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Manages the state of the system during a swipe up gesture.
@@ -398,14 +397,6 @@
}
/**
- * @return the packages of gesture-blocked activities.
- */
- public List<String> getGestureBlockedActivityPackages() {
- return mGestureBlockedActivities.stream().map(ComponentName::getPackageName)
- .collect(Collectors.toList());
- }
-
- /**
* Updates the system ui state flags from SystemUI.
*/
public void setSystemUiFlags(int stateFlags) {
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index dcc7ccc..8c4ba97 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -128,7 +128,7 @@
}
}
- class MultiWindowSystemShortcut extends SystemShortcut {
+ class MultiWindowSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
private Handler mHandler;
@@ -305,7 +305,7 @@
return new PinSystemShortcut(activity, taskContainer);
};
- class PinSystemShortcut extends SystemShortcut {
+ class PinSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
private static final String TAG = "PinSystemShortcut";
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 46b228e..4844f6b 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -66,7 +66,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
@@ -972,22 +971,6 @@
mInputConsumer);
}
- protected boolean shouldNotifyBackGesture() {
- return mBackGestureNotificationCounter > 0 &&
- !mDeviceState.getGestureBlockedActivityPackages().isEmpty();
- }
-
- @WorkerThread
- protected void tryNotifyBackGesture() {
- if (shouldNotifyBackGesture()) {
- mBackGestureNotificationCounter--;
- Utilities.getDevicePrefs(this).edit()
- .putInt(KEY_BACK_NOTIFICATION_COUNT, mBackGestureNotificationCounter).apply();
- mDeviceState.getGestureBlockedActivityPackages().forEach(blockedPackage ->
- sendBroadcast(new Intent(NOTIFY_ACTION_BACK).setPackage(blockedPackage)));
- }
- }
-
@Override
public void writeToProto(LauncherTraceProto.Builder proto) {
TouchInteractionServiceProto.Builder serviceProto =
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 4472bdc..f731cb3 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -32,6 +32,8 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.util.TISBindHelper;
import java.net.URISyntaxException;
@@ -47,6 +49,9 @@
private static final String EXTRA_ACCENT_COLOR_DARK_MODE = "suwColorAccentDark";
private static final String EXTRA_ACCENT_COLOR_LIGHT_MODE = "suwColorAccentLight";
+ private TISBindHelper mTISBindHelper;
+ private TISBinder mBinder;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -73,6 +78,34 @@
});
findViewById(R.id.hint).setAccessibilityDelegate(new SkipButtonAccessibilityDelegate());
+ mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (mBinder != null) {
+ mBinder.getTaskbarManager().setSetupUIVisible(true);
+ }
+ }
+
+ private void onTISConnected(TISBinder binder) {
+ mBinder = binder;
+ mBinder.getTaskbarManager().setSetupUIVisible(isResumed());
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mBinder != null) {
+ mBinder.getTaskbarManager().setSetupUIVisible(false);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mTISBindHelper.onDestroy();
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/TISBindHelper.java b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
new file mode 100644
index 0000000..92c60c8
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2021 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 android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.TouchInteractionService.TISBinder;
+
+import java.util.function.Consumer;
+
+/**
+ * Utility class to simplify binding to {@link TouchInteractionService}
+ */
+public class TISBindHelper implements ServiceConnection {
+
+ private static final String TAG = "TISBindHelper";
+
+ private static final long BACKOFF_MILLIS = 1000;
+
+ // Max backoff caps at 5 mins
+ private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
+
+ private final Handler mHandler = new Handler();
+ private final Runnable mConnectionRunnable = this::internalBindToTIS;
+ private final Context mContext;
+ private final Consumer<TISBinder> mConnectionCallback;
+
+ private short mConnectionAttempts;
+ private boolean mTisServiceBound;
+
+ public TISBindHelper(Context context, Consumer<TISBinder> connectionCallback) {
+ mContext = context;
+ mConnectionCallback = connectionCallback;
+ internalBindToTIS();
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+ if (!(iBinder instanceof TISBinder)) {
+ // Seems like there can be a race condition when user unlocks, which kills the TIS
+ // process and re-starts it. I guess in the meantime service can be connected to
+ // a killed TIS? Either way, unbind and try to re-connect in that case.
+ internalUnbindToTIS();
+ mHandler.postDelayed(mConnectionRunnable, BACKOFF_MILLIS);
+ return;
+ }
+
+ Log.d(TAG, "TIS service connected");
+ mConnectionCallback.accept((TISBinder) iBinder);
+ resetServiceBindRetryState();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) { }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ Log.w(TAG, "TIS binding died");
+ internalBindToTIS();
+ }
+
+
+ /**
+ * Binds to {@link TouchInteractionService}. If the binding fails, attempts to retry via
+ * {@link #mConnectionRunnable}. Unbind via {@link #internalUnbindToTIS()}
+ */
+ private void internalBindToTIS() {
+ mTisServiceBound = mContext.bindService(new Intent(mContext, TouchInteractionService.class),
+ this, 0);
+ if (mTisServiceBound) {
+ resetServiceBindRetryState();
+ return;
+ }
+
+ Log.w(TAG, "Retrying TIS Binder connection attempt: " + mConnectionAttempts);
+ final long timeoutMs = (long) Math.min(
+ Math.scalb(BACKOFF_MILLIS, mConnectionAttempts), MAX_BACKOFF_MILLIS);
+ mHandler.postDelayed(mConnectionRunnable, timeoutMs);
+ mConnectionAttempts++;
+ }
+
+ /** See {@link #internalBindToTIS()} */
+ private void internalUnbindToTIS() {
+ if (mTisServiceBound) {
+ mContext.unbindService(this);
+ mTisServiceBound = false;
+ }
+ }
+
+ private void resetServiceBindRetryState() {
+ if (mHandler.hasCallbacks(mConnectionRunnable)) {
+ mHandler.removeCallbacks(mConnectionRunnable);
+ }
+ mConnectionAttempts = 0;
+ }
+
+ /**
+ * Called when the activity is destroyed to clear the binding
+ */
+ public void onDestroy() {
+ internalUnbindToTIS();
+ resetServiceBindRetryState();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 4d488db..292e9d7 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1484,6 +1484,20 @@
return groupViewCount;
}
+ /**
+ * Returns the number of tasks in the top row of the overview grid.
+ */
+ public int getTopRowTaskCountForTablet() {
+ return mTopRowIdSet.size();
+ }
+
+ /**
+ * Returns the number of tasks in the bottom row of the overview grid.
+ */
+ public int getBottomRowTaskCountForTablet() {
+ return getTaskViewCount() - mTopRowIdSet.size() - 1;
+ }
+
protected void onTaskStackUpdated() {
// Lazily update the empty message only when the task stack is reapplied
updateEmptyMessage();
@@ -3170,7 +3184,7 @@
* Returns all the tasks in the bottom row, without the focused task
*/
private IntArray getBottomRowIdArray() {
- int bottomRowIdArraySize = getTaskViewCount() - mTopRowIdSet.size() - 1;
+ int bottomRowIdArraySize = getBottomRowTaskCountForTablet();
if (bottomRowIdArraySize <= 0) {
return new IntArray(0);
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 3b7370f..4895b10 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -146,7 +146,8 @@
// Test dismissing all tasks.
mLauncher.pressHome().switchToOverview().dismissAllTasks();
- waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+ assertTrue("Launcher internal state is not Home",
+ isInState(() -> LauncherState.NORMAL));
executeOnLauncher(
launcher -> assertEquals("Still have tasks after dismissing all",
0, getTaskCount(launcher)));
@@ -180,6 +181,14 @@
return launcher.<RecentsView>getOverviewPanel().getTaskViewCount();
}
+ private int getTopRowTaskCountForTablet(Launcher launcher) {
+ return launcher.<RecentsView>getOverviewPanel().getTopRowTaskCountForTablet();
+ }
+
+ private int getBottomRowTaskCountForTablet(Launcher launcher) {
+ return launcher.<RecentsView>getOverviewPanel().getBottomRowTaskCountForTablet();
+ }
+
@Test
@NavigationModeSwitch
@PortraitLandscape
@@ -276,4 +285,70 @@
isTestActivityRunning(2));
getAndAssertBackground();
}
+
+ @Test
+ @PortraitLandscape
+ public void testOverviewForTablet() throws Exception {
+ if (!mLauncher.isTablet()) {
+ return;
+ }
+ for (int i = 2; i <= 12; i++) {
+ startTestActivity(i);
+ }
+
+ Overview overview = mLauncher.pressHome().switchToOverview();
+ executeOnLauncher(
+ launcher -> assertTrue("Don't have at least 11 tasks",
+ getTaskCount(launcher) >= 11));
+
+ // Test scroll the first task off screen
+ overview.scrollCurrentTaskOffScreen();
+ assertTrue("Launcher internal state is not Overview",
+ isInState(() -> LauncherState.OVERVIEW));
+ executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
+ getCurrentOverviewPage(launcher) > 0));
+
+ // Test opening the task.
+ overview.getCurrentTask().open();
+ assertTrue("Test activity didn't open from Overview",
+ mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity8")),
+ DEFAULT_UI_TIMEOUT));
+
+ // Scroll the task offscreen as it is now first
+ overview = mLauncher.pressHome().switchToOverview();
+ overview.scrollCurrentTaskOffScreen();
+ assertTrue("Launcher internal state is not Overview",
+ isInState(() -> LauncherState.OVERVIEW));
+ executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
+ getCurrentOverviewPage(launcher) > 0));
+
+ // Test dismissing the later task.
+ final Integer numTasks = getFromLauncher(this::getTaskCount);
+ overview.getCurrentTask().dismiss();
+ executeOnLauncher(
+ launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
+ numTasks - 1, getTaskCount(launcher)));
+ executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after dismissal",
+ (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
+ launcher)) <= 1)));
+
+ // Test dismissing more tasks.
+ assertTrue("Launcher internal state didn't remain in Overview",
+ isInState(() -> LauncherState.OVERVIEW));
+ overview.getCurrentTask().dismiss();
+ assertTrue("Launcher internal state didn't remain in Overview",
+ isInState(() -> LauncherState.OVERVIEW));
+ overview.getCurrentTask().dismiss();
+ executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple dismissals",
+ (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
+ launcher)) <= 1)));
+
+ // Test dismissing all tasks.
+ mLauncher.pressHome().switchToOverview().dismissAllTasks();
+ assertTrue("Launcher internal state is not Home",
+ isInState(() -> LauncherState.NORMAL));
+ executeOnLauncher(
+ launcher -> assertEquals("Still have tasks after dismissing all",
+ 0, getTaskCount(launcher)));
+ }
}
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index ebfd281..1bfd7b5 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -215,7 +215,7 @@
dl.addView(frame);
frame.mIsOpen = true;
- frame.snapToWidget(false);
+ frame.post(() -> frame.snapToWidget(false));
}
private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 7954011..dd56ca3 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -41,7 +41,6 @@
import android.view.ActionMode;
import android.view.Display;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.WindowInsets.Type;
import android.view.WindowMetrics;
import android.widget.Toast;
@@ -166,12 +165,6 @@
// no-op
}
- public Rect getViewBounds(View v) {
- int[] pos = new int[2];
- v.getLocationOnScreen(pos);
- return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
- }
-
@NonNull
public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
int left = 0, top = 0;
@@ -206,7 +199,7 @@
// Prepare intent
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (v != null) {
- intent.setSourceBounds(getViewBounds(v));
+ intent.setSourceBounds(Utilities.getViewBounds(v));
}
try {
boolean isShortcut = (item instanceof WorkspaceItemInfo)
@@ -316,7 +309,8 @@
}
}
- public OnClickListener getItemOnClickListener() {
+ @Override
+ public View.OnClickListener getItemOnClickListener() {
return ItemClickHandler.INSTANCE;
}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 521d8f4..7811047 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_ICON_LABEL_AUTO_SCALING;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
@@ -33,6 +34,7 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.icu.text.MessageFormat;
+import android.text.TextPaint;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.Property;
@@ -66,6 +68,7 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BubbleTextHolder;
import com.android.launcher3.views.IconLabelDotView;
import java.text.NumberFormat;
@@ -87,6 +90,9 @@
private static final int DISPLAY_SEARCH_RESULT = 6;
private static final int DISPLAY_SEARCH_RESULT_SMALL = 7;
+ private static final float MIN_LETTER_SPACING = -0.5f;
+ private static final int MAX_SEARCH_LOOP_COUNT = 20;
+
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
private static final float HIGHLIGHT_SCALE = 1.16f;
@@ -162,7 +168,7 @@
private HandlerRunnable mIconLoadRequest;
private boolean mEnableIconUpdateAnimation = false;
- private ItemInfoUpdateReceiver mItemInfoUpdateReceiver;
+ private BubbleTextHolder mBubbleTextHolder;
public BubbleTextView(Context context) {
this(context, null, 0);
@@ -240,7 +246,6 @@
mDotParams.scale = 0f;
mForceHideDot = false;
setBackground(null);
- mItemInfoUpdateReceiver = null;
}
private void cancelDotScaleAnim() {
@@ -340,14 +345,14 @@
private void setItemInfo(ItemInfoWithIcon itemInfo) {
setTag(itemInfo);
- if (mItemInfoUpdateReceiver != null) {
- mItemInfoUpdateReceiver.reapplyItemInfo(itemInfo);
+ if (mBubbleTextHolder != null) {
+ mBubbleTextHolder.onItemInfoUpdated(itemInfo);
}
}
- public void setItemInfoUpdateReceiver(
- ItemInfoUpdateReceiver itemInfoUpdateReceiver) {
- mItemInfoUpdateReceiver = itemInfoUpdateReceiver;
+ public void setBubbleTextHolder(
+ BubbleTextHolder bubbleTextHolder) {
+ mBubbleTextHolder = bubbleTextHolder;
}
@UiThread
@@ -460,6 +465,75 @@
return result;
}
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ checkForEllipsis();
+ }
+
+ @Override
+ protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
+ super.onTextChanged(text, start, lengthBefore, lengthAfter);
+ checkForEllipsis();
+ }
+
+ private void checkForEllipsis() {
+ if (!ENABLE_ICON_LABEL_AUTO_SCALING.get()) {
+ return;
+ }
+ float width = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
+ if (width <= 0) {
+ return;
+ }
+ setLetterSpacing(0);
+
+ String text = getText().toString();
+ TextPaint paint = getPaint();
+ if (paint.measureText(text) < width) {
+ return;
+ }
+
+ float spacing = findBestSpacingValue(paint, text, width, MIN_LETTER_SPACING);
+ // Reset the paint value so that the call to TextView does appropriate diff.
+ paint.setLetterSpacing(0);
+ setLetterSpacing(spacing);
+ }
+
+ /**
+ * Find the appropriate text spacing to display the provided text
+ * @param paint the paint used by the text view
+ * @param text the text to display
+ * @param allowedWidthPx available space to render the text
+ * @param minSpacingEm minimum spacing allowed between characters
+ * @return the final textSpacing value
+ *
+ * @see #setLetterSpacing(float)
+ */
+ private float findBestSpacingValue(TextPaint paint, String text, float allowedWidthPx,
+ float minSpacingEm) {
+ paint.setLetterSpacing(minSpacingEm);
+ if (paint.measureText(text) > allowedWidthPx) {
+ // If there is no result at high limit, we can do anything more
+ return minSpacingEm;
+ }
+
+ float lowLimit = 0;
+ float highLimit = minSpacingEm;
+
+ for (int i = 0; i < MAX_SEARCH_LOOP_COUNT; i++) {
+ float value = (lowLimit + highLimit) / 2;
+ paint.setLetterSpacing(value);
+ if (paint.measureText(text) < allowedWidthPx) {
+ highLimit = value;
+ } else {
+ lowLimit = value;
+ }
+ }
+
+ // At the end error on the higher side
+ return highLimit;
+ }
+
@SuppressWarnings("wrongcall")
protected void drawWithoutDot(Canvas canvas) {
super.onDraw(canvas);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index f429d76..8d92bf2 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1391,22 +1391,7 @@
final LauncherAppWidgetHostView launcherHostView = (LauncherAppWidgetHostView) hostView;
CellLayout cellLayout = getCellLayout(launcherInfo.container, launcherInfo.screenId);
if (mStateManager.getState() == NORMAL) {
- // Show resize frame once the widget layout is drawn.
- View.OnLayoutChangeListener onLayoutChangeListener =
- new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View view, int left, int top, int right,
- int bottom, int oldLeft, int oldTop, int oldRight,
- int oldBottom) {
- AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
- launcherHostView.removeOnLayoutChangeListener(this);
- }
- };
- launcherHostView.addOnLayoutChangeListener(onLayoutChangeListener);
- // There is a small chance that the layout was already drawn before the layout
- // change listener was registered, which means that the resize frame wouldn't be
- // shown. Directly call requestLayout to force a layout change.
- launcherHostView.requestLayout();
+ AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
} else {
mStateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
@Override
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 36faeee..7a38fe7 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -69,6 +69,7 @@
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
import androidx.core.graphics.ColorUtils;
import androidx.core.os.BuildCompat;
@@ -846,6 +847,12 @@
view.setLayoutParams(lp);
}
+ public static Rect getViewBounds(@NonNull View v) {
+ int[] pos = new int[2];
+ v.getLocationOnScreen(pos);
+ return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
+ }
+
private static class FixedSizeEmptyDrawable extends ColorDrawable {
private final int mSize;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 8e76d82..8095280 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1676,7 +1676,7 @@
}
if (child instanceof BubbleTextView && !dragOptions.isAccessibleDrag) {
- PopupContainerWithArrow popupContainer = PopupContainerWithArrow
+ PopupContainerWithArrow<Launcher> popupContainer = PopupContainerWithArrow
.showForIcon((BubbleTextView) child);
if (popupContainer != null) {
dragOptions.preDragCondition = popupContainer.createPreDragCondition();
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index f154dd4..3ba6ea4 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -485,8 +485,9 @@
mViewPager = (AllAppsPagedView) newView;
mViewPager.initParentViews(this);
mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
- mWorkManager.attachWorkModeSwitch();
- mWorkManager.getWorkModeSwitch().post(() -> mAH[AdapterHolder.WORK].applyPadding());
+ if (mWorkManager.attachWorkModeSwitch()) {
+ mWorkManager.getWorkModeSwitch().post(() -> mAH[AdapterHolder.WORK].applyPadding());
+ }
} else {
mWorkManager.detachWorkModeSwitch();
mViewPager = null;
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 54a5c51..e223248 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -28,6 +28,7 @@
import android.util.Log;
import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.launcher3.R;
@@ -127,11 +128,11 @@
/**
* Creates and attaches for profile toggle button to {@link AllAppsContainerView}
*/
- public void attachWorkModeSwitch() {
+ public boolean attachWorkModeSwitch() {
if (!mAllApps.getAppsStore().hasModelFlag(
FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION)) {
- Log.e(TAG, "Unable to attach widget; Missing required permissions");
- return;
+ Log.e(TAG, "unable to attach work mode switch; Missing required permissions");
+ return false;
}
if (mWorkModeSwitch == null) {
mWorkModeSwitch = (WorkModeSwitch) mAllApps.getLayoutInflater().inflate(
@@ -144,6 +145,7 @@
getAH().applyPadding();
}
mWorkModeSwitch.updateCurrentState(mCurrentState == STATE_ENABLED);
+ return true;
}
/**
@@ -165,6 +167,7 @@
return mMatcher;
}
+ @Nullable
public WorkModeSwitch getWorkModeSwitch() {
return mWorkModeSwitch;
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index a3d9ba7..796c912 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -252,6 +252,10 @@
"ENABLE_BACK_SWIPE_HOME_ANIMATION", true,
"Enables home animation to icon when user swipes back.");
+ public static final BooleanFlag ENABLE_ICON_LABEL_AUTO_SCALING = getDebugFlag(
+ "ENABLE_ICON_LABEL_AUTO_SCALING", true,
+ "Enables scaling/spacing for icon labels to make more characters visible");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index d6e927b..b963950 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -66,6 +66,7 @@
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
import java.util.ArrayList;
@@ -81,7 +82,7 @@
*
* @param <T> The activity on with the popup shows
*/
-public class PopupContainerWithArrow<T extends BaseDraggingActivity>
+public class PopupContainerWithArrow<T extends Context & ActivityContext>
extends ArrowPopup<T> implements DragSource, DragController.DragListener {
private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
@@ -190,10 +191,10 @@
}
/**
- * Shows the notifications and deep shortcuts associated with {@param icon}.
+ * Shows the notifications and deep shortcuts associated with a Launcher {@param icon}.
* @return the container if shown or null.
*/
- public static PopupContainerWithArrow showForIcon(BubbleTextView icon) {
+ public static PopupContainerWithArrow<Launcher> showForIcon(BubbleTextView icon) {
Launcher launcher = Launcher.getLauncher(icon.getContext());
if (getOpen(launcher) != null) {
// There is already an items container open, so don't open this one.
@@ -205,7 +206,7 @@
return null;
}
- final PopupContainerWithArrow container =
+ final PopupContainerWithArrow<Launcher> container =
(PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
R.layout.popup_container, launcher.getDragLayer(), false);
container.configureForLauncher(launcher);
@@ -489,8 +490,8 @@
/**
* Returns a PopupContainerWithArrow which is already open or null
*/
- public static PopupContainerWithArrow getOpen(BaseDraggingActivity launcher) {
- return getOpenView(launcher, TYPE_ACTION_POPUP);
+ public static <T extends Context & ActivityContext> PopupContainerWithArrow getOpen(T context) {
+ return getOpenView(context, TYPE_ACTION_POPUP);
}
/**
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 5ed6f2e..1dce1f2 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.os.Handler;
import android.os.UserHandle;
@@ -26,7 +27,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.ItemInfo;
@@ -36,6 +36,7 @@
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
import java.util.Collections;
@@ -128,7 +129,8 @@
/**
* Returns a runnable to update the provided shortcuts and notifications
*/
- public static Runnable createUpdateRunnable(final BaseDraggingActivity launcher,
+ public static <T extends Context & ActivityContext> Runnable createUpdateRunnable(
+ final T context,
final ItemInfo originalInfo,
final Handler uiHandler, final PopupContainerWithArrow container,
final List<DeepShortcutView> shortcutViews,
@@ -144,22 +146,22 @@
infos = Collections.emptyList();
} else {
infos = notificationListener.getNotificationsForKeys(notificationKeys).stream()
- .map(sbn -> new NotificationInfo(launcher, sbn, originalInfo))
+ .map(sbn -> new NotificationInfo(context, sbn, originalInfo))
.collect(Collectors.toList());
}
uiHandler.post(() -> container.applyNotificationInfos(infos));
}
- List<ShortcutInfo> shortcuts = new ShortcutRequest(launcher, user)
+ List<ShortcutInfo> shortcuts = new ShortcutRequest(context, user)
.withContainer(activity)
.query(ShortcutRequest.PUBLISHED);
String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
: notificationKeys.get(0).shortcutId;
shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
- IconCache cache = LauncherAppState.getInstance(launcher).getIconCache();
+ IconCache cache = LauncherAppState.getInstance(context).getIconCache();
for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
final ShortcutInfo shortcut = shortcuts.get(i);
- final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, launcher);
+ final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, context);
cache.getUnbadgedShortcutIcon(si, shortcut);
si.rank = i;
si.container = CONTAINER_SHORTCUTS;
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index e5424cf..826c79b 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -18,12 +18,14 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.WidgetsBottomSheet;
import java.util.List;
@@ -35,7 +37,7 @@
* Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
* @param <T>
*/
-public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo
+public abstract class SystemShortcut<T extends Context & ActivityContext> extends ItemInfo
implements View.OnClickListener {
private final int mIconResId;
@@ -100,7 +102,7 @@
return mAccessibilityActionId == action;
}
- public interface Factory<T extends BaseDraggingActivity> {
+ public interface Factory<T extends Context & ActivityContext> {
@Nullable SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo);
}
@@ -135,9 +137,9 @@
public static final Factory<BaseDraggingActivity> APP_INFO = AppInfo::new;
- public static class AppInfo extends SystemShortcut {
+ public static class AppInfo<T extends Context & ActivityContext> extends SystemShortcut<T> {
- public AppInfo(BaseDraggingActivity target, ItemInfo itemInfo) {
+ public AppInfo(T target, ItemInfo itemInfo) {
super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target,
itemInfo);
}
@@ -145,7 +147,7 @@
@Override
public void onClick(View view) {
dismissTaskMenuView(mTarget);
- Rect sourceBounds = mTarget.getViewBounds(view);
+ Rect sourceBounds = Utilities.getViewBounds(view);
new PackageManagerHelper(mTarget).startDetailsActivityForInfo(
mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
@@ -170,7 +172,7 @@
return new Install(activity, itemInfo);
};
- public static class Install extends SystemShortcut {
+ public static class Install extends SystemShortcut<BaseDraggingActivity> {
public Install(BaseDraggingActivity target, ItemInfo itemInfo) {
super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label,
@@ -186,7 +188,7 @@
}
}
- public static void dismissTaskMenuView(BaseDraggingActivity activity) {
+ public static <T extends Context & ActivityContext> void dismissTaskMenuView(T activity) {
AbstractFloatingView.closeOpenViews(activity, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 893a215..5bf0342 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -104,6 +104,8 @@
public static final String REQUEST_GET_ACTIVITIES = "get-activities";
public static final String REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET =
"get-focused-task-height-for-tablet";
+ public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET =
+ "get-grid-task-size-rect-for-tablet";
public static Long sForcePauseTimeout;
public static final String REQUEST_SET_FORCE_PAUSE_TIMEOUT = "set-force-pause-timeout";
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index e07d71e..a2e4ad6 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -19,6 +19,7 @@
import android.content.ContextWrapper;
import android.graphics.Rect;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.View.AccessibilityDelegate;
import androidx.annotation.Nullable;
@@ -159,4 +160,10 @@
return null;
}
}
+
+ default View.OnClickListener getItemOnClickListener() {
+ return v -> {
+ // No op.
+ };
+ }
}
diff --git a/src/com/android/launcher3/views/BubbleTextHolder.java b/src/com/android/launcher3/views/BubbleTextHolder.java
index 42701c6..1cb27e1 100644
--- a/src/com/android/launcher3/views/BubbleTextHolder.java
+++ b/src/com/android/launcher3/views/BubbleTextHolder.java
@@ -16,14 +16,13 @@
package com.android.launcher3.views;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
/**
* Views that contain {@link BubbleTextView} should implement this interface.
*/
-public interface BubbleTextHolder extends IconCache.ItemInfoUpdateReceiver {
+public interface BubbleTextHolder {
BubbleTextView getBubbleText();
/**
@@ -31,6 +30,6 @@
*
* @param itemInfo the new itemInfo
*/
- @Override
- default void reapplyItemInfo(ItemInfoWithIcon itemInfo){};
+ default void onItemInfoUpdated(ItemInfoWithIcon itemInfo) {
+ }
}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 8222f75..aae8fb5 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -145,6 +145,16 @@
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts"/>
</activity>
+ <activity
+ android:name="com.android.launcher3.testcomponent.OtherBaseTestingActivity"
+ android:label="OtherLauncherTestApp"
+ android:exported="true"
+ android:taskAffinity="com.android.launcher3.testcomponent.Affinity2">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
<activity-alias android:name="Activity2"
android:label="TestActivity2"
android:exported="true"
@@ -208,31 +218,36 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
- <activity-alias android:name="Activity9"
- android:label="TestActivity9"
- android:exported="true"
- android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+ <activity-alias android:name="Activity9" android:exported="true"
+ android:label="TestActivity9"
+ android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
- <activity-alias android:name="Activity10"
- android:label="TestActivity10"
- android:exported="true"
- android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+ <activity-alias android:name="Activity10" android:exported="true"
+ android:label="TestActivity10"
+ android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
- <activity-alias android:name="Activity11"
- android:label="TestActivity11"
- android:exported="true"
- android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+ <activity-alias android:name="Activity11" android:exported="true"
+ android:label="TestActivity11"
+ android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+ <activity-alias android:name="Activity12" android:exported="true"
+ android:label="TestActivity12"
+ android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
diff --git a/tests/src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java b/tests/src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java
new file mode 100644
index 0000000..8bcab62
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.testcomponent;
+
+/**
+ * Extension of BaseTestingActivity to help test many activities open at once.
+ */
+public class OtherBaseTestingActivity extends BaseTestingActivity {}
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 5ea5d65..2c9785c 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -15,28 +15,24 @@
*/
package com.android.launcher3.ui.widget;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
-import android.view.View;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.tapl.Widget;
+import com.android.launcher3.tapl.WidgetResizeFrame;
import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.testcomponent.WidgetConfigActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
-import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.Wait.Condition;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -92,48 +88,26 @@
// Drag widget to homescreen
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
- widgets.getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))
- .dragToWorkspace(true, false);
+ WidgetResizeFrame resizeFrame =
+ widgets.getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))
+ .dragConfigWidgetToWorkspace(acceptConfig);
// Widget id for which the config activity was opened
mWidgetId = monitor.getWidgetId();
// Verify that the widget id is valid and bound
assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
- setResult(acceptConfig);
if (acceptConfig) {
- // TODO(b/192655785) Assert widget resize frame is shown and then dismiss it.
- Wait.atMost("", new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
- assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
+ assertNotNull("Widget resize frame not shown after widget added", resizeFrame);
+ resizeFrame.dismiss();
+
+ final Widget widget =
+ mLauncher.getWorkspace().tryGetWidget(mWidgetInfo.label, DEFAULT_UI_TIMEOUT);
+ assertNotNull("Widget not found on the workspace", widget);
} else {
- // Verify that the widget id is deleted.
- Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null,
- DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
- }
- }
-
- private void setResult(boolean success) {
- getInstrumentation().getTargetContext().sendBroadcast(
- WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class,
- success ? "clickOK" : "clickCancel"));
- }
-
- /**
- * Condition for searching widget id
- */
- private class WidgetSearchCondition implements Condition, ItemOperator {
-
- @Override
- public boolean isTrue() throws Throwable {
- return mMainThreadExecutor.submit(mActivityMonitor.itemExists(this)).get();
- }
-
- @Override
- public boolean evaluate(ItemInfo info, View view) {
- return info instanceof LauncherAppWidgetInfo &&
- ((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
- mWidgetInfo.provider.getClassName()) &&
- ((LauncherAppWidgetInfo) info).appWidgetId == mWidgetId;
+ final Widget widget =
+ mLauncher.getWorkspace().tryGetWidget(mWidgetInfo.label, DEFAULT_UI_TIMEOUT);
+ assertNull("Widget unexpectedly found on the workspace", widget);
}
}
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index dad4f2b..194ee4f 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -25,6 +25,7 @@
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.tapl.Widget;
+import com.android.launcher3.tapl.WidgetResizeFrame;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
@@ -53,19 +54,20 @@
final LauncherAppWidgetProviderInfo widgetInfo =
TestViewHelpers.findWidgetProvider(this, false /* hasConfigureScreen */);
- mLauncher.
+ WidgetResizeFrame resizeFrame = mLauncher.
getWorkspace().
openAllWidgets().
getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())).
- dragToWorkspace(false, false);
- // Dismiss widget resize frame.
- mDevice.pressHome();
+ dragWidgetToWorkspace();
assertTrue(mActivityMonitor.itemExists(
(info, view) -> info instanceof LauncherAppWidgetInfo &&
((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
widgetInfo.provider.getClassName())).call());
+ assertNotNull("Widget resize frame not shown after widget add", resizeFrame);
+ resizeFrame.dismiss();
+
final Widget widget = mLauncher.getWorkspace().tryGetWidget(widgetInfo.label,
DEFAULT_UI_TIMEOUT);
assertNotNull("Widget not found on the workspace", widget);
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index b037be4..0e3b501 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -25,6 +25,7 @@
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Common overview panel for both Launcher and fallback recents
@@ -53,14 +54,18 @@
}
private void flingForwardImpl() {
+ flingForwardImpl(0);
+ }
+
+ private void flingForwardImpl(int rightMargin) {
try (LauncherInstrumentation.Closable c =
mLauncher.addContextLayer("want to fling forward in overview")) {
LauncherInstrumentation.log("Overview.flingForward before fling");
final UiObject2 overview = verifyActiveContainer();
final int leftMargin =
mLauncher.getTargetInsets().left + mLauncher.getEdgeSensitivityWidth();
- mLauncher.scroll(
- overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20, false);
+ mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin + 1, 0, rightMargin, 0),
+ 20, false);
try (LauncherInstrumentation.Closable c2 =
mLauncher.addContextLayer("flung forwards")) {
verifyActiveContainer();
@@ -86,6 +91,8 @@
mLauncher.clickLauncherObject(
mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector));
+
+ mLauncher.waitUntilLauncherObjectGone(clearAllSelector);
}
}
@@ -111,6 +118,40 @@
}
/**
+ * Scrolls the current task via flinging forward until it is off screen.
+ *
+ * If only one task is present, it is only partially scrolled off screen and will still be
+ * the current task.
+ */
+ public void scrollCurrentTaskOffScreen() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to scroll current task off screen in overview")) {
+ verifyActiveContainer();
+
+ OverviewTask task = getCurrentTask();
+ mLauncher.assertNotNull("current task is null", task);
+ flingForwardImpl(task.getTaskCenterX());
+
+ try (LauncherInstrumentation.Closable c2 =
+ mLauncher.addContextLayer("scrolled task off screen")) {
+ verifyActiveContainer();
+ verifyActionsViewVisibility();
+
+ if (getTaskCount() > 1) {
+ if (mLauncher.isTablet()) {
+ mLauncher.assertTrue("current task is not grid height",
+ getCurrentTask().getVisibleHeight() == mLauncher
+ .getGridTaskRectForTablet().height());
+ }
+ mLauncher.assertTrue("Current task not scrolled off screen",
+ !getCurrentTask().equals(task));
+ }
+ }
+ }
+ }
+
+ /**
* Gets the current task in the carousel, or fails if the carousel is empty.
*
* @return the task in the middle of the visible tasks list.
@@ -130,6 +171,20 @@
return new OverviewTask(mLauncher, widestTask, this);
}
+ /**
+ * Returns a list of all tasks fully visible in the tablet grid overview.
+ */
+ @NonNull
+ public List<OverviewTask> getCurrentTasksForTablet() {
+ final List<UiObject2> taskViews = getTasks();
+ mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
+
+ final int gridTaskWidth = mLauncher.getGridTaskRectForTablet().width();
+
+ return taskViews.stream().filter(t -> t.getVisibleBounds().width() == gridTaskWidth).map(
+ t -> new OverviewTask(mLauncher, t, this)).collect(Collectors.toList());
+ }
+
@NonNull
private List<UiObject2> getTasks() {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
@@ -166,11 +221,18 @@
}
}
+ /**
+ * Returns if clear all button is visible.
+ */
+ public boolean isClearAllVisible() {
+ return mLauncher.hasLauncherObject(mLauncher.getOverviewObjectSelector("clear_all"));
+ }
+
/* TODO(b/197630182): Once b/188790554 is fixed, remove instanceof check. Currently, when
swiping from app to overview in Fallback Recents, taskbar remains and no action buttons
are visible, so we are only testing Overview for now, not BaseOverview. */
private void verifyActionsViewVisibility() {
- if (!(this instanceof Overview)) {
+ if (!(this instanceof Overview) || !hasTasks()) {
return;
}
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
@@ -198,8 +260,13 @@
/**
* Returns Overview focused task if it exists.
+ *
+ * @throws IllegalStateException if not run on a tablet device.
*/
UiObject2 getFocusedTaskForTablet() {
+ if (!mLauncher.isTablet()) {
+ throw new IllegalStateException("Must be run on tablet device.");
+ }
final List<UiObject2> taskViews = getTasks();
if (taskViews.size() == 0) {
return null;
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 55fb2c1..9ee0e25 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -326,6 +326,11 @@
TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ Rect getGridTaskRectForTablet() {
+ return ((Rect) getTestInfo(TestProtocol.REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET)
+ .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD));
+ }
+
float getExactScreenCenterX() {
return getRealDisplaySize().x / 2f;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 9419839..15bddd7 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -24,7 +24,9 @@
import com.android.launcher3.testing.TestProtocol;
+import java.util.List;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
/**
* A recent task in the overview panel carousel.
@@ -47,10 +49,14 @@
mOverview.verifyActiveContainer();
}
- private int getVisibleHeight() {
+ int getVisibleHeight() {
return mTask.getVisibleBounds().height();
}
+ int getTaskCenterX() {
+ return mTask.getVisibleCenter().x;
+ }
+
/**
* Dismisses the task by swiping up.
*/
@@ -68,6 +74,8 @@
boolean taskWasFocused = mLauncher.isTablet() && getVisibleHeight() == mLauncher
.getFocusedTaskHeightForTablet();
+ List<Integer> originalTasksCenterX = getCurrentTasksCenterXList();
+ boolean isClearAllVisibleBeforeDismiss = mOverview.isClearAllVisible();
dismissBySwipingUp();
@@ -76,6 +84,16 @@
mLauncher.assertNotNull("No task became focused",
mOverview.getFocusedTaskForTablet());
}
+ if (!isClearAllVisibleBeforeDismiss) {
+ List<Integer> currentTasksCenterX = getCurrentTasksCenterXList();
+ if (originalTasksCenterX.size() == currentTasksCenterX.size()) {
+ // Check for the same number of visible tasks before and after to
+ // avoid asserting on cases of shifting all tasks to close the distance
+ // between clear all and tasks at the end of the grid.
+ mLauncher.assertTrue("Task centers not aligned",
+ originalTasksCenterX.equals(currentTasksCenterX));
+ }
+ }
}
}
}
@@ -94,6 +112,14 @@
+ centerY, "swiping to dismiss");
}
+ private List<Integer> getCurrentTasksCenterXList() {
+ return mLauncher.isTablet()
+ ? mOverview.getCurrentTasksForTablet().stream()
+ .map(OverviewTask::getTaskCenterX)
+ .collect(Collectors.toList())
+ : List.of(mOverview.getCurrentTask().getTaskCenterX());
+ }
+
/**
* Clicks at the task.
*/
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index 3520318..f569ef4 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -16,7 +16,12 @@
package com.android.launcher3.tapl;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
import com.android.launcher3.testing.TestProtocol;
@@ -51,4 +56,55 @@
protected String launchableType() {
return "widget";
}
+
+ /**
+ * Drags a non-configurable widget from the widgets container to the workspace and returns the
+ * resize frame that is shown after the widget is added.
+ */
+ @NonNull
+ public WidgetResizeFrame dragWidgetToWorkspace() {
+ return dragWidgetToWorkspace(/* configurable= */ false, /* acceptsConfig= */ false);
+ }
+
+ /**
+ * Drags a configurable widget from the widgets container to the workspace, either accepts or
+ * cancels the configuration based on {@code acceptsConfig}, and returns the resize frame that
+ * is shown if the widget is added.
+ */
+ @Nullable
+ public WidgetResizeFrame dragConfigWidgetToWorkspace(boolean acceptsConfig) {
+ return dragWidgetToWorkspace(/* configurable= */ true, acceptsConfig);
+ }
+
+ /**
+ * Drags a widget from the widgets container to the workspace and returns the resize frame that
+ * is shown after the widget is added.
+ *
+ * <p> If {@code configurable} is true, then either accepts or cancels the configuration based
+ * on {@code acceptsConfig}.
+ */
+ @Nullable
+ private WidgetResizeFrame dragWidgetToWorkspace(
+ boolean configurable, boolean acceptsConfig) {
+ dragToWorkspace(/* startsActivity= */ configurable, /* isWidgetShortcut= */ false);
+
+ if (configurable) {
+ // Configure the widget.
+ BySelector selector = By.text(acceptsConfig ? "OK" : "Cancel");
+ mLauncher.getDevice()
+ .wait(Until.findObject(selector), LauncherInstrumentation.WAIT_TIME_MS)
+ .click();
+
+ // If the widget configuration was cancelled, then the widget wasn't added to the home
+ // screen. In that case, we cannot return a resize frame.
+ if (!acceptsConfig) {
+ return null;
+ }
+ }
+
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to get widget resize frame")) {
+ return new WidgetResizeFrame(mLauncher);
+ }
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java b/tests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
new file mode 100644
index 0000000..8f51d04
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.tapl;
+
+/** The resize frame that is shown for a widget on the workspace. */
+public class WidgetResizeFrame {
+
+ private final LauncherInstrumentation mLauncher;
+
+ WidgetResizeFrame(LauncherInstrumentation launcher) {
+ mLauncher = launcher;
+ launcher.waitForLauncherObject("widget_resize_frame");
+ }
+
+ /** Dismisses the resize frame. */
+ public void dismiss() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to dismiss widget resize frame")) {
+ // Dismiss the resize frame by pressing the home button.
+ mLauncher.getDevice().pressHome();
+ }
+ }
+}