Merge master@5406228 into git_qt-dev-plus-aosp.
Change-Id: I2450207dda942ef17ddeb6a40fef610e80796611 BUG: 129345239
This commit is contained in:
+2
-1
@@ -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
@@ -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
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
@@ -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.
|
||||
|
||||
@@ -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.
+11
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-3
@@ -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);
|
||||
|
||||
+2
-8
@@ -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;
|
||||
|
||||
+127
-95
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-8
@@ -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;
|
||||
|
||||
+30
-20
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+11
-10
@@ -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;
|
||||
}
|
||||
|
||||
+21
-18
@@ -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.
|
||||
*/
|
||||
|
||||
+108
-141
@@ -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);
|
||||
|
||||
+14
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
+21
-43
@@ -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,
|
||||
|
||||
+4
-40
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -55,10 +55,6 @@
|
||||
escaped form of '<'). [CHAR LIMIT=15] -->
|
||||
<string name="shorter_duration_less_than_one_minute">< 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
|
||||
|
||||
+8
-15
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,8 +114,6 @@ public class ItemInfo {
|
||||
|
||||
ItemInfo(ItemInfo info) {
|
||||
copyFrom(info);
|
||||
// tempdebug:
|
||||
LauncherModel.checkItemInfo(this);
|
||||
}
|
||||
|
||||
public void copyFrom(ItemInfo info) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user