Snap for 11908474 from 991a40ca92 to 24Q3-release
Change-Id: I38986697fe625519f89a9e792a8ce77aab3d0f43
This commit is contained in:
+9
-2
@@ -1,6 +1,13 @@
|
||||
[Builtin Hooks]
|
||||
ktfmt = true
|
||||
|
||||
[Builtin Hooks Options]
|
||||
ktfmt = --kotlinlang-style
|
||||
|
||||
[Tool Paths]
|
||||
ktfmt = ${REPO_ROOT}/prebuilts/build-tools/common/framework/ktfmt.jar
|
||||
|
||||
[Hook Scripts]
|
||||
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --config_xml tools/checkstyle.xml --sha ${PREUPLOAD_COMMIT}
|
||||
|
||||
ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check ${PREUPLOAD_FILES}
|
||||
|
||||
flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH}
|
||||
|
||||
@@ -101,6 +101,11 @@ public class AllAppsState extends LauncherState {
|
||||
return launcher.getAppsView().getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTitle() {
|
||||
return R.string.all_apps_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVerticalProgress(Launcher launcher) {
|
||||
return 0f;
|
||||
|
||||
@@ -182,6 +182,11 @@ public class OverviewState extends LauncherState {
|
||||
return launcher.getString(R.string.accessibility_recent_apps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTitle() {
|
||||
return R.string.accessibility_recent_apps;
|
||||
}
|
||||
|
||||
public static float getDefaultSwipeHeight(Launcher launcher) {
|
||||
return LayoutUtils.getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
|
||||
}
|
||||
|
||||
@@ -198,6 +198,12 @@ public class QuickstepTestInformationHandler extends TestInformationHandler {
|
||||
.unstashBubbleBarIfStashed();
|
||||
});
|
||||
return response;
|
||||
case TestProtocol.REQUEST_INJECT_FAKE_TRACKPAD:
|
||||
runOnTISBinder(tisBinder -> tisBinder.injectFakeTrackpadForTesting());
|
||||
return response;
|
||||
case TestProtocol.REQUEST_EJECT_FAKE_TRACKPAD:
|
||||
runOnTISBinder(tisBinder -> tisBinder.ejectFakeTrackpadForTesting());
|
||||
return response;
|
||||
}
|
||||
|
||||
return super.call(method, arg, extras);
|
||||
|
||||
@@ -371,6 +371,9 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> implem
|
||||
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
|
||||
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
|
||||
ACTIVITY_TRACKER.handleCreate(this);
|
||||
|
||||
// Set screen title for Talkback
|
||||
setTitle(R.string.accessibility_recent_apps);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -68,11 +68,13 @@ import android.content.IIntentSender;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Region;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.os.Trace;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.view.Choreographer;
|
||||
import android.view.InputDevice;
|
||||
@@ -83,6 +85,7 @@ import androidx.annotation.BinderThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.ConstantItem;
|
||||
@@ -146,6 +149,7 @@ import com.android.wm.shell.startingsurface.IStartingWindow;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -396,6 +400,25 @@ public class TouchInteractionService extends Service {
|
||||
return tis.mTaskbarManager;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void injectFakeTrackpadForTesting() {
|
||||
TouchInteractionService tis = mTis.get();
|
||||
if (tis == null) return;
|
||||
tis.mTrackpadsConnected.add(1000);
|
||||
tis.initInputMonitor("tapl testing");
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void ejectFakeTrackpadForTesting() {
|
||||
TouchInteractionService tis = mTis.get();
|
||||
if (tis == null) return;
|
||||
tis.mTrackpadsConnected.clear();
|
||||
// This method destroys the current input monitor if set up, and only init a new one
|
||||
// in 3-button mode if {@code mTrackpadsConnected} is not empty. So in other words,
|
||||
// it will destroy the input monitor.
|
||||
tis.initInputMonitor("tapl testing");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether a predictive back-to-home animation is in progress in the device state
|
||||
*/
|
||||
@@ -453,6 +476,47 @@ public class TouchInteractionService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private final InputManager.InputDeviceListener mInputDeviceListener =
|
||||
new InputManager.InputDeviceListener() {
|
||||
@Override
|
||||
public void onInputDeviceAdded(int deviceId) {
|
||||
if (isTrackpadDevice(deviceId)) {
|
||||
boolean wasEmpty = mTrackpadsConnected.isEmpty();
|
||||
mTrackpadsConnected.add(deviceId);
|
||||
if (wasEmpty) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int deviceId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int deviceId) {
|
||||
mTrackpadsConnected.remove(deviceId);
|
||||
if (mTrackpadsConnected.isEmpty()) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
if (mInputMonitorCompat != null && !mTrackpadsConnected.isEmpty()) {
|
||||
// Don't destroy and reinitialize input monitor due to trackpad
|
||||
// connecting when it's already set up.
|
||||
return;
|
||||
}
|
||||
initInputMonitor("onTrackpadConnected()");
|
||||
}
|
||||
|
||||
private boolean isTrackpadDevice(int deviceId) {
|
||||
InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
|
||||
return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
|
||||
| InputDevice.SOURCE_TOUCHPAD);
|
||||
}
|
||||
};
|
||||
|
||||
private static boolean sConnected = false;
|
||||
private static boolean sIsInitialized = false;
|
||||
private RotationTouchHelper mRotationTouchHelper;
|
||||
@@ -503,6 +567,8 @@ public class TouchInteractionService extends Service {
|
||||
private TaskbarManager mTaskbarManager;
|
||||
private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = i -> null;
|
||||
private AllAppsActionManager mAllAppsActionManager;
|
||||
private InputManager mInputManager;
|
||||
private final Set<Integer> mTrackpadsConnected = new ArraySet<>();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
@@ -514,6 +580,15 @@ public class TouchInteractionService extends Service {
|
||||
mDeviceState = new RecentsAnimationDeviceState(this, true);
|
||||
mAllAppsActionManager = new AllAppsActionManager(
|
||||
this, UI_HELPER_EXECUTOR, this::createAllAppsPendingIntent);
|
||||
mInputManager = getSystemService(InputManager.class);
|
||||
if (ENABLE_TRACKPAD_GESTURE.get()) {
|
||||
mInputManager.registerInputDeviceListener(mInputDeviceListener,
|
||||
UI_HELPER_EXECUTOR.getHandler());
|
||||
int [] inputDevices = mInputManager.getInputDeviceIds();
|
||||
for (int inputDeviceId : inputDevices) {
|
||||
mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
|
||||
}
|
||||
}
|
||||
mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager, mNavCallbacks);
|
||||
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
|
||||
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
|
||||
@@ -542,7 +617,8 @@ public class TouchInteractionService extends Service {
|
||||
private void initInputMonitor(String reason) {
|
||||
disposeEventHandlers("Initializing input monitor due to: " + reason);
|
||||
|
||||
if (mDeviceState.isButtonNavMode() && !ENABLE_TRACKPAD_GESTURE.get()) {
|
||||
if (mDeviceState.isButtonNavMode() && (!ENABLE_TRACKPAD_GESTURE.get()
|
||||
|| mTrackpadsConnected.isEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -678,6 +754,9 @@ public class TouchInteractionService extends Service {
|
||||
|
||||
mAllAppsActionManager.onDestroy();
|
||||
|
||||
mInputManager.unregisterInputDeviceListener(mInputDeviceListener);
|
||||
mTrackpadsConnected.clear();
|
||||
|
||||
mTaskbarManager.destroy();
|
||||
sConnected = false;
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public final class DigitalWellBeingToast {
|
||||
SPLIT_GRID_BANNER_SMALL,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface SPLIT_BANNER_CONFIG{}
|
||||
@interface SplitBannerConfig{}
|
||||
|
||||
static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
|
||||
static final int MINUTE_MS = 60000;
|
||||
@@ -88,7 +88,6 @@ public final class DigitalWellBeingToast {
|
||||
private Task mTask;
|
||||
private boolean mHasLimit;
|
||||
|
||||
private long mAppUsageLimitTimeMs;
|
||||
private long mAppRemainingTimeMs;
|
||||
@Nullable
|
||||
private View mBanner;
|
||||
@@ -96,10 +95,11 @@ public final class DigitalWellBeingToast {
|
||||
private float mBannerOffsetPercentage;
|
||||
@Nullable
|
||||
private SplitBounds mSplitBounds;
|
||||
private int mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
|
||||
private float mSplitOffsetTranslationY;
|
||||
private float mSplitOffsetTranslationX;
|
||||
|
||||
private boolean mIsDestroyed = false;
|
||||
|
||||
public DigitalWellBeingToast(RecentsViewContainer container, TaskView taskView) {
|
||||
mContainer = container;
|
||||
mTaskView = taskView;
|
||||
@@ -110,12 +110,10 @@ public final class DigitalWellBeingToast {
|
||||
mHasLimit = false;
|
||||
mTaskView.setContentDescription(mTask.titleDescription);
|
||||
replaceBanner(null);
|
||||
mAppUsageLimitTimeMs = -1;
|
||||
mAppRemainingTimeMs = -1;
|
||||
}
|
||||
|
||||
private void setLimit(long appUsageLimitTimeMs, long appRemainingTimeMs) {
|
||||
mAppUsageLimitTimeMs = appUsageLimitTimeMs;
|
||||
mAppRemainingTimeMs = appRemainingTimeMs;
|
||||
mHasLimit = true;
|
||||
TextView toast = mContainer.getViewCache().getView(R.layout.digital_wellbeing_toast,
|
||||
@@ -138,89 +136,95 @@ public final class DigitalWellBeingToast {
|
||||
}
|
||||
|
||||
public void initialize(Task task) {
|
||||
mAppUsageLimitTimeMs = mAppRemainingTimeMs = -1;
|
||||
if (mIsDestroyed) {
|
||||
throw new IllegalStateException("Cannot re-initialize a destroyed toast");
|
||||
}
|
||||
mTask = task;
|
||||
ORDERED_BG_EXECUTOR.execute(() -> {
|
||||
AppUsageLimit usageLimit = null;
|
||||
try {
|
||||
usageLimit = mLauncherApps.getAppUsageLimit(
|
||||
mTask.getTopComponent().getPackageName(),
|
||||
UserHandle.of(mTask.key.userId));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error initializing digital well being toast", e);
|
||||
}
|
||||
final long appUsageLimitTimeMs =
|
||||
usageLimit != null ? usageLimit.getTotalUsageLimit() : -1;
|
||||
final long appRemainingTimeMs =
|
||||
usageLimit != null ? usageLimit.getUsageRemaining() : -1;
|
||||
|
||||
mTaskView.post(() -> {
|
||||
if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
|
||||
setNoLimit();
|
||||
} else {
|
||||
setLimit(appUsageLimitTimeMs, appRemainingTimeMs);
|
||||
}
|
||||
});
|
||||
AppUsageLimit usageLimit = null;
|
||||
try {
|
||||
usageLimit = mLauncherApps.getAppUsageLimit(
|
||||
mTask.getTopComponent().getPackageName(),
|
||||
UserHandle.of(mTask.key.userId));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error initializing digital well being toast", e);
|
||||
}
|
||||
final long appUsageLimitTimeMs =
|
||||
usageLimit != null ? usageLimit.getTotalUsageLimit() : -1;
|
||||
final long appRemainingTimeMs =
|
||||
usageLimit != null ? usageLimit.getUsageRemaining() : -1;
|
||||
|
||||
mTaskView.post(() -> {
|
||||
if (mIsDestroyed) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
|
||||
setNoLimit();
|
||||
} else {
|
||||
setLimit(appUsageLimitTimeMs, appRemainingTimeMs);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void setSplitConfiguration(SplitBounds splitBounds) {
|
||||
/**
|
||||
* Mark the DWB toast as destroyed and remove banner from TaskView.
|
||||
*/
|
||||
public void destroy() {
|
||||
mIsDestroyed = true;
|
||||
mTaskView.post(() -> replaceBanner(null));
|
||||
}
|
||||
|
||||
public void setSplitBounds(@Nullable SplitBounds splitBounds) {
|
||||
mSplitBounds = splitBounds;
|
||||
}
|
||||
|
||||
private @SplitBannerConfig int getSplitBannerConfig() {
|
||||
if (mSplitBounds == null
|
||||
|| !mContainer.getDeviceProfile().isTablet
|
||||
|| mTaskView.isFocusedTask()) {
|
||||
mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
|
||||
return;
|
||||
return SPLIT_BANNER_FULLSCREEN;
|
||||
}
|
||||
|
||||
// For portrait grid only height of task changes, not width. So we keep the text the same
|
||||
if (!mContainer.getDeviceProfile().isLeftRightSplit) {
|
||||
mSplitBannerConfig = SPLIT_GRID_BANNER_LARGE;
|
||||
return;
|
||||
return SPLIT_GRID_BANNER_LARGE;
|
||||
}
|
||||
|
||||
// For landscape grid, for 30% width we only show icon, otherwise show icon and time
|
||||
if (mTask.key.id == mSplitBounds.leftTopTaskId) {
|
||||
mSplitBannerConfig = mSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY ?
|
||||
SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
|
||||
return mSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY
|
||||
? SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
|
||||
} else {
|
||||
mSplitBannerConfig = mSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY ?
|
||||
SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
|
||||
return mSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY
|
||||
? SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
|
||||
}
|
||||
}
|
||||
|
||||
private String getReadableDuration(
|
||||
Duration duration,
|
||||
FormatWidth formatWidthHourAndMinute,
|
||||
@StringRes int durationLessThanOneMinuteStringId,
|
||||
boolean forceFormatWidth) {
|
||||
@StringRes int durationLessThanOneMinuteStringId) {
|
||||
int hours = Math.toIntExact(duration.toHours());
|
||||
int minutes = Math.toIntExact(duration.minusHours(hours).toMinutes());
|
||||
|
||||
// Apply formatWidthHourAndMinute if both the hour part and the minute part are non-zero.
|
||||
// Apply FormatWidth.WIDE if both the hour part and the minute part are non-zero.
|
||||
if (hours > 0 && minutes > 0) {
|
||||
return MeasureFormat.getInstance(Locale.getDefault(), formatWidthHourAndMinute)
|
||||
return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.NARROW)
|
||||
.formatMeasures(
|
||||
new Measure(hours, MeasureUnit.HOUR),
|
||||
new Measure(minutes, MeasureUnit.MINUTE));
|
||||
}
|
||||
|
||||
// Apply formatWidthHourOrMinute if only the hour part is non-zero (unless forced).
|
||||
// Apply FormatWidth.WIDE if only the hour part is non-zero (unless forced).
|
||||
if (hours > 0) {
|
||||
return MeasureFormat.getInstance(
|
||||
Locale.getDefault(),
|
||||
forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
|
||||
.formatMeasures(new Measure(hours, MeasureUnit.HOUR));
|
||||
return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures(
|
||||
new Measure(hours, MeasureUnit.HOUR));
|
||||
}
|
||||
|
||||
// Apply formatWidthHourOrMinute if only the minute part is non-zero (unless forced).
|
||||
// Apply FormatWidth.WIDE if only the minute part is non-zero (unless forced).
|
||||
if (minutes > 0) {
|
||||
return MeasureFormat.getInstance(
|
||||
Locale.getDefault()
|
||||
, forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
|
||||
.formatMeasures(new Measure(minutes, MeasureUnit.MINUTE));
|
||||
return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures(
|
||||
new Measure(minutes, MeasureUnit.MINUTE));
|
||||
}
|
||||
|
||||
// Use a specific string for usage less than one minute but non-zero.
|
||||
@@ -229,13 +233,12 @@ public final class DigitalWellBeingToast {
|
||||
}
|
||||
|
||||
// Otherwise, return 0-minute string.
|
||||
return MeasureFormat.getInstance(
|
||||
Locale.getDefault(), forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
|
||||
.formatMeasures(new Measure(0, MeasureUnit.MINUTE));
|
||||
return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures(
|
||||
new Measure(0, MeasureUnit.MINUTE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns text to show for the banner depending on {@link #mSplitBannerConfig}
|
||||
* Returns text to show for the banner depending on {@link #getSplitBannerConfig()}
|
||||
* If {@param forContentDesc} is {@code true}, this will always return the full
|
||||
* string corresponding to {@link #SPLIT_BANNER_FULLSCREEN}
|
||||
*/
|
||||
@@ -245,16 +248,16 @@ public final class DigitalWellBeingToast {
|
||||
(remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS :
|
||||
remainingTime);
|
||||
String readableDuration = getReadableDuration(duration,
|
||||
FormatWidth.NARROW,
|
||||
R.string.shorter_duration_less_than_one_minute,
|
||||
false /* forceFormatWidth */);
|
||||
if (forContentDesc || mSplitBannerConfig == SPLIT_BANNER_FULLSCREEN) {
|
||||
R.string.shorter_duration_less_than_one_minute
|
||||
/* forceFormatWidth */);
|
||||
@SplitBannerConfig int splitBannerConfig = getSplitBannerConfig();
|
||||
if (forContentDesc || splitBannerConfig == SPLIT_BANNER_FULLSCREEN) {
|
||||
return mContainer.asContext().getString(
|
||||
R.string.time_left_for_app,
|
||||
readableDuration);
|
||||
}
|
||||
|
||||
if (mSplitBannerConfig == SPLIT_GRID_BANNER_SMALL) {
|
||||
if (splitBannerConfig == SPLIT_GRID_BANNER_SMALL) {
|
||||
// show no text
|
||||
return "";
|
||||
} else { // SPLIT_GRID_BANNER_LARGE
|
||||
@@ -309,7 +312,7 @@ public final class DigitalWellBeingToast {
|
||||
|
||||
private void setBanner(@Nullable View view) {
|
||||
mBanner = view;
|
||||
if (view != null && mTaskView.getRecentsView() != null) {
|
||||
if (mBanner != null && mTaskView.getRecentsView() != null) {
|
||||
setupAndAddBanner();
|
||||
setBannerOutline();
|
||||
}
|
||||
|
||||
@@ -162,6 +162,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
|
||||
PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT
|
||||
)
|
||||
}
|
||||
taskContainers.forEach { it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig) }
|
||||
setOrientationState(orientedState)
|
||||
}
|
||||
|
||||
@@ -240,6 +241,10 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
|
||||
|
||||
fun updateSplitBoundsConfig(splitBounds: SplitConfigurationOptions.SplitBounds?) {
|
||||
splitBoundsConfig = splitBounds
|
||||
taskContainers.forEach {
|
||||
it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig)
|
||||
it.digitalWellBeingToast?.initialize(it.task)
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
|
||||
@@ -512,6 +512,7 @@ constructor(
|
||||
onTaskListVisibilityChanged(false)
|
||||
borderEnabled = false
|
||||
taskViewId = UNBOUND_TASK_VIEW_ID
|
||||
taskContainers.forEach { it.destroy() }
|
||||
}
|
||||
|
||||
// TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
|
||||
@@ -801,12 +802,12 @@ constructor(
|
||||
taskContainers.forEach {
|
||||
if (visible) {
|
||||
recentsModel.iconCache
|
||||
.updateIconInBackground(it.task) { thumbnailData ->
|
||||
setIcon(it.iconView, thumbnailData.icon)
|
||||
.updateIconInBackground(it.task) { task ->
|
||||
setIcon(it.iconView, task.icon)
|
||||
if (enableOverviewIconMenu()) {
|
||||
setText(it.iconView, thumbnailData.title)
|
||||
setText(it.iconView, task.title)
|
||||
}
|
||||
it.digitalWellBeingToast?.initialize(thumbnailData)
|
||||
it.digitalWellBeingToast?.initialize(task)
|
||||
}
|
||||
?.also { request -> pendingIconLoadRequests.add(request) }
|
||||
} else {
|
||||
@@ -1586,6 +1587,11 @@ constructor(
|
||||
val taskView: TaskView
|
||||
get() = this@TaskView
|
||||
|
||||
fun destroy() {
|
||||
digitalWellBeingToast?.destroy()
|
||||
thumbnailView?.let { taskView.removeView(it) }
|
||||
}
|
||||
|
||||
// TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
|
||||
// so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
|
||||
fun bindThumbnailView() {
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.android.launcher3.util.rule.TestStabilityRule;
|
||||
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -47,8 +48,14 @@ public class TaplTestsTrackpad extends AbstractQuickStepTest {
|
||||
private static final String READ_DEVICE_CONFIG_PERMISSION =
|
||||
"android.permission.READ_DEVICE_CONFIG";
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mLauncher.injectFakeTrackpad();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mLauncher.ejectFakeTrackpad();
|
||||
mLauncher.setTrackpadGestureType(TrackpadGestureType.NONE);
|
||||
}
|
||||
|
||||
@@ -110,8 +117,8 @@ public class TaplTestsTrackpad extends AbstractQuickStepTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@NavigationModeSwitch
|
||||
@PortraitLandscape
|
||||
@NavigationModeSwitch
|
||||
public void testQuickSwitchFromHome() throws Exception {
|
||||
assumeTrue(mLauncher.isTablet());
|
||||
|
||||
|
||||
@@ -320,6 +320,8 @@
|
||||
<dimen name="bg_popup_item_height">52dp</dimen>
|
||||
<dimen name="bg_popup_item_vertical_padding">12dp</dimen>
|
||||
<dimen name="pre_drag_view_scale">6dp</dimen>
|
||||
<!-- Minimum size of the widget dragged view to keep it visible under the finger. -->
|
||||
<dimen name="widget_drag_view_min_scale_down_size">70dp</dimen>
|
||||
<!-- an icon with shortcuts must be dragged this far before the container is removed. -->
|
||||
<dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
|
||||
<!-- Possibly related to b/235886078, icon needs to be scaled up to match expected visual size of 32 dp -->
|
||||
|
||||
@@ -1268,11 +1268,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
}
|
||||
|
||||
// Set screen title for Talkback
|
||||
if (state == ALL_APPS) {
|
||||
setTitle(R.string.all_apps_label);
|
||||
} else {
|
||||
setTitle(R.string.home_screen);
|
||||
}
|
||||
setTitle(state.getTitle());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,6 +38,7 @@ import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.android.launcher3.statemanager.BaseState;
|
||||
import com.android.launcher3.statemanager.StateManager;
|
||||
@@ -369,6 +370,10 @@ public abstract class LauncherState implements BaseState<LauncherState> {
|
||||
return launcher.getWorkspace().getCurrentPageDescription();
|
||||
}
|
||||
|
||||
public @StringRes int getTitle() {
|
||||
return R.string.home_screen;
|
||||
}
|
||||
|
||||
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
|
||||
if ((this != NORMAL && this != HINT_STATE)
|
||||
|| !launcher.getDeviceProfile().shouldFadeAdjacentWorkspaceScreens()) {
|
||||
|
||||
@@ -267,13 +267,15 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
|
||||
PrivateProfileManager privateProfileManager = mApps.getPrivateProfileManager();
|
||||
if (privateProfileManager != null) {
|
||||
// Set the alpha of the private space icon to 0 upon expanding the header so the
|
||||
// alpha can animate -> 1.
|
||||
// alpha can animate -> 1. This should only be in effect when doing a
|
||||
// transitioning between Locked/Unlocked state.
|
||||
boolean isPrivateSpaceItem =
|
||||
privateProfileManager.isPrivateSpaceItem(adapterItem);
|
||||
if (icon.getAlpha() == 0 || icon.getAlpha() == 1) {
|
||||
icon.setAlpha(isPrivateSpaceItem
|
||||
&& (privateProfileManager.getAnimationScrolling() ||
|
||||
privateProfileManager.getAnimate())
|
||||
&& privateProfileManager.isStateTransitioning()
|
||||
&& (privateProfileManager.isScrolling() ||
|
||||
privateProfileManager.getReadyToAnimate())
|
||||
&& privateProfileManager.getCurrentState() == STATE_ENABLED
|
||||
? 0 : 1);
|
||||
}
|
||||
|
||||
@@ -114,16 +114,21 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
super.onScrollStateChanged(recyclerView, newState);
|
||||
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
mAnimationScrolling = false;
|
||||
mIsScrolling = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
private Intent mAppInstallerIntent = new Intent();
|
||||
private PrivateAppsSectionDecorator mPrivateAppsSectionDecorator;
|
||||
private boolean mPrivateSpaceSettingsAvailable;
|
||||
// Returns if the animation is currently running.
|
||||
private boolean mIsAnimationRunning;
|
||||
private boolean mAnimate;
|
||||
private boolean mAnimationScrolling;
|
||||
// mAnimate denotes if private space is ready to be animated.
|
||||
private boolean mReadyToAnimate;
|
||||
// Returns when the recyclerView is currently scrolling.
|
||||
private boolean mIsScrolling;
|
||||
// mIsStateTransitioning indicates that private space is transitioning between states.
|
||||
private boolean mIsStateTransitioning;
|
||||
private Runnable mOnPSHeaderAdded;
|
||||
@Nullable
|
||||
private RelativeLayout mPSHeader;
|
||||
@@ -230,9 +235,11 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
if (mPSHeader != null) {
|
||||
mPSHeader.setAlpha(1);
|
||||
}
|
||||
if (transitioningFromLockedToUnlocked(previousState, updatedState)) {
|
||||
// It's possible that previousState is 0 when reset is first called.
|
||||
mIsStateTransitioning = previousState != STATE_UNKNOWN && previousState != updatedState;
|
||||
if (previousState == STATE_DISABLED && updatedState == STATE_ENABLED) {
|
||||
postUnlock();
|
||||
} else if (transitioningFromUnlockedToLocked(previousState, updatedState)){
|
||||
} else if (previousState == STATE_ENABLED && updatedState == STATE_DISABLED){
|
||||
executeLock();
|
||||
}
|
||||
resetPrivateSpaceDecorator(updatedState);
|
||||
@@ -321,7 +328,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
@Override
|
||||
public void setQuietMode(boolean enable) {
|
||||
super.setQuietMode(enable);
|
||||
mAnimate = true;
|
||||
mReadyToAnimate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,7 +350,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
|
||||
void setAnimationRunning(boolean isAnimationRunning) {
|
||||
if (!isAnimationRunning) {
|
||||
mAnimate = false;
|
||||
mReadyToAnimate = false;
|
||||
}
|
||||
mIsAnimationRunning = isAnimationRunning;
|
||||
}
|
||||
@@ -352,14 +359,6 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
return mIsAnimationRunning;
|
||||
}
|
||||
|
||||
private boolean transitioningFromLockedToUnlocked(int previousState, int updatedState) {
|
||||
return previousState == STATE_DISABLED && updatedState == STATE_ENABLED;
|
||||
}
|
||||
|
||||
private boolean transitioningFromUnlockedToLocked(int previousState, int updatedState) {
|
||||
return previousState == STATE_ENABLED && updatedState == STATE_DISABLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<UserHandle> getUserMatcher() {
|
||||
return mPrivateProfileMatcher;
|
||||
@@ -386,7 +385,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
}
|
||||
// Set the transition duration for the settings and lock button to animate.
|
||||
ViewGroup settingAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup);
|
||||
if (mAnimate) {
|
||||
if (mReadyToAnimate) {
|
||||
enableLayoutTransition(settingAndLockGroup);
|
||||
} else {
|
||||
// Ensure any unwanted animations to not happen.
|
||||
@@ -681,6 +680,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
}
|
||||
});
|
||||
animatorSet.addListener(forEndCallback(() -> {
|
||||
mIsStateTransitioning = false;
|
||||
setAnimationRunning(false);
|
||||
getMainRecyclerView().setChildAttachedConsumer(child -> child.setAlpha(1));
|
||||
mStatsLogManager.logger().sendToInteractionJankMonitor(
|
||||
@@ -712,7 +712,6 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
animateCollapseAnimation());
|
||||
}
|
||||
}
|
||||
animatorSet.setDuration(EXPAND_COLLAPSE_DURATION);
|
||||
animatorSet.start();
|
||||
}
|
||||
|
||||
@@ -773,7 +772,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
public void endTransition(LayoutTransition transition, ViewGroup viewGroup,
|
||||
View view, int i) {
|
||||
settingsAndLockGroup.setLayoutTransition(null);
|
||||
mAnimate = false;
|
||||
mReadyToAnimate = false;
|
||||
}
|
||||
});
|
||||
settingsAndLockGroup.setLayoutTransition(settingsAndLockTransition);
|
||||
@@ -873,7 +872,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
/** Starts the smooth scroll with the provided smoothScroller and add idle listener. */
|
||||
private void startAnimationScroll(AllAppsRecyclerView allAppsRecyclerView,
|
||||
RecyclerView.LayoutManager layoutManager, RecyclerView.SmoothScroller smoothScroller) {
|
||||
mAnimationScrolling = true;
|
||||
mIsScrolling = true;
|
||||
layoutManager.startSmoothScroll(smoothScroller);
|
||||
allAppsRecyclerView.removeOnScrollListener(mOnIdleScrollListener);
|
||||
allAppsRecyclerView.addOnScrollListener(mOnIdleScrollListener);
|
||||
@@ -887,12 +886,24 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
return mAllApps.mAH.get(ActivityAllAppsContainerView.AdapterHolder.MAIN).mRecyclerView;
|
||||
}
|
||||
|
||||
boolean getAnimate() {
|
||||
return mAnimate;
|
||||
/** Returns if private space is readily available to be animated. */
|
||||
boolean getReadyToAnimate() {
|
||||
return mReadyToAnimate;
|
||||
}
|
||||
|
||||
boolean getAnimationScrolling() {
|
||||
return mAnimationScrolling;
|
||||
/** Returns when a smooth scroll is happening. */
|
||||
boolean isScrolling() {
|
||||
return mIsScrolling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns when private space is in the process of transitioning. This is different from
|
||||
* getAnimate() since mStateTransitioning checks from the time transitioning starts happening
|
||||
* in reset() as oppose to when private space is animating. This should be used to ensure
|
||||
* Private Space state during onBind().
|
||||
*/
|
||||
boolean isStateTransitioning() {
|
||||
return mIsStateTransitioning;
|
||||
}
|
||||
|
||||
int getPsHeaderHeight() {
|
||||
|
||||
@@ -40,11 +40,13 @@ import java.util.function.Predicate;
|
||||
* {@link PrivateProfileManager} which manages private profile state.
|
||||
*/
|
||||
public abstract class UserProfileManager {
|
||||
public static final int STATE_UNKNOWN = 0;
|
||||
public static final int STATE_ENABLED = 1;
|
||||
public static final int STATE_DISABLED = 2;
|
||||
public static final int STATE_TRANSITION = 3;
|
||||
|
||||
@IntDef(value = {
|
||||
STATE_UNKNOWN,
|
||||
STATE_ENABLED,
|
||||
STATE_DISABLED,
|
||||
STATE_TRANSITION
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.view.HapticFeedbackConstants;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.DragSource;
|
||||
@@ -36,6 +37,7 @@ import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.widget.util.WidgetDragScaleUtils;
|
||||
|
||||
/**
|
||||
* Drag controller for Launcher activity
|
||||
@@ -43,7 +45,6 @@ import com.android.launcher3.model.data.ItemInfo;
|
||||
public class LauncherDragController extends DragController<Launcher> {
|
||||
|
||||
private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
|
||||
|
||||
private final FlingToDeleteHelper mFlingToDeleteHelper;
|
||||
|
||||
public LauncherDragController(Launcher launcher) {
|
||||
@@ -92,8 +93,13 @@ public class LauncherDragController extends DragController<Launcher> {
|
||||
&& !mOptions.preDragCondition.shouldStartDrag(0);
|
||||
|
||||
final Resources res = mActivity.getResources();
|
||||
final float scaleDps = mIsInPreDrag
|
||||
? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
|
||||
|
||||
final float scalePx;
|
||||
if (originalView.getViewType() == DraggableView.DRAGGABLE_WIDGET) {
|
||||
scalePx = mIsInPreDrag ? 0f : getWidgetDragScalePx(drawable, view, dragInfo);
|
||||
} else {
|
||||
scalePx = mIsInPreDrag ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
|
||||
}
|
||||
final DragView dragView = mDragObject.dragView = drawable != null
|
||||
? new LauncherDragView(
|
||||
mActivity,
|
||||
@@ -102,7 +108,7 @@ public class LauncherDragController extends DragController<Launcher> {
|
||||
registrationY,
|
||||
initialDragViewScale,
|
||||
dragViewScaleOnDrop,
|
||||
scaleDps)
|
||||
scalePx)
|
||||
: new LauncherDragView(
|
||||
mActivity,
|
||||
view,
|
||||
@@ -112,7 +118,7 @@ public class LauncherDragController extends DragController<Launcher> {
|
||||
registrationY,
|
||||
initialDragViewScale,
|
||||
dragViewScaleOnDrop,
|
||||
scaleDps);
|
||||
scalePx);
|
||||
dragView.setItemInfo(dragInfo);
|
||||
mDragObject.dragComplete = false;
|
||||
|
||||
@@ -157,6 +163,29 @@ public class LauncherDragController extends DragController<Launcher> {
|
||||
return dragView;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the scale in terms of pixels (to be applied on width) to scale the preview
|
||||
* during drag and drop.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
float getWidgetDragScalePx(@Nullable Drawable drawable, @Nullable View view,
|
||||
ItemInfo dragInfo) {
|
||||
float draggedViewWidthPx = 0;
|
||||
float draggedViewHeightPx = 0;
|
||||
|
||||
if (view != null) {
|
||||
draggedViewWidthPx = view.getMeasuredWidth();
|
||||
draggedViewHeightPx = view.getMeasuredHeight();
|
||||
} else if (drawable != null) {
|
||||
draggedViewWidthPx = drawable.getIntrinsicWidth();
|
||||
draggedViewHeightPx = drawable.getIntrinsicHeight();
|
||||
}
|
||||
|
||||
return WidgetDragScaleUtils.getWidgetDragScalePx(mActivity, mActivity.getDeviceProfile(),
|
||||
draggedViewWidthPx, draggedViewHeightPx, dragInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void exitDrag() {
|
||||
if (!mActivity.isInState(EDIT_MODE)) {
|
||||
|
||||
@@ -61,7 +61,7 @@ import com.android.launcher3.widget.util.WidgetSizes;
|
||||
*/
|
||||
public class QsbContainerView extends FrameLayout {
|
||||
|
||||
public static final String SEARCH_PROVIDER_SETTINGS_KEY = "SEARCH_PROVIDER_PACKAGE_NAME";
|
||||
public static final String SEARCH_ENGINE_SETTINGS_KEY = "selected_search_engine";
|
||||
|
||||
/**
|
||||
* Returns the package name for user configured search provider or from searchManager
|
||||
@@ -71,8 +71,8 @@ public class QsbContainerView extends FrameLayout {
|
||||
@WorkerThread
|
||||
@Nullable
|
||||
public static String getSearchWidgetPackageName(@NonNull Context context) {
|
||||
String providerPkg = Settings.Global.getString(context.getContentResolver(),
|
||||
SEARCH_PROVIDER_SETTINGS_KEY);
|
||||
String providerPkg = Settings.Secure.getString(context.getContentResolver(),
|
||||
SEARCH_ENGINE_SETTINGS_KEY);
|
||||
if (providerPkg == null) {
|
||||
SearchManager searchManager = context.getSystemService(SearchManager.class);
|
||||
ComponentName componentName = searchManager.getGlobalSearchActivity();
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.widget.util;
|
||||
|
||||
import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Size;
|
||||
|
||||
import androidx.annotation.Px;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
|
||||
/** Utility classes to evaluate widget scale during drag and drops. **/
|
||||
public final class WidgetDragScaleUtils {
|
||||
// Widgets are 5% scaled down relative to their size to have shadow display well inside the
|
||||
// drop target frame (if its possible to scale it down within visible area under the finger).
|
||||
private static final float WIDGET_SCALE_DOWN = 0.05f;
|
||||
|
||||
/**
|
||||
* Returns the scale to be applied to given dragged view to scale it down relative to the
|
||||
* spring loaded workspace. Applies additional scale down offset to get it a little inside
|
||||
* the drop target frame. If the relative scale is smaller than minimum size needed to keep the
|
||||
* view visible under the finger, scale down is performed only until the minimum size.
|
||||
*/
|
||||
@Px
|
||||
public static float getWidgetDragScalePx(Context context, DeviceProfile deviceProfile,
|
||||
@Px float draggedViewWidthPx, @Px float draggedViewHeightPx, ItemInfo itemInfo) {
|
||||
int minSize = context.getResources().getDimensionPixelSize(
|
||||
R.dimen.widget_drag_view_min_scale_down_size);
|
||||
Size widgetSizesPx = getWidgetSizePx(deviceProfile, itemInfo.spanX, itemInfo.spanY);
|
||||
|
||||
// We add workspace spring load scale, since the widget's drop target is also scaled, so
|
||||
// the widget size is essentially that smaller.
|
||||
float desiredWidgetScale = deviceProfile.getWorkspaceSpringLoadScale(context)
|
||||
- WIDGET_SCALE_DOWN;
|
||||
float desiredWidgetWidthPx = Math.max(minSize,
|
||||
(desiredWidgetScale * widgetSizesPx.getWidth()));
|
||||
float desiredWidgetHeightPx = Math.max(minSize,
|
||||
desiredWidgetScale * widgetSizesPx.getHeight());
|
||||
|
||||
final float bitmapAspectRatio = draggedViewWidthPx / draggedViewHeightPx;
|
||||
final float containerAspectRatio = desiredWidgetWidthPx / desiredWidgetHeightPx;
|
||||
|
||||
// This downscales large views to fit inside drop target frame. Smaller drawable views may
|
||||
// be up-scaled if they are smaller than the min size;
|
||||
final float scale = bitmapAspectRatio >= containerAspectRatio ? desiredWidgetWidthPx
|
||||
/ draggedViewWidthPx : desiredWidgetHeightPx / draggedViewHeightPx;
|
||||
// scale in terms of dp to be applied to the drag shadow during drag and drop
|
||||
return (draggedViewWidthPx * scale) - draggedViewWidthPx;
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,11 @@ public class AllAppsState extends LauncherState {
|
||||
return launcher.getString(R.string.all_apps_button_label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTitle() {
|
||||
return R.string.all_apps_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisibleElements(Launcher launcher) {
|
||||
return ALL_APPS_CONTENT;
|
||||
|
||||
@@ -198,6 +198,9 @@ android_robolectric_test {
|
||||
"androidx.test.uiautomator_uiautomator",
|
||||
"androidx.core_core-animation-testing",
|
||||
"androidx.test.ext.junit",
|
||||
"androidx.test.espresso.core",
|
||||
"androidx.test.espresso.contrib",
|
||||
"androidx.test.espresso.intents",
|
||||
"androidx.test.rules",
|
||||
"uiautomator-helpers",
|
||||
"inline-mockito-robolectric-prebuilt",
|
||||
|
||||
@@ -183,6 +183,9 @@ public final class TestProtocol {
|
||||
public static final String REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED =
|
||||
"unstash-bubble-bar-if-stashed";
|
||||
|
||||
public static final String REQUEST_INJECT_FAKE_TRACKPAD = "inject-fake-trackpad";
|
||||
public static final String REQUEST_EJECT_FAKE_TRACKPAD = "eject-fake-trackpad";
|
||||
|
||||
/** Logs {@link Log#d(String, String)} if {@link #sDebugTracing} is true. */
|
||||
public static void testLogD(String tag, String message) {
|
||||
if (!sDebugTracing) {
|
||||
|
||||
+9
-5
@@ -25,7 +25,7 @@ import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.never
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.verifyZeroInteractions
|
||||
import org.mockito.kotlin.verifyNoMoreInteractions
|
||||
import org.mockito.kotlin.whenever
|
||||
|
||||
/** Test for AbstractFloatingViewHelper */
|
||||
@@ -60,7 +60,8 @@ class AbstractFloatingViewHelperTest {
|
||||
AbstractFloatingView.TYPE_ALL
|
||||
)
|
||||
|
||||
verifyZeroInteractions(view)
|
||||
// b/343530737
|
||||
verifyNoMoreInteractions(view)
|
||||
verify(folderView).close(true)
|
||||
verify(taskMenuView).close(true)
|
||||
}
|
||||
@@ -73,7 +74,8 @@ class AbstractFloatingViewHelperTest {
|
||||
AbstractFloatingView.TYPE_TASK_MENU
|
||||
)
|
||||
|
||||
verifyZeroInteractions(view)
|
||||
// b/343530737
|
||||
verifyNoMoreInteractions(view)
|
||||
verify(folderView, never()).close(any())
|
||||
verify(taskMenuView).close(true)
|
||||
}
|
||||
@@ -86,7 +88,8 @@ class AbstractFloatingViewHelperTest {
|
||||
AbstractFloatingView.TYPE_PIN_IME_POPUP
|
||||
)
|
||||
|
||||
verifyZeroInteractions(view)
|
||||
// b/343530737
|
||||
verifyNoMoreInteractions(view)
|
||||
verify(folderView, never()).close(any())
|
||||
verify(taskMenuView, never()).close(any())
|
||||
}
|
||||
@@ -99,7 +102,8 @@ class AbstractFloatingViewHelperTest {
|
||||
AbstractFloatingView.TYPE_FOLDER or AbstractFloatingView.TYPE_TASK_MENU
|
||||
)
|
||||
|
||||
verifyZeroInteractions(view)
|
||||
// b/343530737
|
||||
verifyNoMoreInteractions(view)
|
||||
verify(folderView).close(false)
|
||||
verify(taskMenuView).close(false)
|
||||
}
|
||||
+3
-2
@@ -28,7 +28,7 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.verifyZeroInteractions
|
||||
import org.mockito.kotlin.verifyNoMoreInteractions
|
||||
import org.mockito.kotlin.whenever
|
||||
|
||||
/** Unit tests for {@link LockedUserState} */
|
||||
@@ -58,7 +58,8 @@ class LockedUserStateTest {
|
||||
val action: Runnable = mock()
|
||||
val state = LockedUserState(context)
|
||||
state.runOnUserUnlocked(action)
|
||||
verifyZeroInteractions(action)
|
||||
// b/343530737
|
||||
verifyNoMoreInteractions(action)
|
||||
state.mUserUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
|
||||
verify(action).run()
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.widget.util
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Point
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.LauncherAppState
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.model.data.ItemInfo
|
||||
import com.android.launcher3.util.ActivityContextWrapper
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.whenever
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class WidgetDragScaleUtilsTest {
|
||||
private lateinit var context: Context
|
||||
private lateinit var itemInfo: ItemInfo
|
||||
private lateinit var deviceProfile: DeviceProfile
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
|
||||
|
||||
itemInfo = ItemInfo()
|
||||
|
||||
deviceProfile =
|
||||
Mockito.spy(LauncherAppState.getIDP(context).getDeviceProfile(context).copy(context))
|
||||
|
||||
doAnswer {
|
||||
return@doAnswer 0.8f
|
||||
}
|
||||
.whenever(deviceProfile)
|
||||
.getWorkspaceSpringLoadScale(any(Context::class.java))
|
||||
whenever(deviceProfile.cellSize).thenReturn(Point(CELL_SIZE, CELL_SIZE))
|
||||
deviceProfile.cellLayoutBorderSpacePx = Point(CELL_SPACING, CELL_SPACING)
|
||||
deviceProfile.widgetPadding.setEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getWidgetDragScalePx_largeDraggedView_downScaled() {
|
||||
itemInfo.spanX = 2
|
||||
itemInfo.spanY = 2
|
||||
val widgetSize = WidgetSizes.getWidgetSizePx(deviceProfile, itemInfo.spanX, itemInfo.spanY)
|
||||
// Assume dragged view was a drawable which was larger than widget's size.
|
||||
val draggedViewWidthPx = widgetSize.width + 0.5f * widgetSize.width
|
||||
val draggedViewHeightPx = widgetSize.height + 0.5f * widgetSize.height
|
||||
// Returns negative scale pixels - i.e. downscaled
|
||||
assertThat(
|
||||
WidgetDragScaleUtils.getWidgetDragScalePx(
|
||||
context,
|
||||
deviceProfile,
|
||||
draggedViewWidthPx,
|
||||
draggedViewHeightPx,
|
||||
itemInfo
|
||||
)
|
||||
)
|
||||
.isLessThan(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getWidgetDragScalePx_draggedViewSameAsWidgetSize_downScaled() {
|
||||
itemInfo.spanX = 4
|
||||
itemInfo.spanY = 2
|
||||
|
||||
val widgetSize = WidgetSizes.getWidgetSizePx(deviceProfile, itemInfo.spanX, itemInfo.spanY)
|
||||
// Assume dragged view was a drawable which was larger than widget's size.
|
||||
val draggedViewWidthPx = widgetSize.width.toFloat()
|
||||
val draggedViewHeightPx = widgetSize.height.toFloat()
|
||||
// Returns negative scale pixels - i.e. downscaled
|
||||
// Even if dragged view was of same size as widget's drop target, to accommodate the spring
|
||||
// load scaling of workspace and additionally getting the view inside of drop target frame,
|
||||
// widget would be downscaled.
|
||||
assertThat(
|
||||
WidgetDragScaleUtils.getWidgetDragScalePx(
|
||||
context,
|
||||
deviceProfile,
|
||||
draggedViewWidthPx,
|
||||
draggedViewHeightPx,
|
||||
itemInfo
|
||||
)
|
||||
)
|
||||
.isLessThan(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getWidgetDragScalePx_draggedViewSmallerThanMinSize_scaledSizeIsAtLeastMinSize() {
|
||||
itemInfo.spanX = 1
|
||||
itemInfo.spanY = 1
|
||||
val minSizePx =
|
||||
context.resources.getDimensionPixelSize(R.dimen.widget_drag_view_min_scale_down_size)
|
||||
|
||||
// Assume min size is greater than cell size, so that, we know the upscale of dragged view
|
||||
// is due to min size enforcement.
|
||||
assumeTrue(minSizePx > CELL_SIZE)
|
||||
|
||||
val draggedViewWidthPx = minSizePx - 15f
|
||||
val draggedViewHeightPx = minSizePx - 15f
|
||||
|
||||
// Returns positive scale pixels - i.e. up-scaled
|
||||
val finalScalePx =
|
||||
WidgetDragScaleUtils.getWidgetDragScalePx(
|
||||
context,
|
||||
deviceProfile,
|
||||
draggedViewWidthPx,
|
||||
draggedViewHeightPx,
|
||||
itemInfo
|
||||
)
|
||||
|
||||
val effectiveWidthPx = draggedViewWidthPx + finalScalePx
|
||||
val scaleFactor = (draggedViewWidthPx + finalScalePx) / draggedViewWidthPx
|
||||
val effectiveHeightPx = scaleFactor * draggedViewHeightPx
|
||||
// Both original height and width were smaller than min size, scaling them down below min
|
||||
// size would have made them not visible under the finger. Here, as expected, widget is
|
||||
// at least as large as min size.
|
||||
assertThat(effectiveWidthPx).isAtLeast(minSizePx)
|
||||
assertThat(effectiveHeightPx).isAtLeast(minSizePx)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CELL_SIZE = 60
|
||||
const val CELL_SPACING = 10
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.same
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.verifyZeroInteractions
|
||||
import org.mockito.kotlin.verifyNoMoreInteractions
|
||||
import org.mockito.kotlin.whenever
|
||||
|
||||
/** Tests for [AddWorkspaceItemsTask] */
|
||||
@@ -97,7 +97,8 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
|
||||
val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
|
||||
|
||||
assertThat(addedItems.size).isEqualTo(0)
|
||||
verifyZeroInteractions(mWorkspaceItemSpaceFinder)
|
||||
// b/343530737
|
||||
verifyNoMoreInteractions(mWorkspaceItemSpaceFinder)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -38,7 +38,7 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.backup.BackupManager;
|
||||
@@ -243,7 +243,8 @@ public class RestoreDbTaskTest {
|
||||
// Then
|
||||
assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
|
||||
assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
|
||||
verifyZeroInteractions(mMockController);
|
||||
// b/343530737
|
||||
verifyNoMoreInteractions(mMockController);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -2318,6 +2318,14 @@ public final class LauncherInstrumentation {
|
||||
getTestInfo(TestProtocol.REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED);
|
||||
}
|
||||
|
||||
public void injectFakeTrackpad() {
|
||||
getTestInfo(TestProtocol.REQUEST_INJECT_FAKE_TRACKPAD);
|
||||
}
|
||||
|
||||
public void ejectFakeTrackpad() {
|
||||
getTestInfo(TestProtocol.REQUEST_EJECT_FAKE_TRACKPAD);
|
||||
}
|
||||
|
||||
/** Blocks the taskbar from automatically stashing based on time. */
|
||||
public void enableBlockTimeout(boolean enable) {
|
||||
getTestInfo(enable
|
||||
|
||||
Reference in New Issue
Block a user