Merge master@5406228 into git_qt-dev-plus-aosp.

Change-Id: I2450207dda942ef17ddeb6a40fef610e80796611
BUG: 129345239
This commit is contained in:
Bill Rassieur
2019-03-29 04:25:56 +00:00
111 changed files with 2632 additions and 986 deletions
+2 -1
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
java_library_static {
android_library {
name: "launcher-aosp-tapl",
static_libs: [
"androidx.annotation_annotation",
@@ -27,5 +27,6 @@ java_library_static {
"src/com/android/launcher3/util/SecureSettingsObserver.java",
"src/com/android/launcher3/TestProtocol.java",
],
manifest: "tests/tapl/AndroidManifest.xml",
platform_apis: true,
}
+1 -21
View File
@@ -16,18 +16,6 @@
LOCAL_PATH := $(call my-dir)
#
# Prebuilt Java Libraries
#
include $(CLEAR_VARS)
LOCAL_MODULE := libSharedSystemUI
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_SRC_FILES := quickstep/libs/sysui_shared.jar
LOCAL_UNINSTALLABLE_MODULE := true
LOCAL_SDK_VERSION := current
include $(BUILD_PREBUILT)
include $(CLEAR_VARS)
LOCAL_MODULE := libPluginCore
LOCAL_MODULE_TAGS := optional
@@ -37,14 +25,6 @@ LOCAL_UNINSTALLABLE_MODULE := true
LOCAL_SDK_VERSION := current
include $(BUILD_PREBUILT)
include $(CLEAR_VARS)
LOCAL_MODULE := libLauncherProtos
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_SRC_FILES := libs/launcher_protos.jar
LOCAL_UNINSTALLABLE_MODULE := true
LOCAL_SDK_VERSION := current
include $(BUILD_PREBUILT)
#
# Build rule for plugin lib (needed to write a plugin).
#
@@ -53,7 +33,7 @@ LOCAL_USE_AAPT2 := true
LOCAL_AAPT2_ONLY := true
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore
LOCAL_STATIC_JAVA_LIBRARIES:= libPluginCore
LOCAL_SRC_FILES := \
$(call all-java-files-under, src_plugins)
@@ -26,12 +26,11 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import com.google.android.material.circularreveal.cardview.CircularRevealCardView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.inputmethod.InputMethodManager;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.PopupMenu;
@@ -41,6 +40,9 @@ import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
import com.google.android.material.circularreveal.cardview.CircularRevealCardView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.HashSet;
import java.util.Set;
@@ -141,6 +143,16 @@ public class SecondaryDisplayLauncher extends FragmentActivity implements AppPic
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (Intent.ACTION_MAIN.equals(intent.getAction())) {
// Hide keyboard.
final View v = getWindow().peekDecorView();
if (v != null && v.getWindowToken() != null) {
getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
v.getWindowToken(), 0);
}
}
// A new intent will bring the launcher to top. Hide the app drawer to reset the state.
showAppDrawer(false);
}
+7 -4
View File
@@ -9,6 +9,9 @@ buildscript {
}
}
final String ANDROID_TOP = "${rootDir}/../../.."
final String FRAMEWORK_PREBUILTS_DIR = "${ANDROID_TOP}/prebuilts/framework_intermediates/"
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'
@@ -148,16 +151,16 @@ dependencies {
implementation "androidx.recyclerview:recyclerview:${ANDROID_X_VERSION}"
implementation "androidx.preference:preference:${ANDROID_X_VERSION}"
implementation project(':IconLoader')
implementation fileTree(dir: "libs", include: 'launcher_protos.jar')
implementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'launcher_protos.jar')
// Recents lib dependency
withQuickstepImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
withQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
// Recents lib dependency for Go
withQuickstepIconRecentsImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
withQuickstepIconRecentsImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
// Required for AOSP to compile. This is already included in the sysui_shared.jar
withoutQuickstepImplementation fileTree(dir: "libs", include: 'plugin_core.jar')
withoutQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'plugin_core.jar')
testImplementation 'junit:junit:4.12'
androidTestImplementation "org.mockito:mockito-core:1.9.5"
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/clear_all_button_bg"/>
<corners android:radius="4dp"/>
</shape>
@@ -19,10 +19,38 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recent_task_recycler_view"
<LinearLayout
android:id="@+id/recent_task_content_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"/>
android:orientation="vertical"
android:visibility="gone">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recent_task_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:scrollbars="none"/>
<Button
android:id="@+id/clear_all_button"
android:layout_width="@dimen/clear_all_button_width"
android:layout_height="@dimen/clear_all_button_height"
android:layout_gravity="center_horizontal"
android:layout_marginVertical="@dimen/task_item_half_vert_margin"
android:background="@drawable/clear_all_button"
android:gravity="center"
android:text="@string/recents_clear_all"
android:textAllCaps="false"
android:textColor="@color/clear_all_button_text"
android:textSize="14sp"/>
</LinearLayout>
<TextView
android:id="@+id/recent_task_empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/recents_empty_message"
android:textColor="@android:color/white"
android:textSize="25sp"
android:visibility="gone"/>
</com.android.quickstep.views.IconRecentsView>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.quickstep.views.TaskItemView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<FrameLayout
android:id="@+id/task_icon_and_thumbnail"
android:layout_width="@dimen/task_thumbnail_and_icon_view_size"
android:layout_height="@dimen/task_thumbnail_and_icon_view_size"
android:layout_gravity="center_vertical"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="@dimen/task_item_half_vert_margin">
<ImageView
android:id="@+id/task_thumbnail"
android:layout_width="@dimen/task_thumbnail_width"
android:layout_height="@dimen/task_thumbnail_height"
android:layout_gravity="top|start"/>
<ImageView
android:id="@+id/task_icon"
android:layout_width="@dimen/task_icon_size"
android:layout_height="@dimen/task_icon_size"
android:layout_gravity="bottom|end"/>
</FrameLayout>
<TextView
android:id="@+id/task_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginHorizontal="8dp"
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="24sp"/>
</com.android.quickstep.views.TaskItemView>
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<color name="clear_all_button_bg">#FFDADCE0</color>
<color name="clear_all_button_text">#FF5F6368</color>
</resources>
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<dimen name="task_item_half_vert_margin">8dp</dimen>
<dimen name="task_thumbnail_and_icon_view_size">60dp</dimen>
<dimen name="task_thumbnail_height">60dp</dimen>
<dimen name="task_thumbnail_width">36dp</dimen>
<dimen name="task_icon_size">36dp</dimen>
<dimen name="clear_all_button_width">106dp</dimen>
<dimen name="clear_all_button_height">36dp</dimen>
</resources>
@@ -31,7 +31,8 @@ public final class GoLauncherAppTransitionManagerImpl extends QuickstepAppTransi
@Override
protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
//TODO: Implement this based off IconRecentsView
// Stubbed. Recents launch animation will come from the recents view itself and will not
// use remote animations.
}
@Override
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3;
import static com.android.launcher3.LauncherState.NORMAL;
import com.android.quickstep.RecentsToActivityHelper;
/**
* {@link RecentsToActivityHelper} for when the recents implementation is contained in
* {@link Launcher}.
*/
public final class LauncherRecentsToActivityHelper implements RecentsToActivityHelper {
private final Launcher mLauncher;
public LauncherRecentsToActivityHelper(Launcher launcher) {
mLauncher = launcher;
}
@Override
public void leaveRecents() {
mLauncher.getStateManager().goToState(NORMAL);
}
}
@@ -43,7 +43,7 @@ public class OverviewState extends LauncherState {
}
@Override
public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
return new float[] {1f, 0f};
}
@@ -84,9 +84,11 @@ public class OverviewState extends LauncherState {
super.onBackPressed(launcher);
}
public static float getDefaultSwipeHeight(Launcher launcher) {
DeviceProfile dp = launcher.getDeviceProfile();
return getDefaultSwipeHeight(launcher.getDeviceProfile());
}
public static float getDefaultSwipeHeight(DeviceProfile dp) {
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
}
}
@@ -28,6 +28,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.views.IconRecentsView;
import java.util.ArrayList;
@@ -35,7 +36,7 @@ import java.util.ArrayList;
* Provides recents-related {@link UiFactory} logic and classes.
*/
public abstract class RecentsUiFactory {
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true;
// Scale recents takes before animating in
private static final float RECENTS_PREPARE_SCALE = 1.33f;
@@ -87,7 +88,10 @@ public abstract class RecentsUiFactory {
*
* @param launcher the launcher activity
*/
public static void resetOverview(Launcher launcher) {}
public static void resetOverview(Launcher launcher) {
IconRecentsView recentsView = launcher.getOverviewPanel();
recentsView.setTransitionedFromApp(false);
}
/**
* Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
@@ -16,15 +16,15 @@
package com.android.launcher3.uioverrides;
import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.IconRecentsView.TRANSLATION_Y_FACTOR;
import android.util.FloatProperty;
import androidx.annotation.NonNull;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherRecentsToActivityHelper;
import com.android.quickstep.views.IconRecentsView;
import androidx.annotation.NonNull;
/**
* State handler for Go's {@link IconRecentsView}.
*/
@@ -33,11 +33,8 @@ public final class RecentsViewStateController extends
public RecentsViewStateController(@NonNull Launcher launcher) {
super(launcher);
}
@Override
FloatProperty<IconRecentsView> getTranslationYFactorProperty() {
return TRANSLATION_Y_FACTOR;
launcher.<IconRecentsView>getOverviewPanel().setRecentsToActivityHelper(
new LauncherRecentsToActivityHelper(launcher));
}
@Override
@@ -15,15 +15,30 @@
*/
package com.android.quickstep;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import com.android.launcher3.BaseDraggingActivity;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.IconRecentsView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
/**
* Provider for the atomic remote window animation from the app to the overview.
@@ -33,13 +48,17 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> implements
RemoteAnimationProvider {
private static final long RECENTS_LAUNCH_DURATION = 250;
private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50;
private static final long APP_SCALE_DOWN_DURATION = 400;
private static final String TAG = "AppToOverviewAnimationProvider";
private final ActivityControlHelper<T> mHelper;
private final int mTargetTaskId;
private IconRecentsView mRecentsView;
AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
mHelper = helper;
mTargetTaskId = targetTaskId;
}
/**
@@ -54,35 +73,161 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
false /* animate activity */, (controller) -> {
controller.dispatchOnStart();
ValueAnimator anim = controller.getAnimationPlayer()
.setDuration(RECENTS_LAUNCH_DURATION);
.setDuration(getRecentsLaunchDuration());
anim.setInterpolator(FAST_OUT_SLOW_IN);
anim.start();
});
factory.onRemoteAnimationReceived(null);
factory.createActivityController(RECENTS_LAUNCH_DURATION);
factory.createActivityController(getRecentsLaunchDuration());
mRecentsView = activity.getOverviewPanel();
return false;
}
/**
* Create remote window animation from the currently running app to the overview panel.
* Create remote window animation from the currently running app to the overview panel. Should
* be called after {@link #onActivityReady}.
*
* @param targetCompats the target apps
* @return animation from app to overview
*/
@Override
public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
//TODO: Implement the overview to app window animation for Go.
AnimatorSet anim = new AnimatorSet();
anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION));
if (mRecentsView == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "No recents view. Using stub animation.");
}
anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
return anim;
}
RemoteAnimationTargetSet targetSet =
new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING);
mRecentsView.setTransitionedFromApp(!targetSet.isAnimatingHome());
RemoteAnimationTargetCompat recentsTarget = null;
RemoteAnimationTargetCompat closingAppTarget = null;
for (RemoteAnimationTargetCompat target : targetCompats) {
if (target.mode == MODE_OPENING) {
recentsTarget = target;
} else if (target.mode == MODE_CLOSING && target.taskId == mTargetTaskId) {
closingAppTarget = target;
}
}
if (closingAppTarget == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "No closing app target. Using stub animation.");
}
anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
return anim;
}
if (recentsTarget == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "No recents target. Using stub animation.");
}
anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
return anim;
}
View thumbnailView = mRecentsView.getThumbnailViewForTask(mTargetTaskId);
if (thumbnailView == null) {
// TODO: We should either 1) guarantee the view is loaded before attempting this
// or 2) have a backup animation.
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "No thumbnail view for running task. Using stub animation.");
}
anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
return anim;
}
playAppScaleDownAnim(anim, closingAppTarget, recentsTarget, thumbnailView);
return anim;
}
/**
* Animate a closing app to scale down to the location of the thumbnail view in recents.
*
* @param anim animator set
* @param appTarget the app surface thats closing
* @param recentsTarget the surface containing recents
* @param thumbnailView the thumbnail view to animate to
*/
private void playAppScaleDownAnim(@NonNull AnimatorSet anim,
@NonNull RemoteAnimationTargetCompat appTarget,
@NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) {
// Identify where the entering remote app should animate to.
Rect endRect = new Rect();
thumbnailView.getGlobalVisibleRect(endRect);
Rect appBounds = appTarget.sourceContainerBounds;
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1);
valueAnimator.setDuration(APP_SCALE_DOWN_DURATION);
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
new SyncRtSurfaceTransactionApplierCompat(thumbnailView);
// Keep recents visible throughout the animation.
SurfaceParams[] params = new SurfaceParams[2];
params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
null /* windowCrop */, getLayer(recentsTarget, MODE_OPENING), 0 /* cornerRadius */);
valueAnimator.addUpdateListener(new MultiValueUpdateListener() {
private final FloatProp mScaleX;
private final FloatProp mScaleY;
private final FloatProp mTranslationX;
private final FloatProp mTranslationY;
private final FloatProp mAlpha;
{
// Scale down and move to view location.
float endScaleX = ((float) endRect.width()) / appBounds.width();
mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION,
ACCEL_DEACCEL);
float endScaleY = ((float) endRect.height()) / appBounds.height();
mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION,
ACCEL_DEACCEL);
float endTranslationX = endRect.left -
(appBounds.width() - thumbnailView.getWidth()) / 2.0f;
mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION,
ACCEL_DEACCEL);
float endTranslationY = endRect.top -
(appBounds.height() - thumbnailView.getHeight()) / 2.0f;
mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION,
ACCEL_DEACCEL);
// Fade out quietly near the end to be replaced by the real view.
mAlpha = new FloatProp(1.0f, 0,
APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION,
APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2);
}
@Override
public void onUpdate(float percent) {
Matrix m = new Matrix();
m.setScale(mScaleX.value, mScaleY.value,
appBounds.width() / 2.0f, appBounds.height() / 2.0f);
m.postTranslate(mTranslationX.value, mTranslationY.value);
params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m,
null /* windowCrop */, getLayer(appTarget, MODE_CLOSING),
0 /* cornerRadius */);
surfaceApplier.scheduleApply(params);
}
});
anim.play(valueAnimator);
}
/**
* Get duration of animation from app to overview.
*
* @return duration of animation
*/
long getRecentsLaunchDuration() {
return RECENTS_LAUNCH_DURATION;
return APP_SCALE_DOWN_DURATION;
}
}
@@ -26,7 +26,6 @@ import androidx.annotation.Nullable;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.IconRecentsView;
@@ -109,11 +108,6 @@ public final class FallbackActivityControllerHelper extends
return false;
}
@Override
public AlphaProperty getAlphaProperty(RecentsActivity activity) {
return activity.getDragLayer().getAlphaProperty(0);
}
@Override
public int getContainerType() {
return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
/**
* {@link RecentsToActivityHelper} for when we are using the fallback recents in
* {@link BaseRecentsActivity}.
*/
public final class FallbackRecentsToActivityHelper implements RecentsToActivityHelper {
BaseRecentsActivity mActivity;
public FallbackRecentsToActivityHelper(BaseRecentsActivity activity) {
mActivity = activity;
}
@Override
public void leaveRecents() {
mActivity.startHome();
}
}
@@ -21,10 +21,9 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.views.IconRecentsView;
import java.util.function.BiPredicate;
@@ -40,10 +39,14 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel
public AnimationFactory prepareRecentsUI(Launcher activity,
boolean activityVisible, boolean animateActivity,
Consumer<AnimatorPlaybackController> callback) {
LauncherState fromState = activity.getStateManager().getState();
//TODO: Implement this based off where the recents view needs to be for app => recents anim.
return new AnimationFactory() {
@Override
public void createActivityController(long transitionLength) {}
public void createActivityController(long transitionLength) {
callback.accept(activity.getStateManager().createAnimationToNewWorkspace(
fromState, OVERVIEW, transitionLength));
}
@Override
public void onTransitionCancelled() {}
@@ -93,11 +96,6 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel
return true;
}
@Override
public AlphaProperty getAlphaProperty(Launcher activity) {
return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
}
@Override
public int getContainerType() {
final Launcher launcher = getVisibleLauncher();
@@ -96,7 +96,7 @@ public class OverviewCommandHelper {
if (recents == null) {
return false;
}
//TODO: Launch last running task or go to home.
recents.handleOverviewCommand();
return true;
}
}
@@ -146,7 +146,7 @@ public class OverviewCommandHelper {
protected boolean handleCommand(long elapsedTime) {
IconRecentsView recents = mHelper.getVisibleRecentsView();
if (recents != null) {
//TODO: Launch next task in icon recents.
recents.handleOverviewCommand();
return true;
} else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
// The user tried to launch back into overview too quickly, either after
@@ -36,6 +36,7 @@ public final class RecentsActivity extends BaseRecentsActivity {
setContentView(R.layout.fallback_recents_activity);
mRecentsRootView = findViewById(R.id.drag_layer);
mIconRecentsView = findViewById(R.id.overview_panel);
mIconRecentsView.setRecentsToActivityHelper(new FallbackRecentsToActivityHelper(this));
}
@Override
@@ -60,7 +61,7 @@ public final class RecentsActivity extends BaseRecentsActivity {
@Override
public ActivityOptions getActivityLaunchOptions(View v) {
//TODO: Hook into recents launch animation
// Stubbed. Recents launch animation will come from the recents view itself.
return null;
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
/**
* Generic interface providing methods to the recents implementation that allow it to callback to
* the containing activity.
*/
public interface RecentsToActivityHelper {
/**
* The default action to take when leaving/closing recents. In general, this should be used to
* go to the appropriate home state.
*/
void leaveRecents();
}
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.app.ActivityOptions;
import android.view.View;
import com.android.quickstep.views.TaskItemView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
/**
* Controller that provides logic for task-related commands on recents and updating the model/view
* as appropriate.
*/
public final class TaskActionController {
private final TaskListLoader mLoader;
private final TaskAdapter mAdapter;
public TaskActionController(TaskListLoader loader, TaskAdapter adapter) {
mLoader = loader;
mAdapter = adapter;
}
/**
* Launch the task associated with the task holder, animating into the app.
*
* @param viewHolder the task view holder to launch
*/
public void launchTask(TaskHolder viewHolder) {
TaskItemView itemView = (TaskItemView) (viewHolder.itemView);
View v = itemView.getThumbnailView();
int left = 0;
int top = 0;
int width = v.getMeasuredWidth();
int height = v.getMeasuredHeight();
ActivityOptions opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(viewHolder.getTask().key,
opts, null /* resultCallback */, null /* resultCallbackHandler */);
}
/**
* Removes the task holder and the task, updating the model and the view.
*
* @param viewHolder the task view holder to remove
*/
public void removeTask(TaskHolder viewHolder) {
int position = viewHolder.getAdapterPosition();
Task task = viewHolder.getTask();
ActivityManagerWrapper.getInstance().removeTask(task.key.id);
mLoader.removeTask(task);
mAdapter.notifyItemRemoved(position);
}
/**
* Clears all tasks and updates the model and view.
*/
public void clearAllTasks() {
// TODO: Play an animation so transition is more natural.
int count = mAdapter.getItemCount();
ActivityManagerWrapper.getInstance().removeAllRecentTasks();
mLoader.clearAllTasks();
mAdapter.notifyItemRangeRemoved(0 /* positionStart */, count);
}
}
@@ -15,15 +15,19 @@
*/
package com.android.quickstep;
import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import com.android.launcher3.R;
import com.android.quickstep.views.TaskItemView;
import com.android.systemui.shared.recents.model.Task;
import java.util.ArrayList;
import java.util.List;
/**
* Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
@@ -34,26 +38,55 @@ public final class TaskAdapter extends Adapter<TaskHolder> {
private static final int MAX_TASKS_TO_DISPLAY = 6;
private static final String TAG = "TaskAdapter";
private final TaskListLoader mLoader;
private final ArrayMap<Integer, TaskItemView> mTaskIdToViewMap = new ArrayMap<>();
private TaskActionController mTaskActionController;
public TaskAdapter(@NonNull TaskListLoader loader) {
mLoader = loader;
}
public void setActionController(TaskActionController taskActionController) {
mTaskActionController = taskActionController;
}
/**
* Get task item view for a given task id if it's attached to the view.
*
* @param taskId task id to search for
* @return corresponding task item view if it's attached, null otherwise
*/
public @Nullable TaskItemView getTaskItemView(int taskId) {
return mTaskIdToViewMap.get(taskId);
}
@Override
public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// TODO: Swap in an actual task view here (view w/ icon, label, etc.)
TextView stubView = new TextView(parent.getContext());
return new TaskHolder(stubView);
TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.task_item_view, parent, false);
TaskHolder holder = new TaskHolder(itemView);
itemView.setOnClickListener(view -> mTaskActionController.launchTask(holder));
return holder;
}
@Override
public void onBindViewHolder(TaskHolder holder, int position) {
ArrayList<Task> tasks = mLoader.getCurrentTaskList();
List<Task> tasks = mLoader.getCurrentTaskList();
if (position >= tasks.size()) {
// Task list has updated.
return;
}
holder.bindTask(tasks.get(position));
}
@Override
public void onViewAttachedToWindow(@NonNull TaskHolder holder) {
mTaskIdToViewMap.put(holder.getTask().key.id, (TaskItemView) holder.itemView);
}
@Override
public void onViewDetachedFromWindow(@NonNull TaskHolder holder) {
mTaskIdToViewMap.remove(holder.getTask().key.id);
}
@Override
@@ -15,34 +15,45 @@
*/
package com.android.quickstep;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.quickstep.views.TaskItemView;
import com.android.systemui.shared.recents.model.Task;
/**
* A recycler view holder that holds the task view and binds {@link Task} content (app title, icon,
* etc.) to the view.
*/
final class TaskHolder extends ViewHolder {
public final class TaskHolder extends ViewHolder {
// TODO: Implement the actual task view to be held.
// For now, we just use a simple text view.
private final TextView mStubView;
private final TaskItemView mTaskItemView;
private Task mTask;
public TaskHolder(TextView stubView) {
super(stubView);
mStubView = stubView;
public TaskHolder(TaskItemView itemView) {
super(itemView);
mTaskItemView = itemView;
}
/**
* Bind task content to the view. This includes the task icon and title as well as binding
* input handlers such as which task to launch/remove.
*
* @param task the task to bind to the view this
* @param task the task to bind to the view
*/
public void bindTask(Task task) {
mStubView.setText("Stub task view: " + task.titleDescription);
mTask = task;
mTaskItemView.setLabel(task.titleDescription);
mTaskItemView.setIcon(task.icon);
mTaskItemView.setThumbnail(task.thumbnail.thumbnail);
}
/**
* Gets the task currently bound to this view
*
* @return the current task
*/
public @NonNull Task getTask() {
return mTask;
}
}
@@ -24,6 +24,7 @@ import com.android.systemui.shared.recents.model.Task;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -45,13 +46,13 @@ public final class TaskListLoader {
/**
* Returns the current task list as of the last completed load (see
* {@link #loadTaskList}). This list of tasks is guaranteed to always have all its task
* content loaded.
* {@link #loadTaskList}) as a read-only list. This list of tasks is guaranteed to always have
* all its task content loaded.
*
* @return the current list of tasks w/ all content loaded
*/
public ArrayList<Task> getCurrentTaskList() {
return mTaskList;
public List<Task> getCurrentTaskList() {
return Collections.unmodifiableList(mTaskList);
}
/**
@@ -85,34 +86,54 @@ public final class TaskListLoader {
}
/**
* Loads task content for a list of tasks, including the label and the icon. Uses the list of
* tasks since the last load as a cache for loaded content.
* Removes the task from the current task list.
*/
void removeTask(Task task) {
mTaskList.remove(task);
}
/**
* Clears the current task list.
*/
void clearAllTasks() {
mTaskList.clear();
}
/**
* Loads task content for a list of tasks, including the label, icon, and thumbnail. For content
* that isn't cached, load the content asynchronously in the background.
*
* @param tasksToLoad list of tasks that need to load their content
* @param onLoadedCallback runnable to run after all tasks have loaded their content
* @param onFullyLoadedCallback runnable to run after all tasks have loaded their content
*/
private void loadTaskContents(ArrayList<Task> tasksToLoad,
@Nullable Runnable onLoadedCallback) {
AtomicInteger loadRequestsCount = new AtomicInteger(0);
@Nullable Runnable onFullyLoadedCallback) {
// Make two load requests per task, one for the icon/title and one for the thumbnail.
AtomicInteger loadRequestsCount = new AtomicInteger(tasksToLoad.size() * 2);
Runnable itemLoadedRunnable = () -> {
if (loadRequestsCount.decrementAndGet() == 0 && onFullyLoadedCallback != null) {
onFullyLoadedCallback.run();
}
};
for (Task task : tasksToLoad) {
// Load icon and title.
int index = mTaskList.indexOf(task);
if (index >= 0) {
// If we've already loaded the task and have its content then just copy it over.
Task loadedTask = mTaskList.get(index);
task.titleDescription = loadedTask.titleDescription;
task.icon = loadedTask.icon;
itemLoadedRunnable.run();
} else {
// Otherwise, load the content in the background.
loadRequestsCount.getAndIncrement();
mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> {
if (loadRequestsCount.decrementAndGet() == 0 && onLoadedCallback != null) {
onLoadedCallback.run();
}
});
mRecentsModel.getIconCache().updateIconInBackground(task,
loadedTask -> itemLoadedRunnable.run());
}
}
if (loadRequestsCount.get() == 0 && onLoadedCallback != null) {
onLoadedCallback.run();
// Load the thumbnail. May return immediately and synchronously if the thumbnail is
// cached.
mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task,
thumbnail -> itemLoadedRunnable.run());
}
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
/**
* Callback for swipe input on {@link TaskHolder} views in the recents view.
*/
public final class TaskSwipeCallback extends ItemTouchHelper.SimpleCallback {
private final TaskActionController mTaskActionController;
public TaskSwipeCallback(TaskActionController taskActionController) {
super(0 /* dragDirs */, RIGHT);
mTaskActionController = taskActionController;
}
@Override
public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder,
ViewHolder target) {
return false;
}
@Override
public void onSwiped(ViewHolder viewHolder, int direction) {
if (direction == RIGHT) {
mTaskActionController.removeTask((TaskHolder) viewHolder);
}
}
}
@@ -17,18 +17,29 @@ package com.android.quickstep.views;
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.View;
import android.view.ViewDebug;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
import com.android.launcher3.R;
import com.android.quickstep.RecentsToActivityHelper;
import com.android.quickstep.TaskActionController;
import com.android.quickstep.TaskAdapter;
import com.android.quickstep.TaskHolder;
import com.android.quickstep.TaskListLoader;
import com.android.quickstep.TaskSwipeCallback;
/**
* Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code
@@ -36,20 +47,6 @@ import com.android.quickstep.TaskListLoader;
*/
public final class IconRecentsView extends FrameLayout {
public static final FloatProperty<IconRecentsView> TRANSLATION_Y_FACTOR =
new FloatProperty<IconRecentsView>("translationYFactor") {
@Override
public void setValue(IconRecentsView view, float v) {
view.setTranslationYFactor(v);
}
@Override
public Float get(IconRecentsView view) {
return view.mTranslationYFactor;
}
};
public static final FloatProperty<IconRecentsView> CONTENT_ALPHA =
new FloatProperty<IconRecentsView>("contentAlpha") {
@Override
@@ -67,6 +64,7 @@ public final class IconRecentsView extends FrameLayout {
return ALPHA.get(view);
}
};
private static final long CROSSFADE_DURATION = 300;
/**
* A ratio representing the view's relative placement within its padded space. For example, 0
@@ -75,26 +73,63 @@ public final class IconRecentsView extends FrameLayout {
@ViewDebug.ExportedProperty(category = "launcher")
private final Context mContext;
private final TaskListLoader mTaskLoader;
private final TaskAdapter mTaskAdapter;
private final TaskActionController mTaskActionController;
private float mTranslationYFactor;
private TaskAdapter mTaskAdapter;
private RecentsToActivityHelper mActivityHelper;
private RecyclerView mTaskRecyclerView;
private TaskListLoader mTaskLoader;
private View mEmptyView;
private View mContentView;
private boolean mTransitionedFromApp;
public IconRecentsView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mTaskLoader = new TaskListLoader(mContext);
mTaskAdapter = new TaskAdapter(mTaskLoader);
mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter);
mTaskAdapter.setActionController(mTaskActionController);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTaskLoader = new TaskListLoader(mContext);
mTaskAdapter = new TaskAdapter(mTaskLoader);
mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
mTaskRecyclerView.setAdapter(mTaskAdapter);
mTaskRecyclerView.setLayoutManager(
new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
if (mTaskRecyclerView == null) {
mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
mTaskRecyclerView.setAdapter(mTaskAdapter);
mTaskRecyclerView.setLayoutManager(
new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
ItemTouchHelper helper = new ItemTouchHelper(
new TaskSwipeCallback(mTaskActionController));
helper.attachToRecyclerView(mTaskRecyclerView);
mEmptyView = findViewById(R.id.recent_task_empty_view);
mContentView = findViewById(R.id.recent_task_content_view);
mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() {
@Override
public void onChanged() {
updateContentViewVisibility();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
updateContentViewVisibility();
}
});
View clearAllView = findViewById(R.id.clear_all_button);
clearAllView.setOnClickListener(v -> mTaskActionController.clearAllTasks());
}
}
/**
* Set activity helper for the view to callback to.
*
* @param helper the activity helper
*/
public void setRecentsToActivityHelper(@NonNull RecentsToActivityHelper helper) {
mActivityHelper = helper;
}
/**
@@ -113,12 +148,89 @@ public final class IconRecentsView extends FrameLayout {
});
}
public void setTranslationYFactor(float translationFactor) {
mTranslationYFactor = translationFactor;
setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
/**
* Set whether we transitioned to recents from the most recent app.
*
* @param transitionedFromApp true if transitioned from the most recent app, false otherwise
*/
public void setTransitionedFromApp(boolean transitionedFromApp) {
mTransitionedFromApp = transitionedFromApp;
}
private float computeTranslationYForFactor(float translationYFactor) {
return translationYFactor * (getPaddingBottom() - getPaddingTop());
/**
* Handles input from the overview button. Launch the most recent task unless we just came from
* the app. In that case, we launch the next most recent.
*/
public void handleOverviewCommand() {
int childCount = mTaskRecyclerView.getChildCount();
if (childCount == 0) {
// Do nothing
return;
}
TaskHolder taskToLaunch;
if (mTransitionedFromApp && childCount > 1) {
// Launch the next most recent app
TaskItemView itemView = (TaskItemView) mTaskRecyclerView.getChildAt(1);
taskToLaunch = (TaskHolder) mTaskRecyclerView.getChildViewHolder(itemView);
} else {
// Launch the most recent app
TaskItemView itemView = (TaskItemView) mTaskRecyclerView.getChildAt(0);
taskToLaunch = (TaskHolder) mTaskRecyclerView.getChildViewHolder(itemView);
}
mTaskActionController.launchTask(taskToLaunch);
}
/**
* Get the thumbnail view associated with a task for the purposes of animation.
*
* @param taskId task id of thumbnail view to get
* @return the thumbnail view for the task if attached, null otherwise
*/
public @Nullable View getThumbnailViewForTask(int taskId) {
TaskItemView view = mTaskAdapter.getTaskItemView(taskId);
if (view == null) {
return null;
}
return view.getThumbnailView();
}
/**
* Update the content view so that the appropriate view is shown based off the current list
* of tasks.
*/
private void updateContentViewVisibility() {
int taskListSize = mTaskLoader.getCurrentTaskList().size();
if (mEmptyView.getVisibility() != VISIBLE && taskListSize == 0) {
crossfadeViews(mEmptyView, mContentView);
mActivityHelper.leaveRecents();
}
if (mContentView.getVisibility() != VISIBLE && taskListSize > 0) {
crossfadeViews(mContentView, mEmptyView);
}
}
/**
* Animate views so that one view fades in while the other fades out.
*
* @param fadeInView view that should fade in
* @param fadeOutView view that should fade out
*/
private void crossfadeViews(View fadeInView, View fadeOutView) {
fadeInView.setVisibility(VISIBLE);
fadeInView.setAlpha(0f);
fadeInView.animate()
.alpha(1f)
.setDuration(CROSSFADE_DURATION)
.setListener(null);
fadeOutView.animate()
.alpha(0f)
.setDuration(CROSSFADE_DURATION)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
fadeOutView.setVisibility(GONE);
}
});
}
}
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.views;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.launcher3.R;
/**
* View representing an individual task item with the icon + thumbnail adjacent to the task label.
*/
public final class TaskItemView extends LinearLayout {
private TextView mLabelView;
private ImageView mIconView;
private ImageView mThumbnailView;
public TaskItemView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mLabelView = findViewById(R.id.task_label);
mThumbnailView = findViewById(R.id.task_thumbnail);
mIconView = findViewById(R.id.task_icon);
}
/**
* Set the label for the task item.
*
* @param label task label
*/
public void setLabel(String label) {
mLabelView.setText(label);
}
/**
* Set the icon for the task item.
*
* @param icon task icon
*/
public void setIcon(Drawable icon) {
// TODO: Scale the icon up based off the padding on the side
// The icon proper is actually smaller than the drawable and has "padding" on the side for
// the purpose of drawing the shadow, allowing the icon to pop up, so we need to scale the
// view if we want the icon to be flush with the bottom of the thumbnail.
mIconView.setImageDrawable(icon);
}
/**
* Set the task thumbnail for the task.
*
* @param thumbnail task thumbnail for the task
*/
public void setThumbnail(Bitmap thumbnail) {
mThumbnailView.setImageBitmap(thumbnail);
}
public View getThumbnailView() {
return mThumbnailView;
}
}
-8
View File
@@ -1,8 +0,0 @@
These jar are compiled in the frameworks/base of the platform tree.
launcher_protos.jar is defined as launcherprotosnano in the following file:
frameworks/base/core/protos/android/stats/launcher/Android.bp
plugin_core.jar is defined as PluginCoreLib in the following file:
frameworks/base/packages/SystemUI/plugin/Android.bp
Binary file not shown.
+1
View File
@@ -122,6 +122,7 @@ enum TipType {
SWIPE_UP_TEXT = 2;
QUICK_SCRUB_TEXT = 3;
PREDICTION_TEXT = 4;
DWB_TOAST = 5;
}
// Used to define the action component of the LauncherEvent.
+8
View File
@@ -73,6 +73,14 @@
</intent-filter>
</provider>
<provider
android:name="com.android.quickstep.TestInformationProvider"
android:authorities="${packageName}.TestInfo"
android:readPermission="android.permission.WRITE_SECURE_SETTINGS"
android:writePermission="android.permission.WRITE_SECURE_SETTINGS"
android:exported="true">
</provider>
<service
android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
tools:node="remove" />
Binary file not shown.
@@ -20,6 +20,7 @@ import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import android.os.RemoteException;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -51,7 +52,7 @@ public class BackgroundAppState extends OverviewState {
}
@Override
public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
// Initialize the recents view scale to what it would be when starting swipe up
RecentsView recentsView = launcher.getOverviewPanel();
recentsView.getTaskSize(sTempRect);
@@ -72,4 +73,13 @@ public class BackgroundAppState extends OverviewState {
float scale = (float) appWidth / sTempRect.width();
return new float[] { scale, 0f };
}
@Override
public int getVisibleElements(Launcher launcher) {
if (FeatureFlags.SWIPE_HOME.get()) {
return super.getVisibleElements(launcher);
}
// Hide shelf content (e.g. QSB) because we fade it in when swiping up.
return ALL_APPS_HEADER_EXTRA;
}
}
@@ -18,6 +18,7 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
import android.graphics.Rect;
@@ -25,13 +26,14 @@ import android.view.View;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -67,7 +69,7 @@ public class OverviewState extends LauncherState {
}
@Override
public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
return new float[] {1f, 0f};
}
@@ -131,7 +133,10 @@ public class OverviewState extends LauncherState {
}
public static float getDefaultSwipeHeight(Launcher launcher) {
DeviceProfile dp = launcher.getDeviceProfile();
return getDefaultSwipeHeight(launcher.getDeviceProfile());
}
public static float getDefaultSwipeHeight(DeviceProfile dp) {
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
}
@@ -139,6 +144,8 @@ public class OverviewState extends LauncherState {
public void onBackPressed(Launcher launcher) {
TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
if (taskView != null) {
launcher.getUserEventDispatcher().logActionCommand(Action.Command.BACK,
newContainerTarget(ContainerType.OVERVIEW));
taskView.launchTask(true);
} else {
super.onBackPressed(launcher);
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.uioverrides;
import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import android.animation.ValueAnimator;
@@ -23,8 +22,6 @@ import android.annotation.TargetApi;
import android.os.Build;
import android.util.FloatProperty;
import androidx.annotation.NonNull;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -32,6 +29,8 @@ import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
import androidx.annotation.NonNull;
/**
* State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing
* the basic view properties, this class also manages changes in the task visuals.
@@ -79,11 +78,6 @@ public final class RecentsViewStateController extends
}
}
@Override
FloatProperty<LauncherRecentsView> getTranslationYFactorProperty() {
return TRANSLATION_Y_FACTOR;
}
@Override
FloatProperty<RecentsView> getContentAlphaProperty() {
return CONTENT_ALPHA;
@@ -22,59 +22,70 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.NavigationBarCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.launcher3.R;
import com.android.systemui.shared.system.NavigationBarCompat;
/**
* Touch consumer for handling events to launch assistant from launcher
*/
public class AssistantTouchConsumer implements InputConsumer {
private static final String TAG = "AssistantTouchConsumer";
private static final long RETRACT_ANIMATION_DURATION_MS = 300;
/* The assistant touch consume competes with quick switch InputConsumer gesture. The delegate
* can be chosen to run if the angle passing the slop is lower than the threshold angle. When
* this occurs, the state changes to {@link #STATE_DELEGATE_ACTIVE} where the next incoming
* motion events are handled by the delegate instead of the assistant touch consumer. If the
* angle is higher than the threshold, the state will change to {@link #STATE_ASSISTANT_ACTIVE}.
*/
private static final int STATE_INACTIVE = 0;
private static final int STATE_ASSISTANT_ACTIVE = 1;
private static final int STATE_DELEGATE_ACTIVE = 2;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private final PointF mStartDragPos = new PointF();
private int mActivePointerId = -1;
private final int mDisplayRotation;
private final Rect mStableInsets = new Rect();
private final float mDragSlop;
private final float mTouchSlop;
private final float mThreshold;
private float mStartDisplacement;
private boolean mPassedDragSlop;
private boolean mPassedTouchSlop;
private long mPassedTouchSlopTime;
private boolean mPassedSlop;
private boolean mLaunchedAssistant;
private float mDistance;
private float mTimeFraction;
private long mDragTime;
private float mLastProgress;
private int mState;
private final float mDistThreshold;
private final long mTimeThreshold;
private final int mAngleThreshold;
private final float mSlop;
private final MotionPauseDetector mMotionPauseDetector;
private final ISystemUiProxy mSysUiProxy;
private final InputConsumer mConsumerDelegate;
public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy) {
public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
InputConsumer delegate) {
final Resources res = context.getResources();
mSysUiProxy = systemUiProxy;
mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
mThreshold = context.getResources().getDimension(R.dimen.gestures_assistant_threshold);
Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
mDisplayRotation = display.getRotation();
WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
mConsumerDelegate = delegate;
mMotionPauseDetector = new MotionPauseDetector(context);
mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
mSlop = NavigationBarCompat.getQuickScrubTouchSlopPx();
mState = STATE_INACTIVE;
}
@Override
@@ -82,15 +93,29 @@ public class AssistantTouchConsumer implements InputConsumer {
return TYPE_ASSISTANT;
}
@Override
public boolean isActive() {
return mState != STATE_INACTIVE;
}
@Override
public void onMotionEvent(MotionEvent ev) {
// TODO add logging
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
mLastProgress = -1;
mTimeFraction = 0;
// Detect when the gesture decelerates to start the assistant
mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
if (isPaused && mState == STATE_ASSISTANT_ACTIVE) {
mTimeFraction = 1;
updateAssistantProgress();
}
});
break;
}
case ACTION_POINTER_UP: {
@@ -107,94 +132,101 @@ public class AssistantTouchConsumer implements InputConsumer {
break;
}
case ACTION_MOVE: {
if (mState == STATE_DELEGATE_ACTIVE) {
break;
}
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
break;
}
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
float displacement = getDisplacement(ev);
if (!mPassedDragSlop) {
// Normal gesture, ensure we pass the drag slop before we start tracking
// the gesture
if (Math.abs(displacement) > mDragSlop) {
mPassedDragSlop = true;
mStartDisplacement = displacement;
mPassedTouchSlopTime = SystemClock.uptimeMillis();
}
}
if (!mPassedSlop) {
// Normal gesture, ensure we pass the slop before we start tracking the gesture
if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) > mSlop) {
mPassedSlop = true;
mStartDragPos.set(mLastPos.x, mLastPos.y);
mDragTime = SystemClock.uptimeMillis();
if (!mPassedTouchSlop) {
if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) >=
mTouchSlop) {
mPassedTouchSlop = true;
if (!mPassedDragSlop) {
mPassedDragSlop = true;
mStartDisplacement = displacement;
mPassedTouchSlopTime = SystemClock.uptimeMillis();
// Determine if angle is larger than threshold for assistant detection
float angle = (float) Math.toDegrees(
Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x));
angle = angle > 90 ? 180 - angle : angle;
if (angle > mAngleThreshold) {
mState = STATE_ASSISTANT_ACTIVE;
if (mConsumerDelegate != null) {
// Send cancel event
MotionEvent event = MotionEvent.obtain(ev);
event.setAction(MotionEvent.ACTION_CANCEL);
mConsumerDelegate.onMotionEvent(event);
event.recycle();
}
} else {
mState = STATE_DELEGATE_ACTIVE;
}
}
}
if (mPassedDragSlop) {
// Move
float distance = mStartDisplacement - displacement;
if (distance >= 0) {
onAssistantProgress(distance / mThreshold);
} else {
// Movement
mDistance = (float) Math.hypot(mLastPos.x - mStartDragPos.x,
mLastPos.y - mStartDragPos.y);
mMotionPauseDetector.addPosition(mDistance, 0);
if (mDistance >= 0) {
final long diff = SystemClock.uptimeMillis() - mDragTime;
mTimeFraction = Math.min(diff * 1f / mTimeThreshold, 1);
updateAssistantProgress();
}
}
break;
}
case ACTION_CANCEL:
break;
case ACTION_UP: {
if (ev.getEventTime() - mPassedTouchSlopTime < ViewConfiguration.getTapTimeout()) {
onAssistantProgress(1);
case ACTION_UP:
if (mState != STATE_DELEGATE_ACTIVE && !mLaunchedAssistant) {
ValueAnimator animator = ValueAnimator.ofFloat(mLastProgress, 0)
.setDuration(RETRACT_ANIMATION_DURATION_MS);
animator.addUpdateListener(valueAnimator -> {
float progress = (float) valueAnimator.getAnimatedValue();
try {
mSysUiProxy.onAssistantProgress(progress);
} catch (RemoteException e) {
Log.w(TAG, "Failed to send SysUI start/send assistant progress: "
+ progress, e);
}
});
animator.setInterpolator(Interpolators.DEACCEL_2);
animator.start();
}
mMotionPauseDetector.clear();
break;
}
}
if (mState != STATE_ASSISTANT_ACTIVE && mConsumerDelegate != null) {
mConsumerDelegate.onMotionEvent(ev);
}
}
private void onAssistantProgress(float progress) {
if (mLastProgress == progress) {
return;
}
try {
mSysUiProxy.onAssistantProgress(Math.max(0, Math.min(1, progress)));
if (progress >= 1 && !mLaunchedAssistant) {
mSysUiProxy.startAssistant(new Bundle());
mLaunchedAssistant = true;
}
private void updateAssistantProgress() {
if (!mLaunchedAssistant) {
float progress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction;
mLastProgress = progress;
} catch (RemoteException e) {
Log.w(TAG, "Failed to notify SysUI to start/send assistant progress: " + progress, e);
try {
mSysUiProxy.onAssistantProgress(progress);
if (mDistance >= mDistThreshold && mTimeFraction >= 1) {
mSysUiProxy.startAssistant(new Bundle());
mLaunchedAssistant = true;
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + progress, e);
}
}
}
private boolean isNavBarOnRight() {
return mDisplayRotation == Surface.ROTATION_90 && mStableInsets.right > 0;
}
private boolean isNavBarOnLeft() {
return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
}
private float getDisplacement(MotionEvent ev) {
float eventX = ev.getX();
float eventY = ev.getY();
float displacement = eventY - mDownPos.y;
if (isNavBarOnRight()) {
displacement = eventX - mDownPos.x;
} else if (isNavBarOnLeft()) {
displacement = mDownPos.x - eventX;
}
return displacement;
}
static boolean withinTouchRegion(Context context, float x) {
return x > context.getResources().getDisplayMetrics().widthPixels
- context.getResources().getDimension(R.dimen.gestures_assistant_width);
static boolean withinTouchRegion(Context context, MotionEvent ev) {
final Resources res = context.getResources();
final int width = res.getDisplayMetrics().widthPixels;
final int height = res.getDisplayMetrics().heightPixels;
final int size = res.getDimensionPixelSize(R.dimen.gestures_assistant_size);
return (ev.getX() > width - size || ev.getX() < size) && ev.getY() > height - size;
}
}
@@ -21,7 +21,6 @@ import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -90,7 +89,7 @@ public final class FallbackActivityControllerHelper implements
@NonNull
@Override
public Animator createActivityAnimationToHome() {
public AnimatorPlaybackController createActivityAnimationToHome() {
Animator anim = ObjectAnimator.ofFloat(recentsView, CONTENT_ALPHA, 0);
anim.addListener(new AnimationSuccessListener() {
@Override
@@ -98,7 +97,10 @@ public final class FallbackActivityControllerHelper implements
recentsView.startHome();
}
});
return anim;
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(anim);
long accuracy = 2 * Math.max(recentsView.getWidth(), recentsView.getHeight());
return AnimatorPlaybackController.wrap(animatorSet, accuracy);
}
};
}
@@ -181,11 +183,6 @@ public final class FallbackActivityControllerHelper implements
return false;
}
@Override
public AlphaProperty getAlphaProperty(RecentsActivity activity) {
return activity.getDragLayer().getAlphaProperty(0);
}
@Override
public int getContainerType() {
return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
@@ -23,6 +23,7 @@ import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.Animator;
@@ -38,19 +39,24 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.LayoutUtils;
@@ -61,10 +67,6 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
/**
* {@link ActivityControlHelper} for the in-launcher recents.
*/
@@ -140,10 +142,9 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
@NonNull
@Override
public Animator createActivityAnimationToHome() {
public AnimatorPlaybackController createActivityAnimationToHome() {
long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
return activity.getStateManager().createAnimationToNewWorkspace(
NORMAL, accuracy).getTarget();
return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy);
}
};
}
@@ -212,7 +213,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
: mShelfState == ShelfAnimState.PEEK
? shelfPeekingProgress
: shelfOverviewProgress;
mShelfAnim = createShelfAnim(activity, toProgress);
mShelfAnim = createShelfProgressAnim(activity, toProgress);
mShelfAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -230,10 +231,10 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
LauncherState fromState, long transitionLength,
Consumer<AnimatorPlaybackController> callback) {
LauncherState endState = OVERVIEW;
DeviceProfile dp = activity.getDeviceProfile();
long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
if (wasVisible && fromState != BACKGROUND_APP) {
// If a translucent app was launched fom launcher, animate launcher states.
DeviceProfile dp = activity.getDeviceProfile();
long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
callback.accept(activity.getStateManager()
.createAnimationToNewWorkspace(fromState, endState, accuracy));
return;
@@ -246,10 +247,11 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
if (!activity.getDeviceProfile().isVerticalBarLayout()
&& !FeatureFlags.SWIPE_HOME.get()) {
// Don't animate the shelf when SWIPE_HOME is true, because we update it atomically.
Animator shiftAnim = createShelfAnim(activity,
Animator shiftAnim = createShelfProgressAnim(activity,
fromState.getVerticalProgress(activity),
endState.getVerticalProgress(activity));
anim.play(shiftAnim);
anim.play(createShelfAlphaAnim(activity, endState, accuracy));
}
playScaleDownAnim(anim, activity, endState);
@@ -266,7 +268,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
callback.accept(controller);
}
private Animator createShelfAnim(Launcher activity, float ... progressValues) {
private Animator createShelfProgressAnim(Launcher activity, float ... progressValues) {
Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
"allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(),
SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
@@ -274,6 +276,19 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
return shiftAnim;
}
/**
* Very quickly fade the alpha of shelf content.
*/
private Animator createShelfAlphaAnim(Launcher activity, LauncherState toState, long accuracy) {
AllAppsTransitionController allAppsController = activity.getAllAppsController();
AnimatorSetBuilder animBuilder = new AnimatorSetBuilder();
animBuilder.setInterpolator(AnimatorSetBuilder.ANIM_ALL_APPS_FADE, DEACCEL_3);
LauncherStateManager.AnimationConfig config = new LauncherStateManager.AnimationConfig();
config.duration = accuracy;
allAppsController.setAlphas(toState.getVisibleElements(activity), config, animBuilder);
return animBuilder.build();
}
/**
* Scale down recents from the center task being full screen to being in overview.
*/
@@ -290,7 +305,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
// starting to line up the side pages during swipe up)
float prevRvScale = recentsView.getScaleX();
float prevRvTransY = recentsView.getTranslationY();
float targetRvScale = endState.getOverviewScaleAndTranslationYFactor(launcher)[0];
float targetRvScale = endState.getOverviewScaleAndTranslationY(launcher)[0];
SCALE_PROPERTY.set(recentsView, targetRvScale);
recentsView.setTranslationY(0);
ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher);
@@ -375,11 +390,6 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
return true;
}
@Override
public AlphaProperty getAlphaProperty(Launcher activity) {
return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
}
@Override
public int getContainerType() {
final Launcher launcher = getVisibleLauncher();
@@ -18,6 +18,8 @@ package com.android.quickstep;
import android.util.Log;
import android.util.SparseArray;
import com.android.launcher3.config.FeatureFlags;
import java.util.StringJoiner;
import java.util.function.Consumer;
@@ -101,6 +103,9 @@ public class MultiStateCallback {
* The callback is only run once.
*/
public void addCallback(int stateMask, Runnable callback) {
if (FeatureFlags.IS_DOGFOOD_BUILD && mCallbacks.get(stateMask) != null) {
throw new IllegalStateException("Multiple callbacks on same state");
}
mCallbacks.put(stateMask, callback);
}
@@ -21,10 +21,9 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -45,8 +44,6 @@ import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import androidx.annotation.UiThread;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RaceConditionTracker;
@@ -63,6 +60,8 @@ import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.function.Consumer;
import androidx.annotation.UiThread;
/**
* Input consumer for handling events originating from an activity other than Launcher
*/
@@ -131,7 +130,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
mVelocityTracker = VelocityTracker.obtain();
mActivityControlHelper = activityControl;
mIsDeferredDownTarget = isDeferredDownTarget;
boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
mOverviewCallbacks = overviewCallbacks;
mTaskOverlayFactory = taskOverlayFactory;
mInputConsumer = inputConsumer;
@@ -143,8 +143,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
// If active listener isn't null, we are continuing the previous gesture.
mPassedTouchSlop = mPassedDragSlop = mSwipeSharedState.getActiveListener() != null;
mPassedTouchSlop = mPassedDragSlop = continuingPreviousGesture;
}
@Override
@@ -230,7 +229,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
mTouchSlop) {
mPassedTouchSlop = true;
TOUCH_INTERACTION_LOG.addLog("startQuickstep");
if (mIsDeferredDownTarget) {
// Deferred gesture, start the animation and gesture tracking once
// we pass the actual touch slop
@@ -272,6 +270,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
}
private void notifyGestureStarted() {
TOUCH_INTERACTION_LOG.addLog("startQuickstep");
if (mInteractionHandler == null) {
return;
}
@@ -310,6 +309,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
if (listenerSet != null) {
listenerSet.addListener(handler);
mSwipeSharedState.applyActiveRecentsAnimationState(handler);
notifyGestureStarted();
} else {
RecentsAnimationListenerSet newListenerSet =
mSwipeSharedState.newRecentsAnimationListenerSet();
@@ -330,12 +330,13 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
mVelocityTracker.computeCurrentVelocity(1000,
ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
float velocity = isNavBarOnRight() ? velocityX
: isNavBarOnLeft() ? -velocityX
: mVelocityTracker.getYVelocity(mActivePointerId);
: velocityY;
mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
mInteractionHandler.onGestureEnded(velocity, velocityX);
mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY));
} else {
// Since we start touch tracking on DOWN, we may reach this state without actually
// starting the gesture. In that case, just cleanup immediately.
@@ -30,6 +30,7 @@ import android.view.MotionEvent;
import android.view.ViewConfiguration;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Utilities;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.util.CachedEventDispatcher;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -139,7 +140,7 @@ public class OverviewInputConsumer<T extends BaseDraggingActivity>
return;
}
int flags = ev.getEdgeFlags();
ev.setEdgeFlags(flags | TouchInteractionService.EDGE_NAV_BAR);
ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
if (ev.getAction() == ACTION_DOWN) {
mTarget.onInterceptTouchEvent(ev);
@@ -173,6 +173,12 @@ public class RecentsAnimationWrapper {
return true;
}
public void setCancelWithDeferredScreenshot(boolean deferredWithScreenshot) {
if (targetSet != null) {
targetSet.controller.setCancelWithDeferredScreenshot(deferredWithScreenshot);
}
}
public SwipeAnimationTargetSet getController() {
return targetSet;
}
@@ -74,8 +74,6 @@ public class TouchInteractionService extends Service {
public static final EventLogArray TOUCH_INTERACTION_LOG =
new EventLogArray("touch_interaction_log", 40);
public static final int EDGE_NAV_BAR = 1 << 8;
private static final String TAG = "TouchInteractionService";
private final IBinder mMyBinder = new IOverviewProxy.Stub() {
@@ -313,31 +311,36 @@ public class TouchInteractionService extends Service {
mSwipeSharedState.clearAllState();
}
final ActivityControlHelper activityControl =
mOverviewComponentObserver.getActivityControlHelper();
if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
return InputConsumer.NO_OP;
} else if (mAssistantAvailable && mOverviewInteractionState.isSwipeUpGestureEnabled()
&& FeatureFlags.ENABLE_ASSISTANT_GESTURE.get()
&& AssistantTouchConsumer.withinTouchRegion(this, event.getX())) {
return new AssistantTouchConsumer(this, mRecentsModel.getSystemUiProxy());
} else if (mSwipeSharedState.goingToLauncher ||
mOverviewComponentObserver.getActivityControlHelper().isResumed()) {
return OverviewInputConsumer.newInstance(
mOverviewComponentObserver.getActivityControlHelper(), false);
&& AssistantTouchConsumer.withinTouchRegion(this, event)) {
return new AssistantTouchConsumer(this, mISystemUiProxy, !activityControl.isResumed()
? createOtherActivityInputConsumer(event, runningTaskInfo) : null);
} else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
return OverviewInputConsumer.newInstance(activityControl, false);
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
mOverviewComponentObserver.getActivityControlHelper().isInLiveTileMode()) {
return OverviewInputConsumer.newInstance(
mOverviewComponentObserver.getActivityControlHelper(), false);
activityControl.isInLiveTileMode()) {
return OverviewInputConsumer.newInstance(activityControl, false);
} else {
ActivityControlHelper activityControl =
mOverviewComponentObserver.getActivityControlHelper();
boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
mOverviewComponentObserver.getOverviewIntent(), activityControl,
shouldDefer, mOverviewCallbacks, mTaskOverlayFactory, mInputConsumer,
this::onConsumerInactive, mSwipeSharedState);
return createOtherActivityInputConsumer(event, runningTaskInfo);
}
}
private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event,
RunningTaskInfo runningTaskInfo) {
final ActivityControlHelper activityControl =
mOverviewComponentObserver.getActivityControlHelper();
boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
mOverviewComponentObserver.getOverviewIntent(), activityControl,
shouldDefer, mOverviewCallbacks, mTaskOverlayFactory, mInputConsumer,
this::onConsumerInactive, mSwipeSharedState);
}
/**
* To be called by the consumer when it's no longer active.
*/
@@ -40,16 +40,15 @@ import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
@@ -78,8 +77,6 @@ import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.RaceConditionTracker;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.views.FloatingIconView;
@@ -88,6 +85,7 @@ import com.android.quickstep.ActivityControlHelper.AnimationFactory;
import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
@@ -95,7 +93,6 @@ import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.RectFEvaluator;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -113,7 +110,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
implements SwipeAnimationListener, OnApplyWindowInsetsListener {
private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[19] : null;
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
private static int getFlagForIndex(int index, String name) {
if (DEBUG_STATES) {
@@ -126,49 +123,42 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
private static final int STATE_LAUNCHER_PRESENT = getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
private static final int STATE_LAUNCHER_STARTED = getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
private static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE =
getFlagForIndex(3, "STATE_ACTIVITY_MULTIPLIER_COMPLETE");
// Internal initialization states
private static final int STATE_APP_CONTROLLER_RECEIVED =
getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED");
// Interaction finish states
private static final int STATE_SCALED_CONTROLLER_HOME =
getFlagForIndex(5, "STATE_SCALED_CONTROLLER_HOME");
getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
private static final int STATE_SCALED_CONTROLLER_RECENTS =
getFlagForIndex(6, "STATE_SCALED_CONTROLLER_RECENTS");
private static final int STATE_SCALED_CONTROLLER_LAST_TASK =
getFlagForIndex(7, "STATE_SCALED_CONTROLLER_LAST_TASK");
getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
private static final int STATE_HANDLER_INVALIDATED =
getFlagForIndex(8, "STATE_HANDLER_INVALIDATED");
getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
private static final int STATE_GESTURE_STARTED =
getFlagForIndex(9, "STATE_GESTURE_STARTED");
getFlagForIndex(7, "STATE_GESTURE_STARTED");
private static final int STATE_GESTURE_CANCELLED =
getFlagForIndex(10, "STATE_GESTURE_CANCELLED");
getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
private static final int STATE_GESTURE_COMPLETED =
getFlagForIndex(11, "STATE_GESTURE_COMPLETED");
getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
private static final int STATE_CAPTURE_SCREENSHOT =
getFlagForIndex(12, "STATE_CAPTURE_SCREENSHOT");
getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
private static final int STATE_SCREENSHOT_CAPTURED =
getFlagForIndex(13, "STATE_SCREENSHOT_CAPTURED");
getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
private static final int STATE_SCREENSHOT_VIEW_SHOWN =
getFlagForIndex(14, "STATE_SCREENSHOT_VIEW_SHOWN");
getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
private static final int STATE_RESUME_LAST_TASK =
getFlagForIndex(15, "STATE_RESUME_LAST_TASK");
getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
private static final int STATE_START_NEW_TASK =
getFlagForIndex(16, "STATE_START_NEW_TASK");
getFlagForIndex(14, "STATE_START_NEW_TASK");
private static final int STATE_CURRENT_TASK_FINISHED =
getFlagForIndex(17, "STATE_CURRENT_TASK_FINISHED");
getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
private static final int LAUNCHER_UI_STATES =
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE
| STATE_LAUNCHER_STARTED;
// For debugging, keep in sync with above states
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
enum GestureEndTarget {
HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE),
@@ -178,7 +168,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP),
LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, false, ContainerType.APP);
LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP);
GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
int containerType) {
@@ -226,7 +216,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
// 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
// visible.
private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
private boolean mDispatchedDownEvent;
private boolean mContinuingLastGesture;
// To avoid UI jump when gesture is started, we offset the animation by the threshold.
private float mShiftAtGestureStart = 0;
@@ -238,10 +227,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
private final ActivityInitListener mActivityInitListener;
private final int mRunningTaskId;
private final RunningTaskInfo mRunningTaskInfo;
private ThumbnailData mTaskSnapshot;
private MultiStateCallback mStateCallback;
// Used to control launcher components throughout the swipe gesture.
private AnimatorPlaybackController mLauncherTransitionController;
private T mActivity;
@@ -265,7 +254,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
InputConsumerController inputConsumer) {
mContext = context;
mRunningTaskInfo = runningTaskInfo;
mRunningTaskId = runningTaskInfo.id;
mTouchTimeMs = touchTimeMs;
mActivityControlHelper = controller;
@@ -283,10 +271,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
private void initStateCallbacks() {
mStateCallback = new MultiStateCallback(STATE_NAMES);
// Re-setup the recents UI when gesture starts, as the state could have been changed during
// that time by a previous window transition.
mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_GESTURE_STARTED,
this::setupRecentsViewUi);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
this::onLauncherPresentAndGestureStarted);
mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
this::initializeLauncherAnimationController);
@@ -294,9 +280,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
this::launcherFrameDrawn);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
this::notifyGestureStartedAsync);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
| STATE_GESTURE_CANCELLED,
this::resetStateForAnimationCancel);
@@ -304,16 +287,13 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
this::sendRemoteAnimationsToAnimationFactory);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_LAST_TASK,
this::resumeLastTaskForQuickstep);
mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::resumeLastTask);
mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::startNewTask);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_ACTIVITY_MULTIPLIER_COMPLETE
| STATE_CAPTURE_SCREENSHOT,
| STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
this::switchToScreenshot);
mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
@@ -322,13 +302,13 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_COMPLETED
| STATE_SCALED_CONTROLLER_HOME | STATE_APP_CONTROLLER_RECEIVED
| STATE_ACTIVITY_MULTIPLIER_COMPLETE,
| STATE_LAUNCHER_DRAWN,
this::finishCurrentTransitionToHome);
mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
this::reset);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_SCALED_CONTROLLER_RECENTS
| STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
| STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
| STATE_GESTURE_STARTED,
this::setupLauncherUiAfterSwipeUpAnimation);
@@ -336,8 +316,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED
| STATE_SCALED_CONTROLLER_LAST_TASK,
mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
this::notifyTransitionCancelled);
mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
@@ -415,6 +394,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
});
mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
mStateCallback.setState(STATE_LAUNCHER_PRESENT);
@@ -423,6 +403,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
} else {
activity.setOnStartCallback(this::onLauncherStart);
}
setupRecentsViewUi();
return true;
}
@@ -434,16 +416,19 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
return;
}
mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated);
// If we've already ended the gesture and are going home, don't prepare recents UI,
// as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
if (mGestureEndTarget != HOME) {
mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated);
}
AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
if (mWasLauncherAlreadyVisible) {
mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_DRAWN);
mStateCallback.setState(STATE_LAUNCHER_DRAWN);
} else {
TraceHelper.beginSection("WTS-init");
View dragLayer = activity.getDragLayer();
mActivityControlHelper.getAlphaProperty(activity).setValue(0);
dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
@Override
@@ -460,11 +445,18 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
});
}
setupRecentsViewUi();
activity.getRootView().setOnApplyWindowInsetsListener(this);
mStateCallback.setState(STATE_LAUNCHER_STARTED);
}
private void onLauncherPresentAndGestureStarted() {
// Re-setup the recents UI when gesture starts, as the state could have been changed during
// that time by a previous window transition.
setupRecentsViewUi();
notifyGestureStartedAsync();
}
private void setupRecentsViewUi() {
if (mContinuingLastGesture) {
return;
@@ -476,25 +468,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
private void launcherFrameDrawn() {
AlphaProperty property = mActivityControlHelper.getAlphaProperty(mActivity);
if (property.getValue() < 1) {
if (mGestureStarted) {
final MultiStateCallback callback = mStateCallback;
ObjectAnimator animator = ObjectAnimator.ofFloat(
property, MultiValueAlpha.VALUE, 1);
animator.setDuration(getFadeInDuration()).addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
callback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
}
});
animator.start();
} else {
property.setValue(1);
mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
}
}
mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
}
@@ -706,15 +679,19 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
}
/**
* @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
* @param velocity The x and y components of the velocity when the gesture ends.
*/
@UiThread
public void onGestureEnded(float endVelocity, float velocityX) {
public void onGestureEnded(float endVelocity, PointF velocity) {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
handleNormalGestureEnd(endVelocity, isFling, velocityX);
handleNormalGestureEnd(endVelocity, isFling, velocity);
}
@UiThread
@@ -732,9 +709,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
@UiThread
private void handleNormalGestureEnd(float endVelocity, boolean isFling, float velocityX) {
float velocityPxPerMs = endVelocity / 1000;
float velocityXPxPerMs = velocityX / 1000;
private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity) {
PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
long duration = MAX_SWIPE_DURATION;
float currentShift = mCurrentShift.value;
final GestureEndTarget endTarget;
@@ -749,7 +725,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
final int lastTaskIndex = mRecentsView.getTaskViewCount() - 1;
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
taskToLaunch = nextPage <= lastTaskIndex ? nextPage : lastTaskIndex;
goingToNewTask = mRecentsView != null && taskToLaunch != runningTaskIndex;
goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
} else {
goingToNewTask = false;
}
@@ -779,7 +755,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
} else {
if (SWIPE_HOME.get() && endVelocity < 0 && !mIsShelfPeeking) {
// If swiping at a diagonal, base end target on the faster velocity.
endTarget = goingToNewTask && Math.abs(velocityX) > Math.abs(endVelocity)
endTarget = goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity)
? NEW_TASK : HOME;
} else if (endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold)) {
// If user scrolled to a new task, only go to recents if they already passed
@@ -789,14 +765,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
}
endShift = endTarget.endShift;
startShift = Utilities.boundToRange(currentShift - velocityPxPerMs
startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
* SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
float minFlingVelocity = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_min_velocity);
if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
if (endTarget == RECENTS) {
Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength);
startShift, endShift, endShift, velocityPxPerMs.y,
mTransitionDragLength);
endShift = overshoot.end;
interpolator = overshoot.interpolator;
duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
@@ -807,7 +784,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
// derivative of the scroll interpolator at zero, ie. 2.
long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
}
}
@@ -822,6 +799,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
} else if (endTarget == RECENTS) {
mLiveTileOverlay.startIconAnimation();
mRecentsAnimationWrapper.enableInputProxy();
if (mRecentsView != null) {
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
@@ -831,7 +809,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
} else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
// Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
// or resumeLastTaskForQuickstep().
// or resumeLastTask().
if (mRecentsView != null) {
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
}
@@ -863,14 +841,14 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
@UiThread
private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
GestureEndTarget target, float velocityPxPerMs) {
GestureEndTarget target, PointF velocityPxPerMs) {
mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
interpolator, target, velocityPxPerMs));
}
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) {
Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
mGestureEndTarget = target;
if (mGestureEndTarget.canBeContinued) {
@@ -883,9 +861,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
RecentsModel.INSTANCE.get(mContext).endStabilizationSession();
}
HomeAnimationFactory homeAnimFactory;
Animator windowAnim;
if (mGestureEndTarget == HOME) {
HomeAnimationFactory homeAnimFactory;
if (mActivity != null) {
homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
} else {
@@ -900,27 +877,33 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
@NonNull
@Override
public Animator createActivityAnimationToHome() {
return new AnimatorSet();
public AnimatorPlaybackController createActivityAnimationToHome() {
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
}
};
mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
}
windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
windowAnim.addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
setStateOnUiThread(target.endState);
}
});
windowAnim.start(velocityPxPerMs);
mLauncherTransitionController = null;
} else {
windowAnim = mCurrentShift.animateToValue(start, end);
homeAnimFactory = null;
Animator windowAnim = mCurrentShift.animateToValue(start, end);
windowAnim.setDuration(duration).setInterpolator(interpolator);
windowAnim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
setStateOnUiThread(target.endState);
}
});
windowAnim.start();
}
windowAnim.setDuration(duration).setInterpolator(interpolator);
windowAnim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
setStateOnUiThread(target.endState);
}
});
windowAnim.start();
// Always play the entire launcher animation when going home, since it is separate from
// the animation that has been controlled thus far.
if (mGestureEndTarget == HOME) {
@@ -931,12 +914,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
// interpolate over the remaining progress (end - start).
TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
interpolator, start, end);
if (homeAnimFactory != null) {
Animator homeAnim = homeAnimFactory.createActivityAnimationToHome();
homeAnim.setDuration(duration).setInterpolator(adjustedInterpolator);
homeAnim.start();
mLauncherTransitionController = null;
}
if (mLauncherTransitionController == null) {
return;
}
@@ -948,50 +925,41 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
mLauncherTransitionController.getAnimationPlayer().setDuration(duration);
if (QUICKSTEP_SPRINGS.get()) {
mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs);
mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
}
mLauncherTransitionController.getAnimationPlayer().start();
}
}
/**
* Creates an Animator that transforms the current app window into the home app.
* Creates an animation that transforms the current app window into the home app.
* @param startProgress The progress of {@link #mCurrentShift} to start the window from.
* @param homeAnimationFactory The home animation factory.
*/
private Animator createWindowAnimationToHome(float startProgress,
private RectFSpringAnim createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
mTransformParams.setProgress(startProgress)));
RectF originalTarget = new RectF(mClipAnimationHelper.getTargetRect());
final RectF finalTarget = homeAnimationFactory.getWindowTargetRect();
final RectFEvaluator rectFEvaluator = new RectFEvaluator();
final RectF targetRect = new RectF();
final RectF currentRect = new RectF();
final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
final View floatingView = homeAnimationFactory.getFloatingView();
final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect);
if (isFloatingIconView) {
anim.addListener((FloatingIconView) floatingView);
anim.addAnimatorListener((FloatingIconView) floatingView);
}
AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
// We want the window alpha to be 0 once this threshold is met, so that the
// FolderIconView can be seen morphing into the icon shape.
final float windowAlphaThreshold = isFloatingIconView ? 0.75f : 1f;
anim.addUpdateListener(animation -> {
float progress = animation.getAnimatedFraction();
anim.addOnUpdateListener((currentRect, progress) -> {
float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress);
// Initially go towards original target (task view in recents),
// but accelerate towards the final target.
// TODO: This is technically not correct. Instead, motion should continue at
// the released velocity but accelerate towards the target.
targetRect.set(rectFEvaluator.evaluate(interpolatedProgress,
originalTarget, finalTarget));
currentRect.set(rectFEvaluator.evaluate(interpolatedProgress, startRect, targetRect));
homeAnim.setPlayFraction(progress);
float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0,
windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR);
@@ -1003,10 +971,17 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress,
windowAlphaThreshold);
}
});
anim.addListener(new AnimationSuccessListener() {
anim.addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
homeAnim.dispatchOnStart();
}
@Override
public void onAnimationSuccess(Animator animator) {
homeAnim.getAnimationPlayer().end();
if (mRecentsView != null) {
mRecentsView.post(mRecentsView::resetTaskVisuals);
}
@@ -1015,17 +990,12 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
return anim;
}
@UiThread
private void resumeLastTaskForQuickstep() {
setStateOnUiThread(STATE_RESUME_LAST_TASK);
doLogGesture(LAST_TASK);
reset();
}
@UiThread
private void resumeLastTask() {
mRecentsAnimationWrapper.finish(false /* toRecents */, null);
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
doLogGesture(LAST_TASK);
reset();
}
@UiThread
@@ -1033,18 +1003,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
// Launch the task user scrolled to (mRecentsView.getNextPage()).
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// We finish recents animation inside launchTask() when live tile is enabled.
mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
mMainThreadHandler);
mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false);
} else {
mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
mMainThreadHandler);
mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false);
});
}
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
doLogGesture(NEW_TASK);
reset();
}
public void reset() {
@@ -1072,7 +1039,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
private void invalidateHandlerWithLauncher() {
mLauncherTransitionController = null;
mActivityControlHelper.getAlphaProperty(mActivity).setValue(1);
mRecentsView.setEnableFreeScroll(true);
mRecentsView.setRunningTaskIconScaledDown(false);
@@ -1170,9 +1136,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
mLauncherTransitionController = null;
}
mActivityControlHelper.onSwipeUpComplete(mActivity);
mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
// Animate the first icon.
mRecentsView.animateUpRunningTaskIconScale();
mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation());
mRecentsView.setSwipeDownShouldLaunchApp(true);
RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
@@ -38,9 +38,16 @@ import androidx.annotation.UiThread;
*/
public class RecentsAnimationListenerSet implements RecentsAnimationListener {
// The actual app surface is replaced by a screenshot upon recents animation cancelation when
// deferredWithScreenshot is true. Launcher takes the responsibility to clean up this screenshot
// after app transition is finished. This delay is introduced to cover the app transition
// period of time.
private final int TRANSITION_DELAY = 100;
private final Set<SwipeAnimationListener> mListeners = new ArraySet<>();
private final boolean mShouldMinimizeSplitScreen;
private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
private RecentsAnimationControllerCompat mController;
public RecentsAnimationListenerSet(boolean shouldMinimizeSplitScreen,
Consumer<SwipeAnimationTargetSet> onFinishListener) {
@@ -64,6 +71,7 @@ public class RecentsAnimationListenerSet implements RecentsAnimationListener {
public final void onAnimationStart(RecentsAnimationControllerCompat controller,
RemoteAnimationTargetCompat[] targets, Rect homeContentInsets,
Rect minimizedHomeBounds) {
mController = controller;
SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets,
homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
mOnFinishListener);
@@ -75,12 +83,17 @@ public class RecentsAnimationListenerSet implements RecentsAnimationListener {
}
@Override
public final void onAnimationCanceled() {
public final void onAnimationCanceled(boolean deferredWithScreenshot) {
Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
for (SwipeAnimationListener listener : getListeners()) {
listener.onRecentsAnimationCanceled();
}
});
// TODO: handle the transition better instead of simply using a transition delay.
if (deferredWithScreenshot) {
MAIN_THREAD_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
TRANSITION_DELAY);
}
}
private SwipeAnimationListener[] getListeners() {
@@ -0,0 +1,179 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.util;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.FloatProperty;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.FlingSpringAnim;
import java.util.ArrayList;
import java.util.List;
import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
/**
* Applies spring forces to animate from a starting rect to a target rect,
* while providing update callbacks to the caller.
*/
public class RectFSpringAnim {
/**
* Although the rect position animation takes an indefinite amount of time since it depends on
* the initial velocity and applied forces, scaling from the starting rect to the target rect
* can be done in parallel at a fixed duration. Update callbacks are sent based on the progress
* of this animation, while the end callback is sent after all animations finish.
*/
private static final long RECT_SCALE_DURATION = 180;
private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X =
new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") {
@Override
public float getValue(RectFSpringAnim anim) {
return anim.mCurrentCenterX;
}
@Override
public void setValue(RectFSpringAnim anim, float currentCenterX) {
anim.mCurrentCenterX = currentCenterX;
anim.onUpdate();
}
};
private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_Y =
new FloatPropertyCompat<RectFSpringAnim>("rectCenterYSpring") {
@Override
public float getValue(RectFSpringAnim anim) {
return anim.mCurrentCenterY;
}
@Override
public void setValue(RectFSpringAnim anim, float currentCenterY) {
anim.mCurrentCenterY = currentCenterY;
anim.onUpdate();
}
};
private static final FloatProperty<RectFSpringAnim> RECT_SCALE_PROGRESS =
new FloatProperty<RectFSpringAnim>("rectScaleProgress") {
@Override
public Float get(RectFSpringAnim anim) {
return anim.mCurrentScaleProgress;
}
@Override
public void setValue(RectFSpringAnim anim, float currentScaleProgress) {
anim.mCurrentScaleProgress = currentScaleProgress;
anim.onUpdate();
}
};
private final RectF mStartRect;
private final RectF mTargetRect;
private final RectF mCurrentRect = new RectF();
private final List<OnUpdateListener> mOnUpdateListeners = new ArrayList<>();
private final List<Animator.AnimatorListener> mAnimatorListeners = new ArrayList<>();
private float mCurrentCenterX;
private float mCurrentCenterY;
private float mCurrentScaleProgress;
private boolean mRectXAnimEnded;
private boolean mRectYAnimEnded;
private boolean mRectScaleAnimEnded;
public RectFSpringAnim(RectF startRect, RectF targetRect) {
mStartRect = startRect;
mTargetRect = targetRect;
mCurrentCenterX = mStartRect.centerX();
mCurrentCenterY = mStartRect.centerY();
}
public void addOnUpdateListener(OnUpdateListener onUpdateListener) {
mOnUpdateListeners.add(onUpdateListener);
}
public void addAnimatorListener(Animator.AnimatorListener animatorListener) {
mAnimatorListeners.add(animatorListener);
}
public void start(PointF velocityPxPerMs) {
// Only tell caller that we ended if both x and y animations have ended.
OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> {
mRectXAnimEnded = true;
maybeOnEnd();
});
OnAnimationEndListener onYEndListener = ((animation, canceled, centerY, velocityY) -> {
mRectYAnimEnded = true;
maybeOnEnd();
});
FlingSpringAnim rectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, mCurrentCenterX,
mTargetRect.centerX(), velocityPxPerMs.x * 1000, onXEndListener);
FlingSpringAnim rectYAnim = new FlingSpringAnim(this, RECT_CENTER_Y, mCurrentCenterY,
mTargetRect.centerY(), velocityPxPerMs.y * 1000, onYEndListener);
ValueAnimator rectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
PropertyValuesHolder.ofFloat(RECT_SCALE_PROGRESS, 1))
.setDuration(RECT_SCALE_DURATION);
rectScaleAnim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
mRectScaleAnimEnded = true;
maybeOnEnd();
}
});
rectXAnim.start();
rectYAnim.start();
rectScaleAnim.start();
for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
animatorListener.onAnimationStart(null);
}
}
private void onUpdate() {
if (!mOnUpdateListeners.isEmpty()) {
float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(),
mTargetRect.width());
float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(),
mTargetRect.height());
mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentCenterY - currentHeight / 2,
mCurrentCenterX + currentWidth / 2, mCurrentCenterY + currentHeight / 2);
for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress);
}
}
}
private void maybeOnEnd() {
if (mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) {
for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
animatorListener.onAnimationEnd(null);
}
}
}
public interface OnUpdateListener {
void onUpdate(RectF currentRect, float progress);
}
}
@@ -23,7 +23,7 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.res.Resources;
import android.content.pm.LauncherApps.AppUsageLimit;
import android.icu.text.MeasureFormat;
import android.icu.text.MeasureFormat.FormatWidth;
import android.icu.util.Measure;
@@ -44,16 +44,16 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.systemui.shared.recents.model.Task;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Locale;
public final class DigitalWellBeingToast extends LinearLayout {
static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
static final int MINUTE_MS = 60000;
private final LauncherApps mLauncherApps;
public interface InitializeCallback {
void call(float saturation, String contentDescription);
void call(String contentDescription);
}
private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
@@ -67,6 +67,7 @@ public final class DigitalWellBeingToast extends LinearLayout {
setLayoutDirection(Utilities.isRtl(getResources()) ?
View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
setOnClickListener((view) -> openAppUsageSettings());
mLauncherApps = context.getSystemService(LauncherApps.class);
}
@Override
@@ -82,52 +83,32 @@ public final class DigitalWellBeingToast extends LinearLayout {
if (task.key.userId != UserHandle.myUserId()) {
setVisibility(GONE);
callback.call(1, task.titleDescription);
callback.call(task.titleDescription);
return;
}
Utilities.THREAD_POOL_EXECUTOR.execute(() -> {
long appUsageLimitTimeMs = -1;
long appRemainingTimeMs = -1;
final AppUsageLimit usageLimit = mLauncherApps.getAppUsageLimit(
task.getTopComponent().getPackageName(),
UserHandle.of(task.key.userId));
try {
final Method getAppUsageLimit = LauncherApps.class.getMethod(
"getAppUsageLimit",
String.class,
UserHandle.class);
final Object usageLimit = getAppUsageLimit.invoke(
getContext().getSystemService(LauncherApps.class),
task.getTopComponent().getPackageName(),
UserHandle.of(task.key.userId));
if (usageLimit != null) {
final Class appUsageLimitClass = usageLimit.getClass();
appUsageLimitTimeMs = (long) appUsageLimitClass.getMethod("getTotalUsageLimit").
invoke(usageLimit);
appRemainingTimeMs = (long) appUsageLimitClass.getMethod("getUsageRemaining").
invoke(usageLimit);
}
} catch (Exception e) {
// Do nothing
}
final long appUsageLimitTimeMsFinal = appUsageLimitTimeMs;
final long appRemainingTimeMsFinal = appRemainingTimeMs;
final long appUsageLimitTimeMs =
usageLimit != null ? usageLimit.getTotalUsageLimit() : -1;
final long appRemainingTimeMs =
usageLimit != null ? usageLimit.getUsageRemaining() : -1;
post(() -> {
if (appUsageLimitTimeMsFinal < 0) {
if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
setVisibility(GONE);
} else {
setVisibility(VISIBLE);
mText.setText(getText(appRemainingTimeMsFinal));
mImage.setImageResource(appRemainingTimeMsFinal > 0 ?
mText.setText(getText(appRemainingTimeMs));
mImage.setImageResource(appRemainingTimeMs > 0 ?
R.drawable.hourglass_top : R.drawable.hourglass_bottom);
}
callback.call(
appUsageLimitTimeMsFinal >= 0 && appRemainingTimeMsFinal <= 0 ? 0 : 1,
getContentDescriptionForTask(
task, appUsageLimitTimeMsFinal, appRemainingTimeMsFinal));
callback.call(getContentDescriptionForTask(
task, appUsageLimitTimeMs, appRemainingTimeMs));
});
});
}
@@ -196,12 +177,9 @@ public final class DigitalWellBeingToast extends LinearLayout {
}
private String getText(long remainingTime) {
final Resources resources = getResources();
return (remainingTime <= 0) ?
resources.getString(R.string.app_in_grayscale) :
resources.getString(
R.string.time_left_for_app,
getRoundedUpToMinuteReadableDuration(remainingTime));
return getResources().getString(
R.string.time_left_for_app,
getRoundedUpToMinuteReadableDuration(remainingTime));
}
public void openAppUsageSettings() {
@@ -225,7 +203,7 @@ public final class DigitalWellBeingToast extends LinearLayout {
private String getContentDescriptionForTask(
Task task, long appUsageLimitTimeMs, long appRemainingTimeMs) {
return appUsageLimitTimeMs >= 0 ?
return appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0 ?
getResources().getString(
R.string.task_contents_description_with_remaining_time,
task.titleDescription,
@@ -56,27 +56,6 @@ import com.android.quickstep.util.LayoutUtils;
@TargetApi(Build.VERSION_CODES.O)
public class LauncherRecentsView extends RecentsView<Launcher> {
public static final FloatProperty<LauncherRecentsView> TRANSLATION_Y_FACTOR =
new FloatProperty<LauncherRecentsView>("translationYFactor") {
@Override
public void setValue(LauncherRecentsView view, float v) {
view.setTranslationYFactor(v);
}
@Override
public Float get(LauncherRecentsView view) {
return view.mTranslationYFactor;
}
};
/**
* A ratio representing the view's relative placement within its padded space. For example, 0
* is top aligned and 0.5 is centered vertically.
*/
@ViewDebug.ExportedProperty(category = "launcher")
private float mTranslationYFactor;
private final TransformParams mTransformParams = new TransformParams();
private ChipsContainer mChipsContainer;
@@ -95,18 +74,7 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
@Override
public void startHome() {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
takeScreenshotAndFinishRecentsAnimation(true,
() -> mActivity.getStateManager().goToState(NORMAL));
} else {
mActivity.getStateManager().goToState(NORMAL);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
setTranslationYFactor(mTranslationYFactor);
mActivity.getStateManager().goToState(NORMAL);
}
@Override
@@ -117,9 +85,9 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
params.bottomMargin = mActivity.getDeviceProfile().chipHintBottomMarginPx;
}
public void setTranslationYFactor(float translationFactor) {
mTranslationYFactor = translationFactor;
setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
@Override
public void setTranslationY(float translationY) {
super.setTranslationY(translationY);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
LauncherState state = mActivity.getStateManager().getState();
if (state == OVERVIEW || state == ALL_APPS) {
@@ -128,10 +96,6 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
}
}
public float computeTranslationYForFactor(float translationYFactor) {
return translationYFactor * (getPaddingBottom() - getPaddingTop());
}
public void setHintVisibility(float v) {
if (mChipsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) {
mChipsContainer.setHintVisibility(v);
@@ -1,5 +1,11 @@
package com.android.quickstep.views;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
@@ -9,16 +15,37 @@ import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.FloatProperty;
import com.android.launcher3.anim.Interpolators;
public class LiveTileOverlay extends Drawable {
private static final long ICON_ANIM_DURATION = 120;
private static final FloatProperty<LiveTileOverlay> PROGRESS =
new FloatProperty<LiveTileOverlay>("progress") {
@Override
public void setValue(LiveTileOverlay liveTileOverlay, float progress) {
liveTileOverlay.setIconAnimationProgress(progress);
}
@Override
public Float get(LiveTileOverlay liveTileOverlay) {
return liveTileOverlay.mIconAnimationProgress;
}
};
private final Paint mPaint = new Paint();
private Rect mBoundsRect = new Rect();
private RectF mCurrentRect;
private float mCornerRadius;
private Drawable mIcon;
private Animator mIconAnimator;
private boolean mDrawEnabled = true;
private float mIconAnimationProgress = 0f;
public LiveTileOverlay() {
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
@@ -35,6 +62,33 @@ public class LiveTileOverlay extends Drawable {
invalidateSelf();
}
public void setIcon(Drawable icon) {
mIcon = icon;
}
public void startIconAnimation() {
if (mIconAnimator != null) {
mIconAnimator.cancel();
}
// This animator must match the icon part of {@link TaskView#FOCUS_TRANSITION} animation.
mIconAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 1);
mIconAnimator.setDuration(ICON_ANIM_DURATION).setInterpolator(LINEAR);
mIconAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mIconAnimator = null;
}
});
mIconAnimator.start();
}
public float cancelIconAnimation() {
if (mIconAnimator != null) {
mIconAnimator.cancel();
}
return mIconAnimationProgress;
}
public void setDrawEnabled(boolean drawEnabled) {
if (mDrawEnabled != drawEnabled) {
mDrawEnabled = drawEnabled;
@@ -46,6 +100,16 @@ public class LiveTileOverlay extends Drawable {
public void draw(Canvas canvas) {
if (mCurrentRect != null && mDrawEnabled) {
canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
if (mIcon != null && mIconAnimationProgress > 0f) {
canvas.save();
float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
1f).getInterpolation(mIconAnimationProgress);
canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
canvas.scale(scale, scale);
mIcon.draw(canvas);
canvas.restore();
}
}
}
@@ -59,4 +123,9 @@ public class LiveTileOverlay extends Drawable {
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
private void setIconAnimationProgress(float progress) {
mIconAnimationProgress = progress;
invalidateSelf();
}
}
@@ -22,13 +22,14 @@ import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.launcher3.uioverrides.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
@@ -81,6 +82,7 @@ import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.OverScroller;
@@ -94,16 +96,15 @@ import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.util.TaskViewDrawable;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.BackgroundExecutor;
import com.android.systemui.shared.system.LauncherEventUtil;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowCallbacksCompat;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -167,6 +168,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
private final ViewPool<TaskView> mTaskViewPool;
private boolean mDwbToastShown;
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
@@ -278,6 +281,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
private final int mEmptyMessagePadding;
private boolean mShowEmptyMessage;
private Layout mEmptyTextLayout;
private LiveTileOverlay mLiveTileOverlay;
private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
(inMultiWindowMode) -> {
@@ -426,6 +430,16 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
public void setOverviewStateEnabled(boolean enabled) {
mOverviewStateEnabled = enabled;
updateTaskStackListenerState();
if (!enabled) mDwbToastShown = false;
}
public void onDigitalWellbeingToastShown() {
if (!mDwbToastShown) {
mDwbToastShown = true;
mActivity.getUserEventDispatcher().logActionTip(
LauncherEventUtil.VISIBLE,
LauncherLogProto.TipType.DWB_TOAST);
}
}
@Override
@@ -855,10 +869,15 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
}
public void animateUpRunningTaskIconScale() {
animateUpRunningTaskIconScale(0);
}
public void animateUpRunningTaskIconScale(float startProgress) {
mRunningTaskIconScaledDown = false;
TaskView firstTask = getRunningTaskView();
if (firstTask != null) {
firstTask.animateIconScaleAndDimIntoView();
firstTask.setIconScaleAnimStartProgress(startProgress);
}
}
@@ -1567,6 +1586,16 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
mClipAnimationHelper = clipAnimationHelper;
}
public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) {
mLiveTileOverlay = liveTileOverlay;
}
public void updateLiveTileIcon(Drawable icon) {
if (mLiveTileOverlay != null) {
mLiveTileOverlay.setIcon(icon);
}
}
public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
if (mRecentsAnimationWrapper == null) {
if (onFinishComplete != null) {
@@ -1577,50 +1606,4 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
mRecentsAnimationWrapper.finish(toRecents, onFinishComplete);
}
public void takeScreenshotAndFinishRecentsAnimation(boolean toRecents,
Runnable onFinishComplete) {
if (mRecentsAnimationWrapper == null || getRunningTaskView() == null) {
if (onFinishComplete != null) {
onFinishComplete.run();
}
return;
}
SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
if (controller != null) {
// Update the screenshot of the task
ThumbnailData taskSnapshot = controller.screenshotTask(mRunningTaskId);
TaskView taskView = updateThumbnail(mRunningTaskId, taskSnapshot);
if (taskView != null) {
taskView.setShowScreenshot(true);
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
new WindowCallbacksCompat(taskView) {
// The number of frames to defer until we actually finish the animation
private int mDeferFrameCount = 2;
@Override
public void onPostDraw(Canvas canvas) {
if (mDeferFrameCount > 0) {
mDeferFrameCount--;
// Workaround, detach and reattach to invalidate the root node for
// another draw
detach();
attach();
taskView.invalidate();
return;
}
detach();
mRecentsAnimationWrapper.finish(toRecents, () -> {
onFinishComplete.run();
mRunningTaskId = -1;
});
}
}.attach();
}
}
}
}
@@ -208,13 +208,7 @@ public class TaskMenuView extends AbstractFloatingView {
R.layout.task_view_menu_option, this, false);
menuOption.setIconAndLabelFor(
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
menuOptionView.setOnClickListener(
view -> mTaskView.getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
() -> onClickListener.onClick(view)));
} else {
menuOptionView.setOnClickListener(onClickListener);
}
menuOptionView.setOnClickListener(onClickListener);
mOptionLayout.addView(menuOptionView);
}
@@ -155,7 +155,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
private float mZoomScale;
private float mFullscreenProgress;
private Animator mIconAndDimAnimator;
private ObjectAnimator mIconAndDimAnimator;
private float mIconScaleAnimStartProgress = 0;
private float mFocusTransitionProgress = 1;
private boolean mShowScreenshot;
@@ -259,11 +260,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
Handler resultCallbackHandler) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (isRunningTask()) {
getRecentsView().finishRecentsAnimation(false,
getRecentsView().finishRecentsAnimation(false /* toRecents */,
() -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
} else {
getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
() -> launchTaskInternal(animate, resultCallback, resultCallbackHandler));
launchTaskInternal(animate, resultCallback, resultCallbackHandler);
}
} else {
launchTaskInternal(animate, resultCallback, resultCallbackHandler);
@@ -316,11 +316,16 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
mIconLoadRequest = iconCache.updateIconInBackground(mTask,
(task) -> {
setIcon(task.icon);
if (isRunningTask()) {
getRecentsView().updateLiveTileIcon(task.icon);
}
mDigitalWellBeingToast.initialize(
mTask,
(saturation, contentDescription) -> {
contentDescription -> {
setContentDescription(contentDescription);
mSnapshotView.setSaturation(saturation);
if (mDigitalWellBeingToast.getVisibility() == VISIBLE) {
getRecentsView().onDigitalWellbeingToastShown();
}
});
});
} else {
@@ -379,11 +384,16 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
mIconView.setScaleY(scale);
}
public void setIconScaleAnimStartProgress(float startProgress) {
mIconScaleAnimStartProgress = startProgress;
}
public void animateIconScaleAndDimIntoView() {
if (mIconAndDimAnimator != null) {
mIconAndDimAnimator.cancel();
}
mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1);
mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress);
mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR);
mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() {
@Override
+4
View File
@@ -26,4 +26,8 @@
determines how many thumbnails will be fetched in the background. -->
<integer name="recentsThumbnailCacheSize">3</integer>
<integer name="recentsIconCacheSize">12</integer>
<!-- Assistant Gesture -->
<integer name="assistant_gesture_min_time_threshold">200</integer>
<integer name="assistant_gesture_corner_deg_threshold">30</integer>
</resources>
+2 -2
View File
@@ -66,6 +66,6 @@
<dimen name="shelf_surface_offset">24dp</dimen>
<!-- Assistant Gestures -->
<dimen name="gestures_assistant_width">70dp</dimen>
<dimen name="gestures_assistant_threshold">200dp</dimen>
<dimen name="gestures_assistant_size">28dp</dimen>
<dimen name="gestures_assistant_drag_threshold">70dp</dimen>
</resources>
-4
View File
@@ -55,10 +55,6 @@
escaped form of '<'). [CHAR LIMIT=15] -->
<string name="shorter_duration_less_than_one_minute">&lt; 1 minute</string>
<!-- Annotation shown on an app card in Recents, telling that the app was switched to a
grayscale because it ran over its time limit [CHAR LIMIT=25] -->
<string name="app_in_grayscale">App in grayscale</string>
<!-- Annotation shown on an app card in Recents, telling that the app has a usage limit set by
the user, and a given time is left for it today [CHAR LIMIT=22] -->
<string name="time_left_for_app"><xliff:g id="time" example="7 minutes">%1$s</xliff:g> left today</string>
@@ -559,7 +559,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
float transY0 = floatingViewBounds[1] - offsetY;
float windowRadius = 0;
if (RecentsModel.INSTANCE.get(mLauncher).supportsRoundedCornersOnWindows()) {
if (!mDeviceProfile.isMultiWindowMode &&
RecentsModel.INSTANCE.get(mLauncher).supportsRoundedCornersOnWindows()) {
windowRadius = RecentsModel.INSTANCE.get(mLauncher)
.getWindowCornerRadius();
}
@@ -700,7 +701,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
float cornerRadius = RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
unlockAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -726,7 +728,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
Matrix matrix = new Matrix();
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
int duration = CLOSING_TRANSITION_DURATION_MS;
float windowCornerRadius = RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
float windowCornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
closingAnimator.setDuration(duration);
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
@@ -78,8 +78,9 @@ public class AllAppsState extends LauncherState {
}
@Override
public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
return new float[] {0.9f, -0.2f};
public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
float slightParallax = -launcher.getDeviceProfile().allAppsCellHeightPx * 0.3f;
return new float[] {0.9f, slightParallax};
}
@Override
@@ -26,8 +26,6 @@ import android.util.FloatProperty;
import android.view.View;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -35,6 +33,8 @@ import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
import androidx.annotation.NonNull;
/**
* State handler for recents view. Manages UI changes and animations for recents view based off the
* current {@link LauncherState}.
@@ -53,9 +53,9 @@ public abstract class BaseRecentsViewStateController<T extends View>
@Override
public void setState(@NonNull LauncherState state) {
float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
SCALE_PROPERTY.set(mRecentsView, scaleTranslationYFactor[0]);
getTranslationYFactorProperty().set(mRecentsView, scaleTranslationYFactor[1]);
float[] scaleTranslationY = state.getOverviewScaleAndTranslationY(mLauncher);
SCALE_PROPERTY.set(mRecentsView, scaleTranslationY[0]);
mRecentsView.setTranslationY(scaleTranslationY[1]);
getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
}
@@ -79,11 +79,11 @@ public abstract class BaseRecentsViewStateController<T extends View>
void setStateWithAnimationInternal(@NonNull final LauncherState toState,
@NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
PropertySetter setter = config.getPropertySetter(builder);
float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
float[] scaleTranslationY = toState.getOverviewScaleAndTranslationY(mLauncher);
Interpolator scaleAndTransYInterpolator = getScaleAndTransYInterpolator(toState, builder);
setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationYFactor[0],
setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationY[0],
scaleAndTransYInterpolator);
setter.setFloat(mRecentsView, getTranslationYFactorProperty(), scaleTranslationYFactor[1],
setter.setFloat(mRecentsView, View.TRANSLATION_Y, scaleTranslationY[1],
scaleAndTransYInterpolator);
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
@@ -101,13 +101,6 @@ public abstract class BaseRecentsViewStateController<T extends View>
return builder.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
}
/**
* Get property for translation Y factor for the recents view.
*
* @return the float property for the recents view
*/
abstract FloatProperty getTranslationYFactorProperty();
/**
* Get property for content alpha for the recents view.
*
@@ -2,7 +2,7 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import android.view.MotionEvent;
@@ -15,7 +15,6 @@
*/
package com.android.quickstep;
import android.animation.Animator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
@@ -31,7 +30,6 @@ import android.view.animation.Interpolator;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -85,8 +83,6 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
return true;
}
AlphaProperty getAlphaProperty(T activity);
/**
* Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
*/
@@ -135,6 +131,6 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
@NonNull RectF getWindowTargetRect();
@NonNull Animator createActivityAnimationToHome();
@NonNull AnimatorPlaybackController createActivityAnimationToHome();
}
}
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static android.content.Intent.ACTION_OVERLAY_CHANGED;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.util.Log;
import com.android.systemui.shared.system.QuickStepContract;
/**
* Observer for the resource config that specifies the navigation bar mode.
*/
public class NavBarModeOverlayResourceObserver extends BroadcastReceiver {
private static final String TAG = "NavBarModeOverlayResourceObserver";
private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
"config_navBarInteractionMode";
private final Context mContext;
private final OnChangeListener mOnChangeListener;
public NavBarModeOverlayResourceObserver(Context context, OnChangeListener listener) {
mContext = context;
mOnChangeListener = listener;
}
public void register() {
IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
filter.addDataScheme("package");
mContext.registerReceiver(this, filter);
}
@Override
public void onReceive(Context context, Intent intent) {
mOnChangeListener.onNavBarModeChanged(getSystemIntegerRes(context,
NAV_BAR_INTERACTION_MODE_RES_NAME));
}
public interface OnChangeListener {
void onNavBarModeChanged(int mode);
}
public static boolean isSwipeUpModeEnabled(Context context) {
return QuickStepContract.isSwipeUpMode(getSystemIntegerRes(context,
NAV_BAR_INTERACTION_MODE_RES_NAME));
}
public static boolean isEdgeToEdgeModeEnabled(Context context) {
return QuickStepContract.isGesturalMode(getSystemIntegerRes(context,
NAV_BAR_INTERACTION_MODE_RES_NAME));
}
private static int getSystemIntegerRes(Context context, String resName) {
Resources res = context.getResources();
int resId = res.getIdentifier(resName, "integer", "android");
if (resId != 0) {
return res.getInteger(resId);
} else {
Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
return -1;
}
}
}
@@ -15,7 +15,6 @@
*/
package com.android.quickstep;
import static com.android.quickstep.SwipeUpSetting.newSwipeUpSettingsObserver;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
@@ -28,10 +27,11 @@ import android.util.Log;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SecureSettingsObserver;
import com.android.launcher3.util.UiThreadHelper;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.QuickStepContract;
import androidx.annotation.WorkerThread;
@@ -58,7 +58,8 @@ public class OverviewInteractionState {
private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
private static final int MSG_SET_SWIPE_UP_ENABLED = 202;
private final SecureSettingsObserver mSwipeUpSettingObserver;
// TODO: Discriminate between swipe up and edge to edge
private final NavBarModeOverlayResourceObserver mSwipeUpSettingObserver;
private final Context mContext;
private final Handler mUiHandler;
@@ -66,7 +67,7 @@ public class OverviewInteractionState {
// These are updated on the background thread
private ISystemUiProxy mISystemUiProxy;
private boolean mSwipeUpEnabled = true;
private boolean mSwipeUpEnabled;
private float mBackButtonAlpha = 1;
private Runnable mOnSwipeUpSettingChangedListener;
@@ -80,15 +81,15 @@ public class OverviewInteractionState {
mUiHandler = new Handler(this::handleUiMessage);
mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
mSwipeUpSettingObserver =
newSwipeUpSettingsObserver(context, this::notifySwipeUpSettingChanged);
mSwipeUpEnabled = NavBarModeOverlayResourceObserver.isSwipeUpModeEnabled(mContext)
|| NavBarModeOverlayResourceObserver.isEdgeToEdgeModeEnabled(mContext);
if (SwipeUpSetting.isSystemNavigationSettingAvailable()) {
mSwipeUpSettingObserver = new NavBarModeOverlayResourceObserver(context,
this::notifySwipeUpSettingChanged);
mSwipeUpSettingObserver.register();
mSwipeUpEnabled = mSwipeUpSettingObserver.getValue();
resetHomeBounceSeenOnQuickstepEnabledFirstTime();
} else {
mSwipeUpSettingObserver = null;
mSwipeUpEnabled = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
}
}
@@ -175,7 +176,13 @@ public class OverviewInteractionState {
}
}
private void notifySwipeUpSettingChanged(boolean swipeUpEnabled) {
private void notifySwipeUpSettingChanged(int mode) {
boolean swipeUpEnabled = !QuickStepContract.isLegacyMode(mode);
boolean gesturalEnabled = QuickStepContract.isGesturalMode(mode);
FeatureFlags.SWIPE_HOME.updateStorage(mContext, gesturalEnabled);
FeatureFlags.ENABLE_ASSISTANT_GESTURE.updateStorage(mContext, gesturalEnabled);
mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, swipeUpEnabled ? 1 : 0, 0).
sendToTarget();
@@ -16,23 +16,18 @@
package com.android.quickstep;
import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import com.android.launcher3.util.SecureSettingsObserver;
import com.android.launcher3.util.SecureSettingsObserver.OnChangeListener;
public final class SwipeUpSetting {
private static final String TAG = "SwipeUpSetting";
private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
"config_swipe_up_gesture_setting_available";
private static final String SWIPE_UP_ENABLED_DEFAULT_RES_NAME =
"config_swipe_up_gesture_default";
public static boolean isSystemNavigationSettingAvailable() {
return getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME);
}
private static boolean getSystemBooleanRes(String resName) {
Resources res = Resources.getSystem();
@@ -45,18 +40,4 @@ public final class SwipeUpSetting {
return false;
}
}
public static boolean isSwipeUpSettingAvailable() {
return getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME);
}
public static boolean isSwipeUpEnabledDefaultValue() {
return getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
}
public static SecureSettingsObserver newSwipeUpSettingsObserver(Context context,
OnChangeListener listener) {
return new SecureSettingsObserver(context.getContentResolver(), listener,
SWIPE_UP_SETTING_NAME, isSwipeUpEnabledDefaultValue() ? 1 : 0);
}
}
@@ -0,0 +1,119 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherState;
import com.android.launcher3.TestProtocol;
import com.android.launcher3.Utilities;
import com.android.launcher3.uioverrides.OverviewState;
import com.android.quickstep.util.LayoutUtils;
public class TestInformationProvider extends ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Override
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
return 0;
}
@Override
public int delete(Uri uri, String s, String[] strings) {
return 0;
}
@Override
public Uri insert(Uri uri, ContentValues contentValues) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
return null;
}
@Override
public Bundle call(String method, String arg, Bundle extras) {
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
final Bundle response = new Bundle();
final Context context = getContext();
final DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE.
get(context).getDeviceProfile(context);
final LauncherAppState launcherAppState = LauncherAppState.getInstanceNoCreate();
final Launcher launcher = launcherAppState != null ?
(Launcher) launcherAppState.getModel().getCallback() : null;
switch (method) {
case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
final float swipeHeight =
OverviewState.getDefaultSwipeHeight(deviceProfile);
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
break;
}
case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
final float swipeHeight =
LayoutUtils.getShelfTrackingDistance(context, deviceProfile);
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
break;
}
case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
if (launcher == null) return null;
final float progress = LauncherState.OVERVIEW.getVerticalProgress(launcher)
- LauncherState.ALL_APPS.getVerticalProgress(launcher);
final float distance =
launcher.getAllAppsController().getShiftRange() * progress;
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
break;
}
case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
if (launcher == null) return null;
final float progress = LauncherState.NORMAL.getVerticalProgress(launcher)
- LauncherState.ALL_APPS.getVerticalProgress(launcher);
final float distance =
launcher.getAllAppsController().getShiftRange() * progress;
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
break;
}
}
return response;
}
return null;
}
}
@@ -16,25 +16,29 @@
package com.android.quickstep;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.android.quickstep.QuickStepOnOffRule.Mode.BOTH;
import static com.android.quickstep.QuickStepOnOffRule.Mode.OFF;
import static com.android.quickstep.QuickStepOnOffRule.Mode.ON;
import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_2BUTTON_OVERLAY;
import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_3BUTTON_OVERLAY;
import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
import static org.junit.Assert.assertTrue;
import android.provider.Settings;
import android.content.Context;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.TestHelpers;
import com.android.systemui.shared.system.QuickStepContract;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -76,53 +80,59 @@ public class QuickStepOnOffRule implements TestRule {
return new Statement() {
@Override
public void evaluate() throws Throwable {
if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
try {
if (mode == ON || mode == BOTH) {
evaluateWithQuickstepOn();
}
if (mode == OFF || mode == BOTH) {
evaluateWithQuickstepOff();
}
} finally {
setSwipeUpSetting(null);
final Context context = getInstrumentation().getContext();
final String prevOverlayPkg = QuickStepContract.isGesturalMode(context)
? NAV_BAR_MODE_GESTURAL_OVERLAY
: QuickStepContract.isSwipeUpMode(context)
? NAV_BAR_MODE_2BUTTON_OVERLAY
: NAV_BAR_MODE_3BUTTON_OVERLAY;
try {
if (mode == ON || mode == BOTH) {
evaluateWithQuickstepOn();
}
} else {
// Execute without changing the setting, if the requested mode is
// compatible.
final boolean swipeUpEnabledDefaultValue =
SwipeUpSetting.isSwipeUpEnabledDefaultValue();
if (mode == BOTH ||
mode == ON && swipeUpEnabledDefaultValue ||
mode == OFF && !swipeUpEnabledDefaultValue) {
evaluateWithoutChangingSetting(base);
if (mode == OFF || mode == BOTH) {
evaluateWithQuickstepOff();
}
} finally {
setActiveOverlay(prevOverlayPkg);
}
}
public void setSwipeUpSetting(String value) {
Log.d(TAG, "setSwipeUpSetting: " + value);
assertTrue("Couldn't change Quickstep mode",
Settings.Secure.putString(
InstrumentationRegistry.getInstrumentation().getTargetContext().
getContentResolver(),
SWIPE_UP_SETTING_NAME,
value));
}
public void evaluateWithoutChangingSetting(Statement base) throws Throwable {
base.evaluate();
}
private void evaluateWithQuickstepOff() throws Throwable {
setSwipeUpSetting("0");
setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY);
evaluateWithoutChangingSetting(base);
}
private void evaluateWithQuickstepOn() throws Throwable {
setSwipeUpSetting("1");
setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY);
base.evaluate();
}
private void setActiveOverlay(String overlayPackage) {
setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
// TODO: Wait until nav bar mode has applied
}
private void setOverlayPackageEnabled(String overlayPackage, boolean enable) {
Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
final String action = enable ? "enable" : "disable";
try {
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
"cmd overlay " + action + " " + overlayPackage);
} catch (IOException e) {
e.printStackTrace();
}
}
};
} else {
return base;
@@ -37,6 +37,9 @@ import org.junit.runner.RunWith;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
static final int STRESS_REPEAT_COUNT = 10;
@Override
@Before
public void setUp() throws Exception {
@@ -79,4 +82,28 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
closeLauncherActivity();
mLauncher.getBackground().switchToOverview();
}
@Test
@QuickstepOnOff
public void testStressPressHome() {
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
// Destroy Launcher activity.
closeLauncherActivity();
// The test action.
mLauncher.pressHome();
}
}
@Test
@QuickstepOnOff
public void testStressSwipeToOverview() {
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
// Destroy Launcher activity.
closeLauncherActivity();
// The test action.
mLauncher.getBackground().switchToOverview();
}
}
}
@@ -275,14 +275,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
mLongPressHelper.setLongPressTimeoutFactor(longPressTimeoutFactor);
}
@Override
public void setTag(Object tag) {
if (tag != null) {
LauncherModel.checkItemInfo((ItemInfo) tag);
}
super.setTag(tag);
}
@Override
public void refreshDrawableState() {
if (!mIgnorePressedStateChange) {
+26 -13
View File
@@ -133,6 +133,7 @@ public class DeviceProfile {
private final Rect mInsets = new Rect();
public final Rect workspacePadding = new Rect();
private final Rect mHotseatPadding = new Rect();
// When true, nav bar is on the left side of the screen.
private boolean mIsSeascape;
// Notification dots
@@ -587,28 +588,40 @@ public class DeviceProfile {
/**
* Gets an item's location on the home screen. This is useful if the home screen
* is animating, otherwise use {@link View#getLocationOnScreen(int[])}.
*
* TODO(b/123900446): Handle landscape mode
* @param pageDiff The page difference relative to the current page.
*/
public void getItemLocation(int cellX, int cellY, int spanX, int spanY, int container,
int pageDiff, Rect outBounds) {
outBounds.setEmpty();
outBounds.left = mInsets.left
+ workspacePadding.left + cellLayoutPaddingLeftRightPx + (cellX * getCellSize().x);
outBounds.top = mInsets.top;
if (container == CONTAINER_HOTSEAT) {
outBounds.top += workspacePadding.top
+ (inv.numRows * getCellSize().y)
+ verticalDragHandleSizePx
- verticalDragHandleOverlapWorkspace;
outBounds.bottom = outBounds.top + hotseatBarSizePx - hotseatBarBottomPaddingPx;
final int actualHotseatCellHeight;
if (isVerticalBarLayout()) {
actualHotseatCellHeight = availableHeightPx / inv.numRows;
if (mIsSeascape) {
outBounds.left = mHotseatPadding.left;
} else {
outBounds.left = availableWidthPx - hotseatBarSizePx + mHotseatPadding.left;
}
outBounds.right = outBounds.left + iconSizePx;
outBounds.top = mHotseatPadding.top
+ actualHotseatCellHeight * (inv.numRows - cellX - 1);
outBounds.bottom = outBounds.top + actualHotseatCellHeight;
} else {
actualHotseatCellHeight = hotseatBarSizePx - hotseatBarBottomPaddingPx
- hotseatBarTopPaddingPx;
outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
+ (cellX * getCellSize().x);
outBounds.right = outBounds.left + getCellSize().x;
outBounds.top = mInsets.top + availableHeightPx - hotseatBarSizePx;
outBounds.bottom = outBounds.top + actualHotseatCellHeight;
}
} else {
outBounds.top += workspacePadding.top + (cellY * getCellSize().y);
outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
+ (cellX * getCellSize().x) + (pageDiff * availableWidthPx);
outBounds.right = outBounds.left + (getCellSize().x * spanX);
outBounds.top = mInsets.top + workspacePadding.top + (cellY * getCellSize().y);
outBounds.bottom = outBounds.top + (getCellSize().y * spanY);
outBounds.left += (pageDiff) * availableWidthPx;
}
outBounds.right = outBounds.left + (getCellSize().x * spanX);
}
public float getAspectRatioWithInsets() {
@@ -20,7 +20,10 @@ import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
import static com.android.launcher3.Utilities.getDevicePrefs;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -114,6 +117,7 @@ public class InvariantDeviceProfile {
private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
private ConfigMonitor mConfigMonitor;
private OverlayMonitor mOverlayMonitor;
@VisibleForTesting
public InvariantDeviceProfile() {}
@@ -131,6 +135,7 @@ public class InvariantDeviceProfile {
defaultLayoutId = p.defaultLayoutId;
demoModeLayoutId = p.demoModeLayoutId;
mExtraAttrs = p.mExtraAttrs;
mOverlayMonitor = p.mOverlayMonitor;
}
@TargetApi(23)
@@ -138,8 +143,12 @@ public class InvariantDeviceProfile {
initGrid(context, Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null));
mConfigMonitor = new ConfigMonitor(context,
APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
mOverlayMonitor = new OverlayMonitor(context);
}
/**
* This constructor should NOT have any monitors by design.
*/
public InvariantDeviceProfile(Context context, String gridName) {
String newName = initGrid(context, gridName);
if (newName == null || !newName.equals(gridName)) {
@@ -555,4 +564,20 @@ public class InvariantDeviceProfile {
return this;
}
}
private class OverlayMonitor extends BroadcastReceiver {
private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
OverlayMonitor(Context context) {
IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
filter.addDataScheme("package");
context.registerReceiver(this, filter);
}
@Override
public void onReceive(Context context, Intent intent) {
onConfigChanged(context);
}
}
}
-2
View File
@@ -114,8 +114,6 @@ public class ItemInfo {
ItemInfo(ItemInfo info) {
copyFrom(info);
// tempdebug:
LauncherModel.checkItemInfo(this);
}
public void copyFrom(ItemInfo info) {
+24 -17
View File
@@ -378,14 +378,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
}
if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
mUserEventDispatcher = null;
initDeviceProfile(mDeviceProfile.inv);
dispatchDeviceProfileChanged();
reapplyUi();
mDragLayer.recreateControllers();
// TODO: We can probably avoid rebind when only screen size changed.
rebindModel();
onIdpChanged(mDeviceProfile.inv);
}
mOldConfig.setTo(newConfig);
@@ -410,8 +403,19 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
@Override
public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
onIdpChanged(idp);
}
private void onIdpChanged(InvariantDeviceProfile idp) {
mUserEventDispatcher = null;
initDeviceProfile(idp);
getRootView().dispatchInsets();
dispatchDeviceProfileChanged();
reapplyUi();
mDragLayer.recreateControllers();
// TODO: We can probably avoid rebind when only screen size changed.
rebindModel();
}
private void initDeviceProfile(InvariantDeviceProfile idp) {
@@ -488,11 +492,15 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
@Override
public void invalidateParent(ItemInfo info) {
FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(getDeviceProfile().inv);
if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) {
if (info.container >= 0) {
View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container);
if (folderIcon != null) {
folderIcon.invalidate();
if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) {
FolderIconPreviewVerifier verifier =
new FolderIconPreviewVerifier(getDeviceProfile().inv);
verifier.setFolderInfo((FolderInfo) folderIcon.getTag());
if (verifier.isItemInPreview(info.rank)) {
folderIcon.invalidate();
}
}
}
}
@@ -1763,12 +1771,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
@Override
public void bindScreens(IntArray orderedScreenIds) {
// Make sure the first screen is always at the start.
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
} else if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()
&& orderedScreenIds.isEmpty()) {
} else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
// If there are no screens, we need to have an empty screen
mWorkspace.addExtraEmptyScreen();
}
@@ -1784,7 +1791,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
int screenId = orderedScreenIds.get(i);
if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenId != Workspace.FIRST_SCREEN_ID) {
if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
// No need to bind the first screen, as its always bound.
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
}
@@ -34,7 +34,6 @@ import android.util.Pair;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.AddWorkspaceItemsTask;
@@ -207,57 +206,6 @@ public class LauncherModel extends BroadcastReceiver
hasVerticalHotseat, verifyChanges);
}
static void checkItemInfoLocked(
final int itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
if (modelItem != null && item != modelItem) {
// If it is a release build on a release device, check all the data is consistent as
// we don't want to crash non-dev users.
if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
if (modelItem.title.toString().equals(item.title.toString()) &&
modelItem.getIntent().filterEquals(item.getIntent()) &&
modelItem.id == item.id &&
modelItem.itemType == item.itemType &&
modelItem.container == item.container &&
modelItem.screenId == item.screenId &&
modelItem.cellX == item.cellX &&
modelItem.cellY == item.cellY &&
modelItem.spanX == item.spanX &&
modelItem.spanY == item.spanY) {
// For all intents and purposes, this is the same object
return;
}
}
// the modelItem needs to match up perfectly with item if our model is
// to be consistent with the database-- for now, just require
// modelItem == item or the equality check above
String msg = "item: " + ((item != null) ? item.toString() : "null") +
"modelItem: " +
((modelItem != null) ? modelItem.toString() : "null") +
"Error: ItemInfo passed to checkItemInfo doesn't match original";
RuntimeException e = new RuntimeException(msg);
if (stackTrace != null) {
e.setStackTrace(stackTrace);
}
throw e;
}
}
static void checkItemInfo(final ItemInfo item) {
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
final int itemId = item.id;
Runnable r = new Runnable() {
public void run() {
synchronized (sBgDataModel) {
checkItemInfoLocked(itemId, item, stackTrace);
}
}
};
runOnWorkerThread(r);
}
/**
* Set this as the current Launcher activity object for the loader.
*/
@@ -20,6 +20,7 @@ import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
import android.annotation.TargetApi;
import android.app.backup.BackupManager;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
@@ -150,7 +151,7 @@ public class LauncherProvider extends ContentProvider {
mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler);
if (RestoreDbTask.isPending(getContext())) {
if (!RestoreDbTask.performRestore(mOpenHelper)) {
if (!RestoreDbTask.performRestore(mOpenHelper, new BackupManager(getContext()))) {
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
}
// Set is pending to false irrespective of the result, so that it doesn't get
@@ -542,6 +543,7 @@ public class LauncherProvider extends ContentProvider {
* The class is subclassed in tests to create an in-memory db.
*/
public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback {
private final BackupManager mBackupManager;
private final Handler mWidgetHostResetHandler;
private final Context mContext;
private int mMaxItemId = -1;
@@ -571,6 +573,7 @@ public class LauncherProvider extends ContentProvider {
super(context, tableName, SCHEMA_VERSION);
mContext = context;
mWidgetHostResetHandler = widgetHostResetHandler;
mBackupManager = new BackupManager(mContext);
}
protected void initIds() {
@@ -620,9 +623,12 @@ public class LauncherProvider extends ContentProvider {
Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
}
public long getSerialNumberForUser(UserHandle user) {
return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user);
}
public long getDefaultUserSerial() {
return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(
Process.myUserHandle());
return getSerialNumberForUser(Process.myUserHandle());
}
private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
@@ -723,7 +729,7 @@ public class LauncherProvider extends ContentProvider {
convertShortcutsToLauncherActivities(db);
case 26:
// QSB was moved to the grid. Clear the first row on screen 0.
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
!LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
break;
}
+1 -7
View File
@@ -192,13 +192,7 @@ public class LauncherState {
return getWorkspaceScaleAndTranslation(launcher);
}
/**
* Returns 2 floats designating how to transition overview:
* scale for the current and adjacent pages
* translationY factor where 0 is top aligned and 0.5 is centered vertically
*/
public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
// TODO: Simplify to use a constant value instead of a factor.
public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
return new float[] {1.1f, 0f};
}
@@ -30,4 +30,14 @@ public final class TestProtocol {
public static final int OVERVIEW_STATE_ORDINAL = 2;
public static final int ALL_APPS_STATE_ORDINAL = 3;
public static final int BACKGROUND_APP_STATE_ORDINAL = 4;
public static final String TEST_INFO_RESPONSE_FIELD = "response";
public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT =
"home-to-overview-swipe-height";
public static final String REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT =
"background-to-overview-swipe-height";
public static final String REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT =
"all-apps-to-overview-swipe-height";
public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT =
"home-to-all-apps-swipe-height";
}
+10 -2
View File
@@ -54,6 +54,7 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -114,6 +115,11 @@ public final class Utilities {
public static final int SINGLE_FRAME_MS = 16;
/**
* Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
*/
public static final int EDGE_NAV_BAR = 1 << 8;
/**
* Indicates if the device has a debug build. Should only be used to store additional info or
* add extra logging and not for changing the app behavior.
@@ -571,9 +577,11 @@ public final class Utilities {
final boolean isFolderIcon = v instanceof FolderIcon;
final Rect rect = new Rect();
// Deep shortcut views have their icon drawn in a separate view.
final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
if (fromDeepShortcutView) {
// Deep shortcut views have their icon drawn in a separate view.
if (v instanceof DeepShortcutView) {
dragLayer.getDescendantRectRelativeToSelf(((DeepShortcutView) v).getIconView(), rect);
} else if (fromDeepShortcutView) {
DeepShortcutView view = (DeepShortcutView) v.getParent();
dragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
} else if ((isBubbleTextView || isFolderIcon) && v.getTag() instanceof ItemInfo
+2 -2
View File
@@ -487,7 +487,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
* @param qsb an existing qsb to recycle or null.
*/
public void bindAndInitFirstWorkspaceScreen(View qsb) {
if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
return;
}
// Add the first page
@@ -794,7 +794,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
int id = mWorkspaceScreens.keyAt(i);
CellLayout cl = mWorkspaceScreens.valueAt(i);
// FIRST_SCREEN_ID can never be removed.
if ((!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || id > FIRST_SCREEN_ID)
if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID)
&& cl.getShortcutsAndWidgets().getChildCount() == 0) {
removeScreens.add(id);
}
@@ -188,9 +188,12 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil
private void setAlphas(LauncherState toState, AnimationConfig config,
AnimatorSetBuilder builder) {
setAlphas(toState.getVisibleElements(mLauncher), config, builder);
}
public void setAlphas(int visibleElements, AnimationConfig config, AnimatorSetBuilder builder) {
PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER
: config.getPropertySetter(builder);
int visibleElements = toState.getVisibleElements(mLauncher);
boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
boolean hasContent = (visibleElements & ALL_APPS_CONTENT) != 0;
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.anim;
import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
import androidx.dynamicanimation.animation.FlingAnimation;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
/**
* Given a property to animate and a target value and starting velocity, first apply friction to
* the fling until we pass the target, then apply a spring force to pull towards the target.
*/
public class FlingSpringAnim {
private static final float FLING_FRICTION = 1.5f;
// Have the spring pull towards the target if we've slowed down too much before reaching it.
private static final float FLING_END_THRESHOLD_PX = 50f;
private static final float SPRING_STIFFNESS = 350f;
private static final float SPRING_DAMPING = SpringForce.DAMPING_RATIO_LOW_BOUNCY;
private final FlingAnimation mFlingAnim;
public <K> FlingSpringAnim(K object, FloatPropertyCompat<K> property, float startPosition,
float targetPosition, float startVelocity, OnAnimationEndListener onEndListener) {
mFlingAnim = new FlingAnimation(object, property)
.setFriction(FLING_FRICTION)
.setMinimumVisibleChange(FLING_END_THRESHOLD_PX)
.setStartVelocity(startVelocity)
.setMinValue(Math.min(startPosition, targetPosition))
.setMaxValue(Math.max(startPosition, targetPosition));
mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> {
SpringAnimation springAnim = new SpringAnimation(object, property)
.setStartVelocity(velocity)
.setSpring(new SpringForce(targetPosition)
.setStiffness(SPRING_STIFFNESS)
.setDampingRatio(SPRING_DAMPING));
springAnim.addEndListener(onEndListener);
springAnim.start();
}));
}
public void start() {
mFlingAnim.start();
}
}
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.anim;
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -32,8 +34,6 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
/**
* This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or
* a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet.
@@ -51,9 +51,9 @@ public class SpringObjectAnimator<T extends ProgressInterface> extends ValueAnim
private SpringProperty<T> mProperty;
private ArrayList<AnimatorListener> mListeners;
private boolean mSpringEnded = false;
private boolean mAnimatorEnded = false;
private boolean mEnded = false;
private boolean mSpringEnded = true;
private boolean mAnimatorEnded = true;
private boolean mEnded = true;
private static final FloatPropertyCompat<ProgressInterface> sFloatProperty =
new FloatPropertyCompat<ProgressInterface>("springObjectAnimator") {
+17 -10
View File
@@ -21,6 +21,9 @@ import static androidx.core.util.Preconditions.checkNotNull;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import androidx.annotation.GuardedBy;
@@ -65,9 +68,8 @@ abstract class BaseFlags {
// When enabled the promise icon is visible in all apps while installation an app.
public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
public static final TogglableFlag QSB_ON_FIRST_SCREEN = new TogglableFlag("QSB_ON_FIRST_SCREEN",
true,
"Enable moving the QSB on the 0th screen of the workspace");
// Enable moving the QSB on the 0th screen of the workspace
public static final boolean QSB_ON_FIRST_SCREEN = true;
public static final TogglableFlag EXAMPLE_FLAG = new TogglableFlag("EXAMPLE_FLAG", true,
"An example flag that doesn't do anything. Useful for testing");
@@ -178,7 +180,7 @@ abstract class BaseFlags {
currentValue = getFromStorage(context, defaultValue);
}
void updateStorage(Context context, boolean value) {
public void updateStorage(Context context, boolean value) {
SharedPreferences.Editor editor = context.getSharedPreferences(FLAGS_PREF_NAME,
Context.MODE_PRIVATE).edit();
if (value == defaultValue) {
@@ -256,11 +258,21 @@ abstract class BaseFlags {
@Override
public void initialize(Context context) {
contentResolver = context.getContentResolver();
contentResolver.registerContentObserver(Settings.Global.getUriFor(getKey()), true,
new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
superInitialize(context);
}});
superInitialize(context);
}
private void superInitialize(Context context) {
super.initialize(context);
}
@Override
void updateStorage(Context context, boolean value) {
public void updateStorage(Context context, boolean value) {
if (contentResolver == null) {
return;
}
@@ -274,10 +286,5 @@ abstract class BaseFlags {
}
return Settings.Global.getInt(contentResolver, getKey(), defaultValue ? 1 : 0) == 1;
}
@Override
public boolean get() {
return getFromStorage(null, getDefaultValue());
}
}
}
@@ -63,8 +63,7 @@ public class DragLayer extends BaseDragLayer<Launcher> {
public static final int ALPHA_INDEX_OVERLAY = 0;
public static final int ALPHA_INDEX_LAUNCHER_LOAD = 1;
public static final int ALPHA_INDEX_TRANSITIONS = 2;
public static final int ALPHA_INDEX_SWIPE_UP = 3;
private static final int ALPHA_CHANNEL_COUNT = 4;
private static final int ALPHA_CHANNEL_COUNT = 3;
public static final int ANIMATION_END_DISAPPEAR = 0;
public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
@@ -200,6 +200,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
private void setFolder(Folder folder) {
mFolder = folder;
mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
mPreviewVerifier.setFolderInfo(mFolder.getInfo());
updatePreviewItems(false);
}
@@ -16,6 +16,8 @@
package com.android.launcher3.folder;
import android.util.Log;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.InvariantDeviceProfile;
@@ -26,14 +28,20 @@ import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_I
*/
public class FolderIconPreviewVerifier {
private static final String TAG = "FolderPreviewVerifier";
private final int mMaxGridCountX;
private final int mMaxGridCountY;
private final int mMaxItemsPerPage;
private final int[] mGridSize = new int[2];
private final int[] mGridSize = new int[] { 1, 1 };
private int mNumItemsInFolder;
private int mGridCountX;
private boolean mDisplayingUpperLeftQuadrant = false;
/**
* Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
*/
public FolderIconPreviewVerifier(InvariantDeviceProfile profile) {
mMaxGridCountX = profile.numFolderColumns;
mMaxGridCountY = profile.numFolderRows;
@@ -42,11 +50,14 @@ public class FolderIconPreviewVerifier {
public void setFolderInfo(FolderInfo info) {
int numItemsInFolder = info.contents.size();
FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
mMaxGridCountY, mMaxItemsPerPage, mGridSize);
mGridCountX = mGridSize[0];
if (numItemsInFolder != mNumItemsInFolder) {
FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
mMaxGridCountY, mMaxItemsPerPage, mGridSize);
mGridCountX = mGridSize[0];
mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW;
mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW;
mNumItemsInFolder = numItemsInFolder;
}
}
/**
@@ -62,6 +73,10 @@ public class FolderIconPreviewVerifier {
* @return True iff the icon is in the 2x2 upper left quadrant of the Folder.
*/
public boolean isItemInPreview(int page, int rank) {
if (mGridSize[0] == 1) {
Log.w(TAG, "setFolderInfo not called before checking if item is in preview.");
}
// First page items are laid out such that the first 4 items are always in the upper
// left quadrant. For all other pages, we need to check the row and col.
if (page > 0 || mDisplayingUpperLeftQuadrant) {
@@ -321,6 +321,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> {
FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(
Launcher.getLauncher(getContext()).getDeviceProfile().inv);
verifier.setFolderInfo(mFolder.getInfo());
rank = 0;
for (int i = 0; i < itemCount; i++) {
View v = list.size() > i ? list.get(i) : null;
@@ -262,7 +262,7 @@ public class LauncherPreviewRenderer implements Callable<Bitmap> {
}
// Add first page QSB
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
View qsb = mHomeElementInflater.inflate(
R.layout.search_container_workspace, mWorkspace, false);
CellLayout.LayoutParams lp =
@@ -61,6 +61,8 @@ public abstract class BaseLoaderResults {
protected final WeakReference<Callbacks> mCallbacks;
private int mMyBindingId;
public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
mUiExecutor = new MainThreadExecutor();
@@ -94,6 +96,7 @@ public abstract class BaseLoaderResults {
appWidgets.addAll(mBgDataModel.appWidgets);
orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
mBgDataModel.lastBindId++;
mMyBindingId = mBgDataModel.lastBindId;
}
final int currentScreen;
@@ -285,6 +288,10 @@ public abstract class BaseLoaderResults {
protected void executeCallbacksTask(CallbackTask task, Executor executor) {
executor.execute(() -> {
if (mMyBindingId != mBgDataModel.lastBindId) {
Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
return;
}
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
task.execute(callbacks);
@@ -129,7 +129,7 @@ public class BgDataModel {
screenSet.add(item.screenId);
}
}
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenSet.isEmpty()) {
if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) {
screenSet.add(Workspace.FIRST_SCREEN_ID);
}
return screenSet.getArray();
@@ -253,8 +253,7 @@ public class GridSizeMigrationTask {
*/
protected void migrateScreen(int screenId) {
// If we are migrating the first screen, do not touch the first row.
int startY =
(FeatureFlags.QSB_ON_FIRST_SCREEN.get() && screenId == Workspace.FIRST_SCREEN_ID)
int startY = (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID)
? 1 : 0;
ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
@@ -442,8 +442,7 @@ public class LoaderCursor extends CursorWrapper {
if (item.screenId == Workspace.FIRST_SCREEN_ID) {
// Mark the first row as occupied (if the feature is enabled)
// in order to account for the QSB.
screen.markCells(0, 0, countX + 1, 1,
FeatureFlags.QSB_ON_FIRST_SCREEN.get());
screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
}
occupied.put(item.screenId, screen);
}
+13 -15
View File
@@ -340,8 +340,6 @@ public class LoaderTask implements Runnable {
Intent intent;
String targetPkg;
FolderIconPreviewVerifier verifier =
new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
while (!mStopped && c.moveToNext()) {
try {
if (c.user == null) {
@@ -366,13 +364,13 @@ public class LoaderTask implements Runnable {
ComponentName cn = intent.getComponent();
targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
if (!Process.myUserHandle().equals(c.user)) {
if (allUsers.indexOfValue(c.user) < 0) {
if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
c.markDeleted("Legacy shortcuts are only allowed for default user");
c.markDeleted("Legacy shortcuts are only allowed for current users");
continue;
} else if (c.restoreFlag != 0) {
// Don't restore items for other profiles.
c.markDeleted("Restore from managed profile not supported");
c.markDeleted("Restore from other profiles not supported");
continue;
}
}
@@ -461,8 +459,7 @@ public class LoaderTask implements Runnable {
c.markRestored();
}
boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
!verifier.isItemInPreview(c.getInt(rankIndex));
boolean useLowResIcon = !c.isOnWorkspaceOrHotseat();
if (c.restoreFlag != 0) {
// Already verified above that user is same as default user
@@ -745,24 +742,25 @@ public class LoaderTask implements Runnable {
}
}
// Sort the folder items, update ranks, and make sure all preview items are high res.
FolderIconPreviewVerifier verifier =
new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
// Sort the folder items and make sure all items in the preview are high resolution.
for (FolderInfo folder : mBgDataModel.folders) {
Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
verifier.setFolderInfo(folder);
int size = folder.contents.size();
// Update ranks here to ensure there are no gaps caused by removed folder items.
// Ranks are the source of truth for folder items, so cellX and cellY can be ignored
// for now. Database will be updated once user manually modifies folder.
for (int rank = 0; rank < size; ++rank) {
ShortcutInfo info = folder.contents.get(rank);
info.rank = rank;
int numItemsInPreview = 0;
for (ShortcutInfo info : folder.contents) {
if (info.usingLowResIcon()
&& info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
&& verifier.isItemInPreview(info.rank)) {
mIconCache.getTitleAndIcon(info, false);
numItemsInPreview++;
}
if (numItemsInPreview >= MAX_NUM_ITEMS_IN_PREVIEW) {
break;
}
}
}
@@ -37,6 +37,7 @@ import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -112,19 +113,18 @@ public class ModelWriter {
ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
if (modelItem != null && item != modelItem) {
// check all the data is consistent
if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
ShortcutInfo shortcut = (ShortcutInfo) item;
if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
modelShortcut.intent.filterEquals(shortcut.intent) &&
modelShortcut.id == shortcut.id &&
modelShortcut.itemType == shortcut.itemType &&
modelShortcut.container == shortcut.container &&
modelShortcut.screenId == shortcut.screenId &&
modelShortcut.cellX == shortcut.cellX &&
modelShortcut.cellY == shortcut.cellY &&
modelShortcut.spanX == shortcut.spanX &&
modelShortcut.spanY == shortcut.spanY) {
if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
if (modelItem.title.toString().equals(item.title.toString()) &&
modelItem.getIntent().filterEquals(item.getIntent()) &&
modelItem.id == item.id &&
modelItem.itemType == item.itemType &&
modelItem.container == item.container &&
modelItem.screenId == item.screenId &&
modelItem.cellX == item.cellX &&
modelItem.cellY == item.cellY &&
modelItem.spanX == item.spanX &&
modelItem.spanY == item.spanY) {
// For all intents and purposes, this is the same object
return;
}
@@ -310,7 +310,7 @@ public class ModelWriter {
/**
* Delete operations tracked using {@link #enqueueDeleteRunnable} will only be called
* if {@link #commitDelete} is called. Note that one of {@link #commitDelete()} or
* {@link #abortDelete()} MUST be called after this method, or else all delete
* {@link #abortDelete} MUST be called after this method, or else all delete
* operations will remain uncommitted indefinitely.
*/
public void prepareToUndoDelete() {
@@ -325,7 +325,7 @@ public class ModelWriter {
/**
* If {@link #prepareToUndoDelete} has been called, we store the Runnable to be run when
* {@link #commitDelete()} is called (or abandoned if {@link #abortDelete()} is called).
* {@link #commitDelete()} is called (or abandoned if {@link #abortDelete} is called).
* Otherwise, we run the Runnable immediately.
*/
private void enqueueDeleteRunnable(Runnable r) {
@@ -22,6 +22,7 @@ import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFI
import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
@@ -167,7 +168,8 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
BaseDragLayer dl = getPopupContainer();
if (!dl.isEventOverView(this, ev)) {
final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
if (!cameFromNavBar && !dl.isEventOverView(this, ev)) {
mLauncher.getUserEventDispatcher().logActionTapOutside(
LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
close(true);
@@ -104,7 +104,7 @@ public class ImportDataTask {
.getSerialNumberForUser(Process.myUserHandle()));
boolean createEmptyRowOnFirstScreen;
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null,
// get items on the first row of the first screen (min screen id)
"profileId = ? AND container = -100 AND cellY = 0 AND screen = " +
@@ -18,10 +18,15 @@ package com.android.launcher3.provider;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import android.app.backup.BackupManager;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.UserHandle;
import android.util.LongSparseArray;
import android.util.SparseLongArray;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
@@ -48,10 +53,10 @@ public class RestoreDbTask {
private static final String INFO_COLUMN_NAME = "name";
private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value";
public static boolean performRestore(DatabaseHelper helper) {
public static boolean performRestore(DatabaseHelper helper, BackupManager backupManager) {
SQLiteDatabase db = helper.getWritableDatabase();
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
new RestoreDbTask().sanitizeDB(helper, db);
new RestoreDbTask().sanitizeDB(helper, db, backupManager);
t.commit();
return true;
} catch (Exception e) {
@@ -62,20 +67,44 @@ public class RestoreDbTask {
/**
* Makes the following changes in the provider DB.
* 1. Removes all entries belonging to a managed profile as managed profiles
* cannot be restored.
* 1. Removes all entries belonging to any profiles that were not restored.
* 2. Marks all entries as restored. The flags are updated during first load or as
* the restored apps get installed.
* 3. If the user serial for primary profile is different than that of the previous device,
* update the entries to the new profile id.
* 3. If the user serial for any restored profile is different than that of the previous
* device, update the entries to the new profile id.
*/
private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db) throws Exception {
private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db, BackupManager backupManager)
throws Exception {
// Primary user ids
long myProfileId = helper.getDefaultUserSerial();
long oldProfileId = getDefaultProfileId(db);
// Delete all entries which do not belong to the main user
int itemsDeleted = db.delete(
Favorites.TABLE_NAME, "profileId != ?", new String[]{Long.toString(oldProfileId)});
LongSparseArray<Long> oldManagedProfileIds = getManagedProfileIds(db, oldProfileId);
LongSparseArray<Long> profileMapping = new LongSparseArray<>(oldManagedProfileIds.size()
+ 1);
// Build mapping of restored profile ids to their new profile ids.
profileMapping.put(oldProfileId, myProfileId);
for (int i = oldManagedProfileIds.size() - 1; i >= 0; --i) {
long oldManagedProfileId = oldManagedProfileIds.keyAt(i);
UserHandle user = getUserForAncestralSerialNumber(backupManager, oldManagedProfileId);
if (user != null) {
long newManagedProfileId = helper.getSerialNumberForUser(user);
profileMapping.put(oldManagedProfileId, newManagedProfileId);
}
}
// Delete all entries which do not belong to any restored profile(s).
int numProfiles = profileMapping.size();
String[] profileIds = new String[numProfiles];
profileIds[0] = Long.toString(oldProfileId);
StringBuilder whereClause = new StringBuilder("profileId != ?");
for (int i = profileMapping.size() - 1; i >= 1; --i) {
whereClause.append(" AND profileId != ?");
profileIds[i] = Long.toString(profileMapping.keyAt(i));
}
int itemsDeleted = db.delete(Favorites.TABLE_NAME, whereClause.toString(), profileIds);
if (itemsDeleted > 0) {
FileLog.d(TAG, itemsDeleted + " items belonging to a managed profile, were deleted");
FileLog.d(TAG, itemsDeleted + " items from unrestored user(s) were deleted");
}
// Mark all items as restored.
@@ -85,7 +114,7 @@ public class RestoreDbTask {
| (keepAllIcons ? ShortcutInfo.FLAG_RESTORE_STARTED : 0));
db.update(Favorites.TABLE_NAME, values, null, null);
// Mark widgets with appropriate restore flag
// Mark widgets with appropriate restore flag.
values.put(Favorites.RESTORED, LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
LauncherAppWidgetInfo.FLAG_UI_NOT_READY |
@@ -93,21 +122,46 @@ public class RestoreDbTask {
db.update(Favorites.TABLE_NAME, values, "itemType = ?",
new String[]{Integer.toString(Favorites.ITEM_TYPE_APPWIDGET)});
long myProfileId = helper.getDefaultUserSerial();
// Migrate ids. To avoid any overlap, we initially move conflicting ids to a temp location.
// Using Long.MIN_VALUE since profile ids can not be negative, so there will be no overlap.
final long tempLocationOffset = Long.MIN_VALUE;
SparseLongArray tempMigratedIds = new SparseLongArray(profileMapping.size());
int numTempMigrations = 0;
for (int i = profileMapping.size() - 1; i >= 0; --i) {
long oldId = profileMapping.keyAt(i);
long newId = profileMapping.valueAt(i);
if (oldId != newId) {
if (profileMapping.indexOfKey(newId) >= 0) {
tempMigratedIds.put(numTempMigrations, newId);
numTempMigrations++;
newId = tempLocationOffset + newId;
}
migrateProfileId(db, oldId, newId);
}
}
// Migrate ids from their temporary id to their actual final id.
for (int i = tempMigratedIds.size() - 1; i >= 0; --i) {
long newId = tempMigratedIds.valueAt(i);
migrateProfileId(db, tempLocationOffset + newId, newId);
}
if (myProfileId != oldProfileId) {
FileLog.d(TAG, "Changing primary user id from " + oldProfileId + " to " + myProfileId);
migrateProfileId(db, myProfileId);
changeDefaultColumn(db, myProfileId);
}
}
/**
* Updates profile id of all entries and changes the default value for the column.
* Updates profile id of all entries from {@param oldProfileId} to {@param newProfileId}.
*/
protected void migrateProfileId(SQLiteDatabase db, long newProfileId) {
protected void migrateProfileId(SQLiteDatabase db, long oldProfileId, long newProfileId) {
FileLog.d(TAG, "Changing profile user id from " + oldProfileId + " to " + newProfileId);
// Update existing entries.
ContentValues values = new ContentValues();
values.put(Favorites.PROFILE_ID, newProfileId);
db.update(Favorites.TABLE_NAME, values, null, null);
db.update(Favorites.TABLE_NAME, values, "profileId = ?",
new String[]{Long.toString(oldProfileId)});
// Change default value of the column.
db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
@@ -116,6 +170,43 @@ public class RestoreDbTask {
dropTable(db, "favorites_old");
}
/**
* Changes the default value for the column.
*/
protected void changeDefaultColumn(SQLiteDatabase db, long newProfileId) {
db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
Favorites.addTableToDb(db, newProfileId, false);
db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;");
dropTable(db, "favorites_old");
}
/**
* Returns a list of the managed profile id(s) used in the favorites table of the provided db.
*/
private LongSparseArray<Long> getManagedProfileIds(SQLiteDatabase db, long defaultProfileId) {
LongSparseArray<Long> ids = new LongSparseArray<>();
try (Cursor c = db.rawQuery("SELECT profileId from favorites WHERE profileId != ? "
+ "GROUP BY profileId", new String[] {Long.toString(defaultProfileId)})){
while (c.moveToNext()) {
ids.put(c.getLong(c.getColumnIndex(Favorites.PROFILE_ID)), null);
}
}
return ids;
}
/**
* Returns a UserHandle of a restored managed profile with the given serial number, or null
* if none found.
*/
private UserHandle getUserForAncestralSerialNumber(BackupManager backupManager,
long ancestralSerialNumber) {
if (Build.VERSION.SDK_INT < 29) {
return null;
}
return backupManager.getUserForAncestralSerialNumber(ancestralSerialNumber);
}
/**
* Returns the profile id used in the favorites table of the provided db.
*/
@@ -215,7 +215,7 @@ public class QsbContainerView extends FrameLayout {
}
public boolean isQsbEnabled() {
return FeatureFlags.QSB_ON_FIRST_SCREEN.get();
return FeatureFlags.QSB_ON_FIRST_SCREEN;
}
protected Bundle createBindOptions() {
@@ -41,8 +41,6 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener
private static final String TAG = "ConfigMonitor";
private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
private final Point mTmpPoint1 = new Point();
private final Point mTmpPoint2 = new Point();
@@ -78,11 +76,6 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener
// Listen for configuration change
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
// Listen for {@link OverlayManager} change
IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
filter.addDataScheme("package");
mContext.registerReceiver(this, filter);
// Listen for display manager change
mContext.getSystemService(DisplayManager.class)
.registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper()));
@@ -91,12 +84,6 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener
@Override
public void onReceive(Context context, Intent intent) {
Configuration config = context.getResources().getConfiguration();
// TODO: when overlay manager service encodes more information to the Uri such as category
// of the overlay, only listen to the ones that are of interest to launcher.
if (intent != null && ACTION_OVERLAY_CHANGED.equals(intent.getAction())) {
Log.d(TAG, "Overlay changed.");
notifyChange();
}
if (mFontScale != config.fontScale || mDensity != config.densityDpi) {
Log.d(TAG, "Configuration changed.");
notifyChange();
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.views;
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -50,12 +52,11 @@ import com.android.launcher3.folder.FolderShape;
import com.android.launcher3.graphics.ShiftedBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.DeepShortcutView;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
/**
* A view that is created to look like another view with the purpose of creating fluid animations.
*/
@@ -142,9 +143,6 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
setBackgroundDrawableBounds(bgScale);
mRevealAnimator.setCurrentFraction(shapeRevealProgress);
if (Float.compare(shapeRevealProgress, 1f) >= 0f) {
mRevealAnimator.end();
}
}
invalidate();
invalidateOutline();
@@ -159,6 +157,9 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
@Override
public void onAnimationEnd(Animator animator) {
if (mRevealAnimator != null) {
mRevealAnimator.end();
}
if (mEndRunnable != null) {
mEndRunnable.run();
}
@@ -197,8 +198,14 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
// Similar to DragView, we simply use the BubbleTextView icon here.
drawable = ((BubbleTextView) v).getIcon();
}
if (v instanceof ImageView && info instanceof SystemShortcut) {
drawable = ((ImageView) v).getDrawable();
if (info instanceof SystemShortcut) {
if (v instanceof ImageView) {
drawable = ((ImageView) v).getDrawable();
} else if (v instanceof DeepShortcutView) {
drawable = ((DeepShortcutView) v).getIconView().getBackground();
} else {
drawable = v.getBackground();
}
}
if (drawable == null) {
drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
@@ -18,8 +18,10 @@ package com.android.launcher3.views;
import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.graphics.RectF;
import android.text.TextUtils;
@@ -150,7 +152,7 @@ public class OptionsPopupView extends ArrowPopup
RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
ArrayList<OptionItem> options = new ArrayList<>();
int res = FeatureFlags.STYLE_WALLPAPER.get() ?
int res = FeatureFlags.STYLE_WALLPAPER.get() && existsStyleWallpapers(launcher) ?
R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
options.add(new OptionItem(res, R.drawable.ic_wallpaper,
ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker));
@@ -164,6 +166,14 @@ public class OptionsPopupView extends ArrowPopup
show(launcher, target, options);
}
private static boolean existsStyleWallpapers(Launcher launcher) {
Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
intent.setComponent(new ComponentName(launcher.getString(R.string.wallpaper_picker_package),
"com.android.customization.picker.CustomizationPickerActivity"));
ResolveInfo ri = launcher.getPackageManager().resolveActivity(intent, 0);
return ri != null;
}
public static boolean onWidgetsClicked(View view) {
return openWidgets(Launcher.getLauncher(view.getContext()));
}

Some files were not shown because too many files have changed in this diff Show More