Remove Launcher3GoIconRecents

Bug: 144129431
Test: Build and check that Launcher3QuickStepGo is used instead
Change-Id: Ib2a30c9e1e78de7792f34c3505372c8af1cb307d
This commit is contained in:
Peter Kalauskas 2019-12-11 11:06:50 -08:00
parent 4f37a5e268
commit 0842cf5f9d
44 changed files with 1 additions and 4459 deletions

View File

@ -242,53 +242,7 @@ LOCAL_PROGUARD_ENABLED := full
LOCAL_PACKAGE_NAME := Launcher3QuickStepGo
LOCAL_PRIVILEGED_MODULE := true
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep Launcher3GoIconRecents
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
LOCAL_FULL_LIBS_MANIFEST_FILES := \
$(LOCAL_PATH)/go/AndroidManifest.xml \
$(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
$(LOCAL_PATH)/AndroidManifest-common.xml
LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
include $(BUILD_PACKAGE)
#
# Build rule for Launcher3 Go app with quickstep and Go-specific
# version of recents for Android Go devices.
#
include $(CLEAR_VARS)
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLibLauncherWrapper launcherprotosnano
ifneq (,$(wildcard frameworks/base))
LOCAL_PRIVATE_PLATFORM_APIS := true
else
LOCAL_SDK_VERSION := system_current
LOCAL_MIN_SDK_VERSION := 26
endif
LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-java-files-under, quickstep/src) \
$(call all-java-files-under, go/src) \
$(call all-java-files-under, go/quickstep/src)
LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/quickstep/res \
$(LOCAL_PATH)/go/res \
$(LOCAL_PATH)/go/quickstep/res
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
LOCAL_PROGUARD_ENABLED := full
LOCAL_PACKAGE_NAME := Launcher3GoIconRecents
LOCAL_PRIVILEGED_MODULE := true
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3Go Launcher3QuickStep
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
LOCAL_FULL_LIBS_MANIFEST_FILES := \

View File

@ -1,22 +0,0 @@
<?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>

View File

@ -1,22 +0,0 @@
<?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="@android:color/darker_gray"/>
<corners android:radius="@dimen/task_thumbnail_corner_radius"/>
</shape>

View File

@ -1,23 +0,0 @@
<?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="@android:color/transparent"/>
<stroke android:color="@android:color/white" android:width="4px"/>
<corners android:radius="2dp"/>
</shape>

View File

@ -1,34 +0,0 @@
<?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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/clear_all_item_view"
android:layout_width="match_parent"
android:layout_height="@dimen/clear_all_item_view_height">
<Button
android:id="@+id/clear_all_button"
android:layout_width="@dimen/clear_all_button_width"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
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"
style="@style/TextTitle"/>
</FrameLayout>

View File

@ -1,30 +0,0 @@
<?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.fallback.GoRecentsActivityRootView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<include
android:id="@+id/overview_panel"
layout="@layout/overview_panel"
android:clipChildren="false"
android:clipToPadding="false"
android:outlineProvider="none"
android:theme="@style/HomeScreenElementTheme" />
</com.android.quickstep.fallback.GoRecentsActivityRootView>

View File

@ -1,41 +0,0 @@
<?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.IconRecentsView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipChildren="false">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recent_task_recycler_view"
android:layout_width="@dimen/recents_list_width"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:scrollbars="none"
android:clipToPadding="false"
android:clipChildren="false"/>
<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"
style="@style/TextTitle"
android:visibility="gone"/>
</com.android.quickstep.views.IconRecentsView>

View File

@ -1,21 +0,0 @@
<?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.
-->
<fragment android:name="com.android.quickstep.IconRecentsFragment"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/low_ram_recents_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@ -1,46 +0,0 @@
<?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="@dimen/task_item_height"
android:orientation="horizontal"
android:clipChildren="false">
<com.android.quickstep.views.TaskThumbnailIconView
android:id="@+id/task_icon_and_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="@dimen/task_thumbnail_icon_horiz_margin">
<ImageView
android:id="@+id/task_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/task_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.android.quickstep.views.TaskThumbnailIconView>
<TextView
android:id="@+id/task_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="24sp"
style="@style/TextTitle"/>
</com.android.quickstep.views.TaskItemView>

View File

@ -1,30 +0,0 @@
<?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="recents_list_width">480dp</dimen>
<dimen name="task_item_height">90dp</dimen>
<dimen name="task_item_top_margin">24dp</dimen>
<dimen name="task_thumbnail_icon_horiz_margin">24dp</dimen>
<dimen name="task_thumbnail_corner_radius">4dp</dimen>
<dimen name="clear_all_item_view_height">52dp</dimen>
<dimen name="clear_all_item_view_top_margin">28dp</dimen>
<dimen name="clear_all_item_view_bottom_margin">28dp</dimen>
<dimen name="clear_all_button_width">160dp</dimen>
</resources>

View File

@ -1,20 +0,0 @@
<?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>

View File

@ -1,30 +0,0 @@
<?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="recents_list_width">320dp</dimen>
<dimen name="task_item_height">60dp</dimen>
<dimen name="task_item_top_margin">16dp</dimen>
<dimen name="task_thumbnail_icon_horiz_margin">16dp</dimen>
<dimen name="task_thumbnail_corner_radius">3dp</dimen>
<dimen name="clear_all_item_view_height">36dp</dimen>
<dimen name="clear_all_item_view_top_margin">20dp</dimen>
<dimen name="clear_all_item_view_bottom_margin">20dp</dimen>
<dimen name="clear_all_button_width">106dp</dimen>
</resources>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Class overrides for Go version of launcher with Go recents. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_transition_manager_class" translatable="false">com.android.launcher3.GoLauncherAppTransitionManagerImpl</string>
<string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
<string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
</resources>

View File

@ -1,98 +0,0 @@
package com.android.launcher3;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.os.Handler;
import android.view.View;
import com.android.quickstep.views.IconRecentsView;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
* A {@link QuickstepAppTransitionManagerImpl} with recents-specific app transitions based off
* {@link com.android.quickstep.views.IconRecentsView}.
*/
public final class GoLauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
public GoLauncherAppTransitionManagerImpl(Context context) {
super(context);
}
@Override
protected boolean isLaunchingFromRecents(View v, RemoteAnimationTargetCompat[] targets) {
return mLauncher.getStateManager().getState().overviewUi;
}
@Override
RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
return new GoWallpaperOpenLauncherAnimationRunner(mHandler,
false /* startAtFrontOfQueue */, fromUnlock);
}
@Override
protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
boolean launcherClosing) {
// Stubbed. Recents launch animation will come from the recents view itself and will not
// use remote animations.
}
@Override
protected Runnable composeViewContentAnimator(AnimatorSet anim, float[] alphas, float[] trans) {
IconRecentsView overview = mLauncher.getOverviewPanel();
ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
CONTENT_ALPHA, alphas);
alpha.setDuration(CONTENT_ALPHA_DURATION);
alpha.setInterpolator(LINEAR);
anim.play(alpha);
ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
transY.setInterpolator(AGGRESSIVE_EASE);
transY.setDuration(CONTENT_TRANSLATION_DURATION);
anim.play(transY);
return mLauncher.getStateManager()::reapplyState;
}
/**
* Remote animation runner for animation from app to Launcher. For Go, when going to recents,
* we need to ensure that the recents view is ready for remote animation before starting.
*/
private final class GoWallpaperOpenLauncherAnimationRunner extends
WallpaperOpenLauncherAnimationRunner {
public GoWallpaperOpenLauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue,
boolean fromUnlock) {
super(handler, startAtFrontOfQueue, fromUnlock);
}
@Override
public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
AnimationResult result) {
boolean isGoingToRecents =
taskIsATargetWithMode(appTargets, mLauncher.getTaskId(), MODE_OPENING)
&& (mLauncher.getStateManager().getState() == LauncherState.OVERVIEW);
if (isGoingToRecents) {
IconRecentsView recentsView = mLauncher.getOverviewPanel();
if (!recentsView.isReadyForRemoteAnim()) {
recentsView.setOnReadyForRemoteAnimCallback(() ->
postAsyncCallback(mHandler, () -> onCreateAnimation(appTargets,
wallpaperTargets, result))
);
return;
}
}
super.onCreateAnimation(appTargets, wallpaperTargets, result);
}
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.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);
}
}

View File

@ -1,46 +0,0 @@
/**
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.uioverrides;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.SysUINavigationMode;
import java.util.ArrayList;
public class QuickstepLauncher extends BaseQuickstepLauncher {
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true;
@Override
public TouchController[] createTouchControllers() {
ArrayList<TouchController> list = new ArrayList<>();
list.add(getDragController());
if (getDeviceProfile().isVerticalBarLayout()) {
list.add(new LandscapeStatesTouchController(this));
list.add(new LandscapeEdgeSwipeController(this));
} else {
boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(this)
.getMode().hasGestures;
list.add(new PortraitStatesTouchController(this, allowDragToOverview));
}
return list.toArray(new TouchController[list.size()]);
}
}

View File

@ -1,44 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.uioverrides;
import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
import android.util.FloatProperty;
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}.
*/
public final class RecentsViewStateController extends
BaseRecentsViewStateController<IconRecentsView> {
public RecentsViewStateController(@NonNull Launcher launcher) {
super(launcher);
launcher.<IconRecentsView>getOverviewPanel().setRecentsToActivityHelper(
new LauncherRecentsToActivityHelper(launcher));
}
@Override
FloatProperty<IconRecentsView> getContentAlphaProperty() {
return CONTENT_ALPHA;
}
}

View File

@ -1,159 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.uioverrides.states;
import static android.view.View.VISIBLE;
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
import android.content.Context;
import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.views.IconRecentsView;
/**
* Definition for overview state
*/
public class OverviewState extends LauncherState {
// Scale recents takes before animating in
private static final float RECENTS_PREPARE_SCALE = 1.33f;
private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
| FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
public OverviewState(int id) {
this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
}
protected OverviewState(int id, int transitionDuration, int stateFlags) {
super(id, LauncherLogProto.ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
}
@Override
public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
return new ScaleAndTranslation(1f, 0f, 0f);
}
@Override
public void onStateEnabled(Launcher launcher) {
IconRecentsView recentsView = launcher.getOverviewPanel();
recentsView.onBeginTransitionToOverview();
recentsView.setShowStatusBarForegroundScrim(true);
// Request orientation be set to unspecified, letting the system decide the best
// orientation.
launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
}
@Override
public void onStateDisabled(Launcher launcher) {
IconRecentsView recentsView = launcher.getOverviewPanel();
recentsView.setShowStatusBarForegroundScrim(false);
}
@Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
return new PageAlphaProvider(DEACCEL_2) {
@Override
public float getPageAlpha(int pageIndex) {
return 0;
}
};
}
@Override
public int getVisibleElements(Launcher launcher) {
return NONE;
}
@Override
public float getWorkspaceScrimAlpha(Launcher launcher) {
return 0.5f;
}
@Override
public String getDescription(Launcher launcher) {
return launcher.getString(R.string.accessibility_desc_recent_apps);
}
@Override
public void onBackPressed(Launcher launcher) {
// TODO: Add logic to go back to task if coming from a currently running task.
super.onBackPressed(launcher);
}
public static float getDefaultSwipeHeight(Launcher launcher) {
return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
}
public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
}
@Override
public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
AnimatorSetBuilder builder) {
if (fromState == NORMAL && this == OVERVIEW) {
if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
builder.setInterpolator(ANIM_WORKSPACE_SCALE, ACCEL);
builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
} else {
builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
}
builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
View overview = launcher.getOverviewPanel();
if (overview.getVisibility() != VISIBLE) {
SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
}
}
}
public static OverviewState newBackgroundState(int id) {
return new OverviewState(id);
}
public static OverviewState newPeekState(int id) {
return new OverviewState(id);
}
public static OverviewState newSwitchState(int id) {
return new OverviewState(id);
}
}

View File

@ -1,70 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.userevent.nano.LauncherLogProto;
/**
* Touch controller for landscape mode.
*/
public final class LandscapeStatesTouchController extends PortraitStatesTouchController {
public LandscapeStatesTouchController(Launcher l) {
super(l, true /* allowDragToOverview */);
}
@Override
protected boolean canInterceptTouch(MotionEvent ev) {
if (mCurrentAnimation != null) {
// If we are already animating from a previous state, we can intercept.
return true;
}
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return false;
}
if (mLauncher.isInState(ALL_APPS)) {
// In all-apps only listen if the container cannot scroll itself
return mLauncher.getAppsView().shouldContainerScroll(ev);
} else if (mLauncher.isInState(NORMAL)) {
return true;
} else {
return false;
}
}
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
if (fromState == ALL_APPS && !isDragTowardPositive) {
return NORMAL;
} else if (isDragTowardPositive) {
return ALL_APPS;
}
return fromState;
}
@Override
protected int getLogContainerTypeForNormalState(MotionEvent ev) {
return LauncherLogProto.ContainerType.WORKSPACE;
}
}

View File

@ -1,65 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.uioverrides.touchcontrollers;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
import com.android.launcher3.util.PendingAnimation;
/**
* Helper class for {@link PortraitStatesTouchController} that determines swipeable regions and
* animations on the overview state that depend on the recents implementation.
*/
public final class PortraitOverviewStateTouchHelper {
public PortraitOverviewStateTouchHelper(Launcher launcher) {}
/**
* Whether or not {@link PortraitStatesTouchController} should intercept the touch when on the
* overview state.
*
* @param ev the motion event
* @return true if we should intercept the motion event
*/
boolean canInterceptTouch(MotionEvent ev) {
// Go does not support swiping to all-apps from recents.
return false;
}
/**
* Whether or not swiping down to leave overview state should return to the currently running
* task app.
*
* @return true if going back should take the user to the currently running task
*/
boolean shouldSwipeDownReturnToApp() {
// Go does not support swiping tasks down to launch tasks from recents.
return false;
}
/**
* Create the animation for going from overview to the task app via swiping.
*
* @param duration how long the animation should be
* @return the animation
*/
PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
// Go does not support swiping tasks down to launch tasks from recents.
return null;
}
}

View File

@ -1,203 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.quickstep.views.IconRecentsView.REMOTE_APP_TO_OVERVIEW_DURATION;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
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.app.ActivityOptions;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherAnimationRunner;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.views.IconRecentsView;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
* Provider for the atomic remote window animation from the app to the overview.
*
* @param <T> activity that contains the overview
*/
final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> implements
RemoteAnimationProvider {
private static final String TAG = "AppToOverviewAnimationProvider";
private final BaseActivityInterface<T> mActivityInterface;
private final int mTargetTaskId;
private IconRecentsView mRecentsView;
private AppToOverviewAnimationListener mAnimationReadyListener;
AppToOverviewAnimationProvider(BaseActivityInterface<T> activityInterface, int targetTaskId) {
mActivityInterface = activityInterface;
mTargetTaskId = targetTaskId;
}
/**
* Set listener to various points in the animation preparing to animate.
*
* @param listener listener
*/
void setAnimationListener(AppToOverviewAnimationListener listener) {
mAnimationReadyListener = listener;
}
/**
* Callback for when the activity is ready/initialized.
*
* @param wasVisible true if it was visible before
*/
boolean onActivityReady(Boolean wasVisible) {
T activity = mActivityInterface.getCreatedActivity();
if (mAnimationReadyListener != null) {
mAnimationReadyListener.onActivityReady(activity);
}
BaseActivityInterface.AnimationFactory factory =
mActivityInterface.prepareRecentsUI(wasVisible,
false /* animate activity */, (controller) -> {
controller.dispatchOnStart();
ValueAnimator anim = controller.getAnimationPlayer()
.setDuration(getRecentsLaunchDuration());
anim.setInterpolator(FAST_OUT_SLOW_IN);
anim.start();
});
factory.onRemoteAnimationReceived(null);
factory.createActivityInterface(getRecentsLaunchDuration());
mRecentsView = activity.getOverviewPanel();
return false;
}
/**
* Create remote window animation from the currently running app to the overview panel. Should
* be called after {@link #onActivityReady}.
*
* @param appTargets the target apps
* @return animation from app to overview
*/
@Override
public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets) {
if (mAnimationReadyListener != null) {
mAnimationReadyListener.onWindowAnimationCreated();
}
AnimatorSet anim = new AnimatorSet();
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;
}
RemoteAnimationTargets targets =
new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_CLOSING);
mRecentsView.setTransitionedFromApp(!targets.isAnimatingHome());
RemoteAnimationTargetCompat recentsTarget = null;
RemoteAnimationTargetCompat closingAppTarget = null;
for (RemoteAnimationTargetCompat target : appTargets) {
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;
}
if (closingAppTarget.activityType == ACTIVITY_TYPE_HOME) {
mRecentsView.playRemoteHomeToRecentsAnimation(anim, closingAppTarget, recentsTarget);
} else {
mRecentsView.playRemoteAppToRecentsAnimation(anim, closingAppTarget, recentsTarget);
}
return anim;
}
@Override
public ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
false /* startAtFrontOfQueue */) {
@Override
public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
AnimationResult result) {
IconRecentsView recentsView = mRecentsView;
if (!recentsView.isReadyForRemoteAnim()) {
recentsView.setOnReadyForRemoteAnimCallback(() -> postAsyncCallback(handler,
() -> onCreateAnimation(appTargets, wallpaperTargets, result))
);
return;
}
result.setAnimation(createWindowAnimation(appTargets, wallpaperTargets), context);
}
};
return ActivityOptionsCompat.makeRemoteAnimation(
new RemoteAnimationAdapterCompat(runner, duration,
0 /* statusBarTransitionDelay */));
}
/**
* Get duration of animation from app to overview.
*
* @return duration of animation
*/
long getRecentsLaunchDuration() {
return REMOTE_APP_TO_OVERVIEW_DURATION;
}
/**
* Listener for various points in the app to overview animation preparing to animate.
*/
interface AppToOverviewAnimationListener {
/**
* Logic for when activity we're animating to is ready
*
* @param activity activity to animate to
*/
void onActivityReady(BaseDraggingActivity activity);
/**
* Logic for when we've created the app to recents animation.
*/
void onWindowAnimationCreated();
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
/**
* Holder for clear all button view in task recycler view.
*/
final class ClearAllHolder extends ViewHolder {
public ClearAllHolder(@NonNull View itemView) {
super(itemView);
}
}

View File

@ -1,281 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static android.view.View.ALPHA;
import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
import static com.android.quickstep.views.TaskItemView.CONTENT_TRANSITION_PROGRESS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import androidx.recyclerview.widget.SimpleItemAnimator;
import com.android.quickstep.views.TaskItemView;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* An item animator that is only set and used for the transition from the empty loading UI to
* the filled task content UI. The animation starts from the bottom to top, changing all valid
* empty item views to be filled and removing all extra empty views.
*/
public final class ContentFillItemAnimator extends SimpleItemAnimator {
private static final class PendingAnimation {
ViewHolder viewHolder;
int animType;
PendingAnimation(ViewHolder vh, int type) {
viewHolder = vh;
animType = type;
}
}
private static final int ANIM_TYPE_REMOVE = 0;
private static final int ANIM_TYPE_CHANGE = 1;
private static final int ITEM_BETWEEN_DELAY = 40;
private static final int ITEM_CHANGE_DURATION = 150;
private static final int ITEM_REMOVE_DURATION = 150;
/**
* Animations that have been registered to occur together at the next call of
* {@link #runPendingAnimations()} but have not started.
*/
private final ArrayList<PendingAnimation> mPendingAnims = new ArrayList<>();
/**
* Animations that have started and are running.
*/
private final ArrayList<ObjectAnimator> mRunningAnims = new ArrayList<>();
private Runnable mOnFinishRunnable;
/**
* Set runnable to run after the content fill animation is fully completed.
*
* @param runnable runnable to run on end
*/
public void setOnAnimationFinishedRunnable(Runnable runnable) {
mOnFinishRunnable = runnable;
}
@Override
public void setChangeDuration(long changeDuration) {
throw new UnsupportedOperationException("Cascading item animator cannot have animation "
+ "duration changed.");
}
@Override
public void setRemoveDuration(long removeDuration) {
throw new UnsupportedOperationException("Cascading item animator cannot have animation "
+ "duration changed.");
}
@Override
public boolean animateRemove(ViewHolder holder) {
PendingAnimation pendAnim = new PendingAnimation(holder, ANIM_TYPE_REMOVE);
mPendingAnims.add(pendAnim);
return true;
}
private void animateRemoveImpl(ViewHolder holder, long startDelay) {
final View view = holder.itemView;
if (holder.itemView.getAlpha() == 0) {
// View is already visually removed. We can just get rid of it now.
view.setAlpha(1.0f);
dispatchRemoveFinished(holder);
dispatchFinishedWhenDone();
return;
}
final ObjectAnimator anim = ObjectAnimator.ofFloat(
holder.itemView, ALPHA, holder.itemView.getAlpha(), 0.0f);
anim.setDuration(ITEM_REMOVE_DURATION).setStartDelay(startDelay);
anim.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(Animator animation) {
view.setAlpha(1);
dispatchRemoveFinished(holder);
mRunningAnims.remove(anim);
dispatchFinishedWhenDone();
}
}
);
anim.start();
mRunningAnims.add(anim);
}
@Override
public boolean animateAdd(ViewHolder holder) {
dispatchAddFinished(holder);
return false;
}
@Override
public boolean animateMove(ViewHolder holder, int fromX, int fromY, int toX,
int toY) {
dispatchMoveFinished(holder);
return false;
}
@Override
public boolean animateChange(ViewHolder oldHolder,
ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
// Only support changes where the holders are the same
if (oldHolder == newHolder) {
PendingAnimation pendAnim = new PendingAnimation(oldHolder, ANIM_TYPE_CHANGE);
mPendingAnims.add(pendAnim);
return true;
}
dispatchChangeFinished(oldHolder, true /* oldItem */);
dispatchChangeFinished(newHolder, false /* oldItem */);
return false;
}
private void animateChangeImpl(ViewHolder viewHolder, long startDelay) {
TaskItemView itemView = (TaskItemView) viewHolder.itemView;
if (itemView.getAlpha() == 0) {
// View is still not visible, so we can finish the change immediately.
CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f);
dispatchChangeFinished(viewHolder, true /* oldItem */);
dispatchFinishedWhenDone();
return;
}
final ObjectAnimator anim =
ObjectAnimator.ofFloat(itemView, CONTENT_TRANSITION_PROGRESS, 0.0f, 1.0f);
anim.setDuration(ITEM_CHANGE_DURATION).setStartDelay(startDelay);
anim.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
dispatchChangeStarting(viewHolder, true /* oldItem */);
}
@Override
public void onAnimationEnd(Animator animation) {
CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f);
dispatchChangeFinished(viewHolder, true /* oldItem */);
mRunningAnims.remove(anim);
dispatchFinishedWhenDone();
}
}
);
anim.start();
mRunningAnims.add(anim);
}
@Override
public void runPendingAnimations() {
// Run animations bottom to top.
mPendingAnims.sort(Comparator.comparingInt(o -> -o.viewHolder.itemView.getBottom()));
int delay = 0;
while (!mPendingAnims.isEmpty()) {
PendingAnimation curAnim = mPendingAnims.remove(0);
ViewHolder vh = curAnim.viewHolder;
switch (curAnim.animType) {
case ANIM_TYPE_REMOVE:
animateRemoveImpl(vh, delay);
break;
case ANIM_TYPE_CHANGE:
animateChangeImpl(vh, delay);
break;
default:
break;
}
delay += ITEM_BETWEEN_DELAY;
}
}
@Override
public void endAnimation(@NonNull ViewHolder item) {
for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
endPendingAnimation(mPendingAnims.get(i));
mPendingAnims.remove(i);
}
dispatchFinishedWhenDone();
}
@Override
public void endAnimations() {
if (!isRunning()) {
return;
}
for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
endPendingAnimation(mPendingAnims.get(i));
mPendingAnims.remove(i);
}
for (int i = mRunningAnims.size() - 1; i >= 0; i--) {
ObjectAnimator anim = mRunningAnims.get(i);
// This calls the on end animation callback which will set values to their end target.
anim.cancel();
}
dispatchFinishedWhenDone();
}
private void endPendingAnimation(PendingAnimation pendAnim) {
ViewHolder item = pendAnim.viewHolder;
switch (pendAnim.animType) {
case ANIM_TYPE_REMOVE:
item.itemView.setAlpha(1.0f);
dispatchRemoveFinished(item);
break;
case ANIM_TYPE_CHANGE:
CONTENT_TRANSITION_PROGRESS.set(item.itemView, 1.0f);
dispatchChangeFinished(item, true /* oldItem */);
break;
default:
break;
}
}
@Override
public boolean isRunning() {
return !mPendingAnims.isEmpty() || !mRunningAnims.isEmpty();
}
@Override
public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
@NonNull List<Object> payloads) {
if (!payloads.isEmpty()
&& (int) payloads.get(0) == CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT) {
return true;
}
return super.canReuseUpdatedViewHolder(viewHolder, payloads);
}
private void dispatchFinishedWhenDone() {
if (!isRunning()) {
dispatchAnimationsFinished();
if (mOnFinishRunnable != null) {
mOnFinishRunnable.run();
}
}
}
}

View File

@ -1,121 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.graphics.Rect;
import androidx.annotation.Nullable;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.views.IconRecentsView;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* {@link BaseActivityInterface} for recents when the default launcher is different than the
* currently running one and apps should interact with the {@link RecentsActivity} as opposed
* to the in-launcher one.
*/
public final class FallbackActivityInterface extends
GoActivityInterface<RecentsActivity> {
public FallbackActivityInterface() { }
@Override
public AnimationFactory prepareRecentsUI(boolean activityVisible,
boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
if (activityVisible) {
return (transitionLength) -> { };
}
RecentsActivity activity = getCreatedActivity();
IconRecentsView rv = activity.getOverviewPanel();
rv.setUsingRemoteAnimation(true);
rv.setAlpha(0);
return new AnimationFactory() {
boolean isAnimatingToRecents = false;
@Override
public void onRemoteAnimationReceived(RemoteAnimationTargets targets) {
isAnimatingToRecents = targets != null && targets.isAnimatingHome();
if (!isAnimatingToRecents) {
rv.setAlpha(1);
}
createActivityInterface(getSwipeUpDestinationAndLength(
activity.getDeviceProfile(), activity, new Rect()));
}
@Override
public void createActivityInterface(long transitionLength) {
if (!isAnimatingToRecents) {
return;
}
ObjectAnimator anim = ObjectAnimator.ofFloat(rv, CONTENT_ALPHA, 0, 1);
anim.setDuration(transitionLength).setInterpolator(LINEAR);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(anim);
callback.accept(AnimatorPlaybackController.wrap(animatorSet, transitionLength));
}
};
}
@Override
public ActivityInitListener createActivityInitListener(
Predicate<Boolean> onInitListener) {
return new ActivityInitListener<>((activity, alreadyOnHome) ->
onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER);
}
@Nullable
@Override
public RecentsActivity getCreatedActivity() {
return RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
}
@Nullable
@Override
public IconRecentsView getVisibleRecentsView() {
RecentsActivity activity = getCreatedActivity();
if (activity != null && activity.hasWindowFocus()) {
return activity.getOverviewPanel();
}
return null;
}
@Override
public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
return false;
}
@Override
public int getContainerType() {
return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
}
@Override
public void onLaunchTaskSuccess() { }
}

View File

@ -1,34 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
/**
* {@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();
}
}

View File

@ -1,69 +0,0 @@
package com.android.quickstep;
import android.content.Context;
import android.graphics.Rect;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
* Base activity control helper for Go that stubs out most of the functionality that is not needed
* for Go.
*
* @param <T> activity that contains the overview
*/
public abstract class GoActivityInterface<T extends BaseDraggingActivity> implements
BaseActivityInterface<T> {
@Override
public void onTransitionCancelled(boolean activityVisible) {
// Go transitions to overview are all atomic.
}
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
// TODO Implement outRect depending on where the task should animate to.
// Go does not support swipe up gesture.
return 0;
}
@Override
public void onSwipeUpToRecentsComplete() {
// Go does not support swipe up gesture.
}
@Override
public void onAssistantVisibilityChanged(float visibility) {
// Go does not support assistant visibility transitions.
}
@Override
public HomeAnimationFactory prepareHomeUI() {
// Go does not support gestures from app to home.
return null;
}
@Override
public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
// Go does not support gestures to overview.
return null;
}
@Override
public boolean shouldMinimizeSplitScreen() {
// Go does not support split screen.
return true;
}
@Override
public boolean isInLiveTileMode() {
// Go does not support live tiles.
return false;
}
@Override
public void onLaunchTaskFailed() {
// Go does not support gestures from one task to another.
}
}

View File

@ -1,37 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
public class IconRecentsFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.icon_recents_root_view, container, false);
}
}

View File

@ -1,112 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static com.android.launcher3.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.userevent.nano.LauncherLogProto;
import com.android.quickstep.views.IconRecentsView;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* {@link BaseActivityInterface} for the in-launcher recents.
* TODO: Implement the app to overview animation functionality
*/
public final class LauncherActivityInterface extends GoActivityInterface<Launcher> {
@Override
public AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity,
Consumer<AnimatorPlaybackController> callback) {
Launcher launcher = getCreatedActivity();
LauncherState fromState = launcher.getStateManager().getState();
launcher.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(true);
//TODO: Implement this based off where the recents view needs to be for app => recents anim.
return new AnimationFactory() {
public void createActivityInterface(long transitionLength) {
callback.accept(launcher.getStateManager().createAnimationToNewWorkspace(
fromState, OVERVIEW, transitionLength));
}
@Override
public void onTransitionCancelled() {}
};
}
@Override
public LauncherInitListener createActivityInitListener(Predicate<Boolean> onInitListener) {
return new LauncherInitListener((activity, alreadyOnHome) ->
onInitListener.test(alreadyOnHome));
}
@Override
public Launcher getCreatedActivity() {
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app == null) {
return null;
}
return (Launcher) app.getModel().getCallback();
}
private Launcher getVisibleLauncher() {
Launcher launcher = getCreatedActivity();
return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
launcher : null;
}
@Override
public IconRecentsView getVisibleRecentsView() {
Launcher launcher = getVisibleLauncher();
return launcher != null && launcher.getStateManager().getState().overviewUi
? launcher.getOverviewPanel() : null;
}
@Override
public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
Launcher launcher = getVisibleLauncher();
if (launcher == null) {
return false;
}
launcher.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(false);
launcher.getUserEventDispatcher().logActionCommand(
LauncherLogProto.Action.Command.RECENTS_BUTTON,
getContainerType(),
LauncherLogProto.ContainerType.TASKSWITCHER);
launcher.getStateManager().goToState(OVERVIEW,
launcher.getStateManager().shouldAnimateStateChange(), onCompleteCallback);
return true;
}
@Override
public int getContainerType() {
final Launcher launcher = getVisibleLauncher();
return launcher != null ? launcher.getStateManager().getState().containerType
: LauncherLogProto.ContainerType.APP;
}
@Override
public void onLaunchTaskSuccess() {
Launcher launcher = getCreatedActivity();
launcher.getStateManager().moveToRestState();
}
}

View File

@ -1,180 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
import android.view.ViewConfiguration;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.AppToOverviewAnimationProvider.AppToOverviewAnimationListener;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.views.IconRecentsView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.LatencyTrackerCompat;
/**
* Helper class to handle various atomic commands for switching between Overview.
*/
@TargetApi(Build.VERSION_CODES.P)
public class OverviewCommandHelper {
private final Context mContext;
private final RecentsAnimationDeviceState mDeviceState;
private final RecentsModel mRecentsModel;
private final OverviewComponentObserver mOverviewComponentObserver;
private long mLastToggleTime;
public OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState,
OverviewComponentObserver observer) {
mContext = context;
mDeviceState = deviceState;
mRecentsModel = RecentsModel.INSTANCE.get(mContext);
mOverviewComponentObserver = observer;
}
public void onOverviewToggle() {
// If currently screen pinning, do not enter overview
if (mDeviceState.isScreenPinningActive()) {
return;
}
ActivityManagerWrapper.getInstance()
.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
}
public void onOverviewShown(boolean triggeredFromAltTab) {
MAIN_EXECUTOR.execute(new ShowRecentsCommand());
}
public void onOverviewHidden() {
MAIN_EXECUTOR.execute(new HideRecentsCommand());
}
public void onTip(int actionType, int viewType) {
MAIN_EXECUTOR.execute(() ->
UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
}
private class ShowRecentsCommand extends RecentsActivityCommand {
@Override
protected boolean handleCommand(long elapsedTime) {
return mHelper.getVisibleRecentsView() != null;
}
}
private class HideRecentsCommand extends RecentsActivityCommand {
@Override
protected boolean handleCommand(long elapsedTime) {
IconRecentsView recents = (IconRecentsView) mHelper.getVisibleRecentsView();
if (recents == null) {
return false;
}
recents.handleOverviewCommand();
return true;
}
}
private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
protected final BaseActivityInterface<T> mHelper;
private final long mCreateTime;
private final long mToggleClickedTime = SystemClock.uptimeMillis();
private boolean mUserEventLogged;
private ActivityInitListener<T> mListener;
public RecentsActivityCommand() {
mHelper = mOverviewComponentObserver.getActivityInterface();
mCreateTime = SystemClock.elapsedRealtime();
// Preload the plan
mRecentsModel.getTasks(null);
}
@Override
public void run() {
long elapsedTime = mCreateTime - mLastToggleTime;
mLastToggleTime = mCreateTime;
if (handleCommand(elapsedTime)) {
// Command already handled.
return;
}
if (mHelper.switchToRecentsIfVisible(null /* onCompleteCallback */)) {
// If successfully switched, then return
return;
}
AppToOverviewAnimationProvider<T> provider =
new AppToOverviewAnimationProvider<>(mHelper, RecentsModel.getRunningTaskId());
provider.setAnimationListener(
new AppToOverviewAnimationListener() {
@Override
public void onActivityReady(BaseDraggingActivity activity) {
if (!mUserEventLogged) {
activity.getUserEventDispatcher().logActionCommand(
LauncherLogProto.Action.Command.RECENTS_BUTTON,
mHelper.getContainerType(),
LauncherLogProto.ContainerType.TASKSWITCHER);
mUserEventLogged = true;
}
}
@Override
public void onWindowAnimationCreated() {
if (LatencyTrackerCompat.isEnabled(mContext)) {
LatencyTrackerCompat.logToggleRecents(
(int) (SystemClock.uptimeMillis() - mToggleClickedTime));
}
mListener.unregister();
}
});
// Otherwise, start overview.
mListener = mHelper.createActivityInitListener(provider::onActivityReady);
mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
provider, mContext, MAIN_EXECUTOR.getHandler(),
provider.getRecentsLaunchDuration());
}
protected boolean handleCommand(long elapsedTime) {
IconRecentsView recents = mHelper.getVisibleRecentsView();
if (recents != null) {
recents.handleOverviewCommand();
return true;
} else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
// The user tried to launch back into overview too quickly, either after
// launching an app, or before overview has actually shown, just ignore for now
return true;
}
return false;
}
}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.app.ActivityOptions;
import android.view.View;
import com.android.launcher3.R;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.fallback.GoRecentsActivityRootView;
import com.android.quickstep.views.IconRecentsView;
/**
* A recents activity that displays recent tasks with an icon and small snapshot.
*/
public final class RecentsActivity extends BaseRecentsActivity {
private GoRecentsActivityRootView mRecentsRootView;
private IconRecentsView mIconRecentsView;
@Override
protected void initViews() {
setContentView(R.layout.fallback_recents_activity);
mRecentsRootView = findViewById(R.id.drag_layer);
mIconRecentsView = findViewById(R.id.overview_panel);
mIconRecentsView.setRecentsToActivityHelper(new FallbackRecentsToActivityHelper(this));
mIconRecentsView.setShowStatusBarForegroundScrim(true);
}
@Override
protected void reapplyUi() {
// No-op. Insets are automatically re-applied in the root view.
}
@Override
public BaseDragLayer getDragLayer() {
return mRecentsRootView;
}
@Override
public View getRootView() {
return mRecentsRootView;
}
@Override
public <T extends View> T getOverviewPanel() {
return (T) mIconRecentsView;
}
@Override
public ActivityOptions getActivityLaunchOptions(View v) {
// Stubbed. Recents launch animation will come from the recents view itself.
return null;
}
@Override
protected void onResume() {
mIconRecentsView.onBeginTransitionToOverview();
super.onResume();
}
}

View File

@ -1,29 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
/**
* 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();
}

View File

@ -1,110 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION;
import static com.android.quickstep.TaskUtils.getLaunchComponentKeyForTask;
import android.app.ActivityOptions;
import android.view.View;
import androidx.annotation.NonNull;
import com.android.launcher3.logging.StatsLogManager;
import com.android.quickstep.views.TaskItemView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
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;
private final StatsLogManager mStatsLogManager;
public TaskActionController(TaskListLoader loader, TaskAdapter adapter,
StatsLogManager logManager) {
mLoader = loader;
mAdapter = adapter;
mStatsLogManager = logManager;
}
/**
* Launch the task associated with the task holder, animating into the app from the task view.
*
* @param viewHolder the task view holder to launch
*/
public void launchTaskFromView(@NonNull TaskHolder viewHolder) {
if (!viewHolder.getTask().isPresent()) {
return;
}
TaskItemView itemView = (TaskItemView) (viewHolder.itemView);
View v = itemView.getThumbnailView();
int left = 0;
int top = 0;
int width = v.getMeasuredWidth();
int height = v.getMeasuredHeight();
TaskKey key = viewHolder.getTask().get().key;
ActivityOptions opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts,
null /* resultCallback */, null /* resultCallbackHandler */);
mStatsLogManager.logTaskLaunch(null /* view */, getLaunchComponentKeyForTask(key));
}
/**
* Launch the task directly with a basic animation.
*
* @param task the task to launch
*/
public void launchTask(@NonNull Task task) {
ActivityOptions opts = ActivityOptions.makeBasic();
ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
null /* resultCallback */, null /* resultCallbackHandler */);
mStatsLogManager.logTaskLaunch(null /* view */, getLaunchComponentKeyForTask(task.key));
}
/**
* 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) {
if (!viewHolder.getTask().isPresent()) {
return;
}
int position = viewHolder.getAdapterPosition();
Task task = viewHolder.getTask().get();
ActivityManagerWrapper.getInstance().removeTask(task.key.id);
mLoader.removeTask(task);
mAdapter.notifyItemRemoved(position);
// TODO(b/131840601): Add logging point for removal.
}
/**
* Clears all tasks and updates the model and view.
*/
public void clearAllTasks() {
int count = mAdapter.getItemCount();
ActivityManagerWrapper.getInstance().removeAllRecentTasks();
mLoader.clearAllTasks();
mAdapter.notifyItemRangeRemoved(TASKS_START_POSITION /* positionStart */, count);
}
}

View File

@ -1,179 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.launcher3.R;
import com.android.quickstep.views.TaskItemView;
import com.android.systemui.shared.recents.model.Task;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
* appropriate {@link Task} from the recents task list.
*/
public final class TaskAdapter extends Adapter<ViewHolder> {
public static final int CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT = 0;
public static final int MAX_TASKS_TO_DISPLAY = 6;
public static final int TASKS_START_POSITION = 1;
public static final int ITEM_TYPE_TASK = 0;
public static final int ITEM_TYPE_CLEAR_ALL = 1;
private static final String TAG = "TaskAdapter";
private final TaskListLoader mLoader;
private TaskActionController mTaskActionController;
private OnClickListener mClearAllListener;
private boolean mIsShowingLoadingUi;
public TaskAdapter(@NonNull TaskListLoader loader) {
mLoader = loader;
}
public void setActionController(TaskActionController taskActionController) {
mTaskActionController = taskActionController;
}
public void setOnClearAllClickListener(OnClickListener listener) {
mClearAllListener = listener;
}
/**
* Sets all positions in the task adapter to loading views, binding new views if necessary.
* This changes the task adapter's view of the data, so the appropriate notify events should be
* called in addition to this method to reflect the changes.
*
* @param isShowingLoadingUi true to bind loading task views to all positions, false to return
* to the real data
*/
public void setIsShowingLoadingUi(boolean isShowingLoadingUi) {
mIsShowingLoadingUi = isShowingLoadingUi;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case ITEM_TYPE_TASK:
TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.task_item_view, parent, false);
TaskHolder taskHolder = new TaskHolder(itemView);
itemView.setOnClickListener(
view -> mTaskActionController.launchTaskFromView(taskHolder));
return taskHolder;
case ITEM_TYPE_CLEAR_ALL:
View clearView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.clear_all_button, parent, false);
ClearAllHolder clearAllHolder = new ClearAllHolder(clearView);
Button clearViewButton = clearView.findViewById(R.id.clear_all_button);
clearViewButton.setOnClickListener(mClearAllListener);
return clearAllHolder;
default:
throw new IllegalArgumentException("No known holder for item type: " + viewType);
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
onBindViewHolderInternal(holder, position, false /* willAnimate */);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position,
@NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads);
return;
}
int changeType = (int) payloads.get(0);
if (changeType == CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT) {
// Bind in preparation for animation
onBindViewHolderInternal(holder, position, true /* willAnimate */);
} else {
throw new IllegalArgumentException("Payload content is not a valid change event type: "
+ changeType);
}
}
private void onBindViewHolderInternal(@NonNull ViewHolder holder, int position,
boolean willAnimate) {
int itemType = getItemViewType(position);
switch (itemType) {
case ITEM_TYPE_TASK:
TaskHolder taskHolder = (TaskHolder) holder;
if (mIsShowingLoadingUi) {
taskHolder.bindEmptyUi();
return;
}
List<Task> tasks = mLoader.getCurrentTaskList();
int taskPos = position - TASKS_START_POSITION;
if (taskPos >= tasks.size()) {
// Task list has updated.
return;
}
Task task = tasks.get(taskPos);
taskHolder.bindTask(task, willAnimate /* willAnimate */);
mLoader.loadTaskIconAndLabel(task, () -> {
// Ensure holder still has the same task.
if (Objects.equals(Optional.of(task), taskHolder.getTask())) {
taskHolder.getTaskItemView().setIcon(task.icon);
taskHolder.getTaskItemView().setLabel(task.titleDescription);
}
});
mLoader.loadTaskThumbnail(task, () -> {
if (Objects.equals(Optional.of(task), taskHolder.getTask())) {
taskHolder.getTaskItemView().setThumbnail(task.thumbnail);
}
});
break;
case ITEM_TYPE_CLEAR_ALL:
// Nothing to bind.
break;
default:
throw new IllegalArgumentException("No known holder for item type: " + itemType);
}
}
@Override
public int getItemViewType(int position) {
// Bottom is always clear all button.
return (position == 0) ? ITEM_TYPE_CLEAR_ALL : ITEM_TYPE_TASK;
}
@Override
public int getItemCount() {
int itemCount = TASKS_START_POSITION;
if (mIsShowingLoadingUi) {
// Show loading version of all items.
itemCount += MAX_TASKS_TO_DISPLAY;
} else {
itemCount += Math.min(mLoader.getCurrentTaskList().size(), MAX_TASKS_TO_DISPLAY);
}
return itemCount;
}
}

View File

@ -1,85 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.quickstep.views.TaskItemView;
import com.android.systemui.shared.recents.model.Task;
import java.util.Optional;
/**
* A recycler view holder that holds the task view and binds {@link Task} content (app title, icon,
* etc.) to the view.
*/
public final class TaskHolder extends ViewHolder {
private final TaskItemView mTaskItemView;
private Task mTask;
public TaskHolder(TaskItemView itemView) {
super(itemView);
mTaskItemView = itemView;
}
public TaskItemView getTaskItemView() {
return mTaskItemView;
}
/**
* Bind the task model to the holder. This will take the current task content in the task
* object (i.e. icon, thumbnail, label) and either apply the content immediately or simply bind
* the content to animate to at a later time. If the task does not have all its content loaded,
* the view will prepare appropriate default placeholders and it is the callers responsibility
* to change them at a later time.
*
* Regardless of whether it is animating, input handlers will be bound immediately (see
* {@link TaskActionController}).
*
* @param task the task to bind to the view
* @param willAnimate true if UI should animate in later, false if it should apply immediately
*/
public void bindTask(@NonNull Task task, boolean willAnimate) {
mTask = task;
if (willAnimate) {
mTaskItemView.startContentAnimation(task.icon, task.thumbnail, task.titleDescription);
} else {
mTaskItemView.setIcon(task.icon);
mTaskItemView.setThumbnail(task.thumbnail);
mTaskItemView.setLabel(task.titleDescription);
}
}
/**
* Bind a generic empty UI to the holder to make it clear that the item is loading/unbound and
* should not be expected to react to user input.
*/
public void bindEmptyUi() {
mTask = null;
mTaskItemView.resetToEmptyUi();
}
/**
* Gets the task currently bound to this view. May be null if task holder is in a loading state.
*
* @return the current task
*/
public Optional<Task> getTask() {
return Optional.ofNullable(mTask);
}
}

View File

@ -1,140 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.content.Context;
import androidx.annotation.Nullable;
import com.android.systemui.shared.recents.model.Task;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/**
* This class is responsible for maintaining the list of tasks and the task content. The list must
* be updated explicitly with {@link #loadTaskList} whenever the list needs to be
* up-to-date.
*/
public final class TaskListLoader {
private final RecentsModel mRecentsModel;
private ArrayList<Task> mTaskList = new ArrayList<>();
private int mTaskListChangeId;
public TaskListLoader(Context context) {
mRecentsModel = RecentsModel.INSTANCE.get(context);
}
/**
* Returns the current task list as of the last completed load (see {@link #loadTaskList}) as a
* read-only list. This list of tasks is not guaranteed to have all content loaded.
*
* @return the current list of tasks
*/
public List<Task> getCurrentTaskList() {
return Collections.unmodifiableList(mTaskList);
}
/**
* Whether or not the loader needs to load data to be up to date. This can return true if the
* task list is already up to date OR there is already a load in progress for the task list to
* become up to date.
*
* @return true if already up to date or load in progress, false otherwise
*/
public boolean needsToLoad() {
return !mRecentsModel.isTaskListValid(mTaskListChangeId);
}
/**
* Fetches the most recent tasks and updates the task list asynchronously. This call does not
* provide guarantees the task content (icon, thumbnail, label) are loaded but will fill in
* what it has. May run the callback immediately if there have been no changes in the task
* list since the start of the last load.
*
* @param onLoadedCallback callback to run when task list is loaded
*/
public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onLoadedCallback) {
if (!needsToLoad()) {
if (onLoadedCallback != null) {
onLoadedCallback.accept(mTaskList);
}
return;
}
// TODO: Look into error checking / more robust handling for when things go wrong.
mTaskListChangeId = mRecentsModel.getTasks(loadedTasks -> {
ArrayList<Task> tasks = new ArrayList<>(loadedTasks);
// Reverse tasks to put most recent at the bottom of the view
Collections.reverse(tasks);
// Load task content
for (Task task : tasks) {
int loadedPos = mTaskList.indexOf(task);
if (loadedPos == -1) {
continue;
}
Task loadedTask = mTaskList.get(loadedPos);
task.icon = loadedTask.icon;
task.titleDescription = loadedTask.titleDescription;
task.thumbnail = loadedTask.thumbnail;
}
mTaskList = tasks;
onLoadedCallback.accept(tasks);
});
}
/**
* Load task icon and label asynchronously if it is not already loaded in the task. If the task
* already has an icon, this calls the callback immediately.
*
* @param task task to update with icon + label
* @param onLoadedCallback callback to run when task has icon and label
*/
public void loadTaskIconAndLabel(Task task, @Nullable Runnable onLoadedCallback) {
mRecentsModel.getIconCache().updateIconInBackground(task,
loadedTask -> onLoadedCallback.run());
}
/**
* Load thumbnail asynchronously if not already loaded in the task. If the task already has a
* thumbnail or if the thumbnail is cached, this calls the callback immediately.
*
* @param task task to update with the thumbnail
* @param onLoadedCallback callback to run when task has thumbnail
*/
public void loadTaskThumbnail(Task task, @Nullable Runnable onLoadedCallback) {
mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task,
thumbnail -> onLoadedCallback.run());
}
/**
* Removes the task from the current task list.
*/
void removeTask(Task task) {
mTaskList.remove(task);
}
/**
* Clears the current task list.
*/
void clearAllTasks() {
mTaskList.clear();
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT;
import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL;
import android.graphics.Canvas;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import java.util.function.Consumer;
/**
* Callback for swipe input on {@link TaskHolder} views in the recents view.
*/
public final class TaskSwipeCallback extends ItemTouchHelper.SimpleCallback {
private final Consumer<TaskHolder> mOnTaskSwipeCallback;
public TaskSwipeCallback(Consumer<TaskHolder> onTaskSwipeCallback) {
super(0 /* dragDirs */, RIGHT);
mOnTaskSwipeCallback = onTaskSwipeCallback;
}
@Override
public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder,
ViewHolder target) {
return false;
}
@Override
public void onSwiped(ViewHolder viewHolder, int direction) {
if (direction == RIGHT) {
mOnTaskSwipeCallback.accept((TaskHolder) viewHolder);
}
}
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
@NonNull ViewHolder viewHolder, float dX, float dY, int actionState,
boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
float alpha = 1.0f - dX / (float) viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(alpha);
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY,
actionState, isCurrentlyActive);
}
@Override
public int getSwipeDirs(@NonNull RecyclerView recyclerView,
@NonNull ViewHolder viewHolder) {
if (viewHolder.getItemViewType() == ITEM_TYPE_CLEAR_ALL) {
// Clear all button should not be swipable.
return 0;
}
return super.getSwipeDirs(recyclerView, viewHolder);
}
}

View File

@ -1,145 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static android.graphics.Shader.TileMode.CLAMP;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import com.android.launcher3.R;
import com.android.systemui.shared.recents.model.ThumbnailData;
/**
* Bitmap backed drawable that supports rotating the thumbnail bitmap depending on if the
* orientation the thumbnail was taken in matches the desired orientation. In addition, the
* thumbnail always fills into the containing bounds.
*/
public final class ThumbnailDrawable extends Drawable {
private final Paint mPaint = new Paint();
private final Matrix mMatrix = new Matrix();
private final ThumbnailData mThumbnailData;
private final BitmapShader mShader;
private final RectF mDestRect = new RectF();
private final int mCornerRadius;
private int mRequestedOrientation;
public ThumbnailDrawable(Resources res, @NonNull ThumbnailData thumbnailData,
int requestedOrientation) {
mThumbnailData = thumbnailData;
mRequestedOrientation = requestedOrientation;
mCornerRadius = (int) res.getDimension(R.dimen.task_thumbnail_corner_radius);
mShader = new BitmapShader(mThumbnailData.thumbnail, CLAMP, CLAMP);
mPaint.setShader(mShader);
mPaint.setAntiAlias(true);
updateMatrix();
}
/**
* Set the requested orientation.
*
* @param orientation the orientation we want the thumbnail to be in
*/
public void setRequestedOrientation(int orientation) {
if (mRequestedOrientation != orientation) {
mRequestedOrientation = orientation;
updateMatrix();
}
}
@Override
public void draw(Canvas canvas) {
if (mThumbnailData.thumbnail == null) {
return;
}
canvas.drawRoundRect(mDestRect, mCornerRadius, mCornerRadius, mPaint);
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mDestRect.set(bounds);
updateMatrix();
}
@Override
public void setAlpha(int alpha) {
final int oldAlpha = mPaint.getAlpha();
if (alpha != oldAlpha) {
mPaint.setAlpha(alpha);
invalidateSelf();
}
}
@Override
public int getAlpha() {
return mPaint.getAlpha();
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
invalidateSelf();
}
@Override
public ColorFilter getColorFilter() {
return mPaint.getColorFilter();
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
private void updateMatrix() {
if (mThumbnailData.thumbnail == null) {
return;
}
mMatrix.reset();
float scaleX;
float scaleY;
Rect bounds = getBounds();
Bitmap thumbnail = mThumbnailData.thumbnail;
if (mRequestedOrientation != mThumbnailData.orientation) {
// Rotate and translate so that top left is the same.
mMatrix.postRotate(90, 0, 0);
mMatrix.postTranslate(thumbnail.getHeight(), 0);
scaleX = (float) bounds.width() / thumbnail.getHeight();
scaleY = (float) bounds.height() / thumbnail.getWidth();
} else {
scaleX = (float) bounds.width() / thumbnail.getWidth();
scaleY = (float) bounds.height() / thumbnail.getHeight();
}
// Scale to fill.
mMatrix.postScale(scaleX, scaleY);
mShader.setLocalMatrix(mMatrix);
}
}

View File

@ -1,167 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import android.annotation.TargetApi;
import android.app.Service;
import android.content.Intent;
import android.graphics.Region;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.MotionEvent;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
/**
* Service connected by system-UI for handling touch interaction.
*/
@TargetApi(Build.VERSION_CODES.O)
public class TouchInteractionService extends Service {
private static final String TAG = "GoTouchInteractionService";
private final IBinder mMyBinder = new IOverviewProxy.Stub() {
@Override
public void onActiveNavBarRegionChanges(Region region) throws RemoteException { }
@Override
public void onInitialize(Bundle bundle) throws RemoteException {
ISystemUiProxy iSystemUiProxy = ISystemUiProxy.Stub
.asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(iSystemUiProxy);
}
@Override
public void onOverviewToggle() {
if (mDeviceState.isUserUnlocked()) {
mOverviewCommandHelper.onOverviewToggle();
}
}
@Override
public void onOverviewShown(boolean triggeredFromAltTab) {
if (mDeviceState.isUserUnlocked()) {
mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
}
}
@Override
public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mDeviceState.isUserUnlocked() && triggeredFromAltTab && !triggeredFromHomeKey) {
// onOverviewShownFromAltTab hides the overview and ends at the target app
mOverviewCommandHelper.onOverviewHidden();
}
}
@Override
public void onTip(int actionType, int viewType) {
if (mDeviceState.isUserUnlocked()) {
mOverviewCommandHelper.onTip(actionType, viewType);
}
}
@Override
public void onAssistantAvailable(boolean available) {
// TODO handle assistant
}
@Override
public void onAssistantVisibilityChanged(float visibility) {
// TODO handle assistant
}
public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
boolean gestureSwipeLeft) {
}
public void onSystemUiStateChanged(int stateFlags) {
// To be implemented
}
/** Deprecated methods **/
public void onQuickStep(MotionEvent motionEvent) { }
public void onQuickScrubEnd() { }
public void onQuickScrubProgress(float progress) { }
public void onQuickScrubStart() { }
public void onPreMotionEvent(int downHitTarget) { }
public void onMotionEvent(MotionEvent ev) { }
public void onBind(ISystemUiProxy iSystemUiProxy) {
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(iSystemUiProxy);
}
};
private static boolean sConnected = false;;
public static boolean isConnected() {
return sConnected;
}
private RecentsModel mRecentsModel;
private OverviewComponentObserver mOverviewComponentObserver;
private OverviewCommandHelper mOverviewCommandHelper;
private RecentsAnimationDeviceState mDeviceState;
@Override
public void onCreate() {
super.onCreate();
mDeviceState = new RecentsAnimationDeviceState(this);
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
sConnected = true;
}
public void onUserUnlocked() {
mRecentsModel = RecentsModel.INSTANCE.get(this);
mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
mOverviewComponentObserver);
}
@Override
public void onDestroy() {
if (mDeviceState.isUserUnlocked()) {
mOverviewComponentObserver.onDestroy();
}
mDeviceState.destroy();
sConnected = false;
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Touch service connected");
}
return mMyBinder;
}
public static boolean isInitialized() {
return true;
}
}

View File

@ -1,55 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.fallback;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.WindowInsets;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.RecentsActivity;
/**
* Minimal implementation of {@link BaseDragLayer} for Go's fallback recents activity.
*/
public final class GoRecentsActivityRootView extends BaseDragLayer<RecentsActivity> {
public GoRecentsActivityRootView(Context context, AttributeSet attrs) {
super(context, attrs, 1 /* alphaChannelCount */);
// Go leaves touch control to the view itself.
mControllers = new TouchController[0];
setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
@Override
public void setInsets(Rect insets) {
if (insets.equals(mInsets)) {
return;
}
super.setInsets(insets);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
Insets sysInsets = insets.getSystemWindowInsets();
setInsets(new Rect(sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom));
return insets.consumeSystemWindowInsets();
}
}

View File

@ -1,31 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.util;
import com.android.launcher3.Launcher;
/** Empty class, only exists so that lowRamWithQuickstepIconRecentsDebug compiles. */
public class ShelfPeekAnim {
public ShelfPeekAnim(Launcher launcher) {
}
public enum ShelfAnimState {
}
public boolean isPeeking() {
return false;
}
}

View File

@ -1,962 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.views;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL;
import static com.android.quickstep.TaskAdapter.ITEM_TYPE_TASK;
import static com.android.quickstep.TaskAdapter.MAX_TASKS_TO_DISPLAY;
import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION;
import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewTreeObserver;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
import com.android.quickstep.ContentFillItemAnimator;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
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;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code
* base.
*/
public final class IconRecentsView extends FrameLayout
implements Insettable, TaskVisualsChangeListener {
public static final FloatProperty<IconRecentsView> CONTENT_ALPHA =
new FloatProperty<IconRecentsView>("contentAlpha") {
@Override
public void setValue(IconRecentsView view, float v) {
ALPHA.set(view, v);
if (view.getVisibility() != VISIBLE && v > 0) {
view.setVisibility(VISIBLE);
} else if (view.getVisibility() != GONE && v == 0){
view.setVisibility(GONE);
}
}
@Override
public Float get(IconRecentsView view) {
return ALPHA.get(view);
}
};
private static final long CROSSFADE_DURATION = 300;
private static final long LAYOUT_ITEM_ANIMATE_IN_DURATION = 150;
private static final long LAYOUT_ITEM_ANIMATE_IN_DELAY_BETWEEN = 40;
private static final long ITEM_ANIMATE_OUT_DURATION = 150;
private static final long ITEM_ANIMATE_OUT_DELAY_BETWEEN = 40;
private static final float ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO = .25f;
private static final long CLEAR_ALL_FADE_DELAY = 120;
private static final long REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION = 300;
private static final long REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION = 400;
private static final long REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY = 200;
private static final long REMOTE_TO_RECENTS_ITEM_FADE_DURATION = 217;
private static final long REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY = 33;
private static final PathInterpolator FAST_OUT_SLOW_IN_1 =
new PathInterpolator(.4f, 0f, 0f, 1f);
private static final PathInterpolator FAST_OUT_SLOW_IN_2 =
new PathInterpolator(.5f, 0f, 0f, 1f);
private static final LinearOutSlowInInterpolator OUT_SLOW_IN =
new LinearOutSlowInInterpolator();
public static final long REMOTE_APP_TO_OVERVIEW_DURATION =
REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION;
/**
* 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 final Context mContext;
private final TaskListLoader mTaskLoader;
private final TaskAdapter mTaskAdapter;
private final LinearLayoutManager mTaskLayoutManager;
private final TaskActionController mTaskActionController;
private final DefaultItemAnimator mDefaultItemAnimator = new DefaultItemAnimator();
private final ContentFillItemAnimator mLoadingContentItemAnimator =
new ContentFillItemAnimator();
private final BaseActivity mActivity;
private final Drawable mStatusBarForegroundScrim;
private RecentsToActivityHelper mActivityHelper;
private RecyclerView mTaskRecyclerView;
private View mShowingContentView;
private View mEmptyView;
private View mContentView;
private boolean mTransitionedFromApp;
private boolean mUsingRemoteAnimation;
private boolean mStartedEnterAnimation;
private boolean mShowStatusBarForegroundScrim;
private AnimatorSet mLayoutAnimation;
private final ArraySet<View> mLayingOutViews = new ArraySet<>();
private Rect mInsets;
public IconRecentsView(Context context, AttributeSet attrs) {
super(context, attrs);
mActivity = BaseActivity.fromContext(context);
mContext = context;
mStatusBarForegroundScrim =
Themes.getAttrDrawable(mContext, R.attr.workspaceStatusBarScrim);
mTaskLoader = new TaskListLoader(mContext);
mTaskAdapter = new TaskAdapter(mTaskLoader);
mTaskAdapter.setOnClearAllClickListener(view -> animateClearAllTasks());
mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter,
mActivity.getStatsLogManager());
mTaskAdapter.setActionController(mTaskActionController);
mTaskLayoutManager = new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */);
}
@Override
public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
ArrayList<TaskItemView> itemViews = getTaskViews();
for (int i = 0, size = itemViews.size(); i < size; i++) {
TaskItemView taskView = itemViews.get(i);
TaskHolder taskHolder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView);
Optional<Task> optTask = taskHolder.getTask();
if (optTask.filter(task -> task.key.id == taskId).isPresent()) {
Task task = optTask.get();
// Update thumbnail on the task.
task.thumbnail = thumbnailData;
taskView.setThumbnail(thumbnailData);
return task;
}
}
return null;
}
@Override
public void onTaskIconChanged(String pkg, UserHandle user) { }
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (mTaskRecyclerView == null) {
mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
mTaskRecyclerView.setAdapter(mTaskAdapter);
mTaskRecyclerView.setLayoutManager(mTaskLayoutManager);
ItemTouchHelper helper = new ItemTouchHelper(
new TaskSwipeCallback(holder -> {
mTaskActionController.removeTask(holder);
if (mTaskLoader.getCurrentTaskList().isEmpty()) {
mActivityHelper.leaveRecents();
}
}));
helper.attachToRecyclerView(mTaskRecyclerView);
mTaskRecyclerView.addOnChildAttachStateChangeListener(
new OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(@NonNull View view) {
if (mLayoutAnimation != null && !mLayingOutViews.contains(view)) {
// Child view was added that is not part of current layout animation
// so restart the animation.
animateFadeInLayoutAnimation();
}
}
@Override
public void onChildViewDetachedFromWindow(@NonNull View view) { }
});
mTaskRecyclerView.setItemAnimator(mDefaultItemAnimator);
mLoadingContentItemAnimator.setOnAnimationFinishedRunnable(
() -> mTaskRecyclerView.setItemAnimator(new DefaultItemAnimator()));
ItemDecoration marginDecorator = new ItemDecoration() {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
// TODO: Determine if current margins cause off screen item to be fully off
// screen and if so, modify them so that it is partially off screen.
int itemType = parent.getChildViewHolder(view).getItemViewType();
Resources res = getResources();
switch (itemType) {
case ITEM_TYPE_CLEAR_ALL:
outRect.top = (int) res.getDimension(
R.dimen.clear_all_item_view_top_margin);
outRect.bottom = (int) res.getDimension(
R.dimen.clear_all_item_view_bottom_margin);
break;
case ITEM_TYPE_TASK:
int desiredTopMargin = (int) res.getDimension(
R.dimen.task_item_top_margin);
if (mTaskRecyclerView.getChildAdapterPosition(view) ==
state.getItemCount() - 1) {
// Only add top margin to top task view if insets aren't enough.
if (mInsets.top < desiredTopMargin) {
outRect.top = desiredTopMargin - mInsets.bottom;
}
return;
}
outRect.top = desiredTopMargin;
break;
default:
}
}
};
mTaskRecyclerView.addItemDecoration(marginDecorator);
mEmptyView = findViewById(R.id.recent_task_empty_view);
mContentView = mTaskRecyclerView;
mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() {
@Override
public void onChanged() {
updateContentViewVisibility();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
updateContentViewVisibility();
}
});
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
int childCount = mTaskRecyclerView.getChildCount();
for (int i = 0; i < childCount; i++) {
mTaskRecyclerView.getChildAt(i).setEnabled(enabled);
}
}
/**
* Set activity helper for the view to callback to.
*
* @param helper the activity helper
*/
public void setRecentsToActivityHelper(@NonNull RecentsToActivityHelper helper) {
mActivityHelper = helper;
}
/**
* Logic for when we know we are going to overview/recents and will be putting up the recents
* view. This should be used to prepare recents (e.g. load any task data, etc.) before it
* becomes visible.
*/
public void onBeginTransitionToOverview() {
mStartedEnterAnimation = false;
if (mContext.getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
// Scroll to bottom of task in landscape mode. This is a non-issue in portrait mode as
// all tasks should be visible to fill up the screen in portrait mode and the view will
// not be scrollable.
mTaskLayoutManager.scrollToPositionWithOffset(TASKS_START_POSITION, 0 /* offset */);
}
if (!mUsingRemoteAnimation) {
scheduleFadeInLayoutAnimation();
}
// Load any task changes
if (!mTaskLoader.needsToLoad()) {
return;
}
mTaskAdapter.setIsShowingLoadingUi(true);
mTaskAdapter.notifyDataSetChanged();
mTaskLoader.loadTaskList(tasks -> {
int numEmptyItems = mTaskAdapter.getItemCount() - TASKS_START_POSITION;
mTaskAdapter.setIsShowingLoadingUi(false);
int numActualItems = mTaskAdapter.getItemCount() - TASKS_START_POSITION;
if (numEmptyItems < numActualItems) {
throw new IllegalStateException("There are less empty item views than the number "
+ "of items to animate to.");
}
// Possible that task list loads faster than adapter changes propagate to layout so
// only start content fill animation if there aren't any pending adapter changes and
// we've started the on enter layout animation.
boolean needsContentFillAnimation =
!mTaskRecyclerView.hasPendingAdapterUpdates() && mStartedEnterAnimation;
if (needsContentFillAnimation) {
// Set item animator for content filling animation. The item animator will switch
// back to the default on completion
mTaskRecyclerView.setItemAnimator(mLoadingContentItemAnimator);
mTaskAdapter.notifyItemRangeRemoved(TASKS_START_POSITION + numActualItems,
numEmptyItems - numActualItems);
mTaskAdapter.notifyItemRangeChanged(TASKS_START_POSITION, numActualItems,
CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT);
} else {
// Notify change without animating.
mTaskAdapter.notifyDataSetChanged();
}
});
}
/**
* 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;
}
/**
* Set whether we're using a custom remote animation. If so, we will not do the default layout
* animation when entering recents and instead wait for the remote app surface to be ready to
* use.
*
* @param usingRemoteAnimation true if doing a remote animation, false o/w
*/
public void setUsingRemoteAnimation(boolean usingRemoteAnimation) {
mUsingRemoteAnimation = usingRemoteAnimation;
}
/**
* 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() {
List<Task> tasks = mTaskLoader.getCurrentTaskList();
int tasksSize = tasks.size();
if (tasksSize == 0) {
// Do nothing
return;
}
Task taskToLaunch;
if (mTransitionedFromApp && tasksSize > 1) {
// Launch the next most recent app
taskToLaunch = tasks.get(1);
} else {
// Launch the most recent app
taskToLaunch = tasks.get(0);
}
// See if view for this task is attached, and if so, animate launch from that view.
ArrayList<TaskItemView> itemViews = getTaskViews();
for (int i = 0, size = itemViews.size(); i < size; i++) {
TaskItemView taskView = itemViews.get(i);
TaskHolder holder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView);
if (Objects.equals(holder.getTask(), Optional.of(taskToLaunch))) {
mTaskActionController.launchTaskFromView(holder);
return;
}
}
// Otherwise, just use a basic launch animation.
mTaskActionController.launchTask(taskToLaunch);
}
/**
* Set whether or not to show the scrim in between the view and the top insets. This only works
* if the view is being insetted in the first place.
*
* The scrim is added to the activity's root view to prevent animations on this view
* affecting the scrim. As a result, it is the activity's responsibility to show/hide this
* scrim as appropriate.
*
* @param showStatusBarForegroundScrim true to show the scrim, false to hide
*/
public void setShowStatusBarForegroundScrim(boolean showStatusBarForegroundScrim) {
mShowStatusBarForegroundScrim = showStatusBarForegroundScrim;
if (mShowStatusBarForegroundScrim != showStatusBarForegroundScrim) {
updateStatusBarScrim();
}
}
private void updateStatusBarScrim() {
boolean shouldShow = mInsets.top != 0 && mShowStatusBarForegroundScrim;
mActivity.getDragLayer().setForeground(shouldShow ? mStatusBarForegroundScrim : null);
}
/**
* Get the bottom most task view to animate to.
*
* @return the task view
*/
private @Nullable TaskItemView getBottomTaskView() {
int childCount = mTaskRecyclerView.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = mTaskRecyclerView.getChildAt(i);
if (mTaskRecyclerView.getChildViewHolder(view).getItemViewType() == ITEM_TYPE_TASK) {
return (TaskItemView) view;
}
}
return null;
}
/**
* Whether this view has processed all data changes and is ready to animate from the app to
* the overview.
*
* @return true if ready to animate app to overview, false otherwise
*/
public boolean isReadyForRemoteAnim() {
return !mTaskRecyclerView.hasPendingAdapterUpdates();
}
/**
* Set a callback for whenever this view is ready to do a remote animation from the app to
* overview. See {@link #isReadyForRemoteAnim()}.
*
* @param callback callback to run when view is ready to animate
*/
public void setOnReadyForRemoteAnimCallback(onReadyForRemoteAnimCallback callback) {
mTaskRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (isReadyForRemoteAnim()) {
callback.onReadyForRemoteAnim();
mTaskRecyclerView.getViewTreeObserver().
removeOnGlobalLayoutListener(this);
}
}
});
}
/**
* Clear all tasks and animate out.
*/
private void animateClearAllTasks() {
setEnabled(false);
ArrayList<TaskItemView> itemViews = getTaskViews();
AnimatorSet clearAnim = new AnimatorSet();
long currentDelay = 0;
// Animate each item view to the right and fade out.
for (int i = 0, size = itemViews.size(); i < size; i++) {
TaskItemView itemView = itemViews.get(i);
PropertyValuesHolder transXproperty = PropertyValuesHolder.ofFloat(TRANSLATION_X,
0, itemView.getWidth() * ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO);
PropertyValuesHolder alphaProperty = PropertyValuesHolder.ofFloat(ALPHA, 1.0f, 0f);
ObjectAnimator itemAnim = ObjectAnimator.ofPropertyValuesHolder(itemView,
transXproperty, alphaProperty);
itemAnim.setDuration(ITEM_ANIMATE_OUT_DURATION);
itemAnim.setStartDelay(currentDelay);
clearAnim.play(itemAnim);
currentDelay += ITEM_ANIMATE_OUT_DELAY_BETWEEN;
}
// Animate view fading and leave recents when faded enough.
ValueAnimator contentAlpha = ValueAnimator.ofFloat(1.0f, 0f)
.setDuration(CROSSFADE_DURATION);
contentAlpha.setStartDelay(CLEAR_ALL_FADE_DELAY);
contentAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private boolean mLeftRecents = false;
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mContentView.setAlpha((float) valueAnimator.getAnimatedValue());
// Leave recents while fading out.
if ((float) valueAnimator.getAnimatedValue() < .5f && !mLeftRecents) {
mActivityHelper.leaveRecents();
mLeftRecents = true;
}
}
});
clearAnim.play(contentAlpha);
clearAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
for (int i = 0, size = itemViews.size(); i < size; i++) {
TaskItemView itemView = itemViews.get(i);
itemView.setTranslationX(0);
itemView.setAlpha(1.0f);
}
setEnabled(true);
mContentView.setVisibility(GONE);
mTaskActionController.clearAllTasks();
}
});
clearAnim.start();
}
/**
* Get attached task item views ordered by most recent.
*
* @return array list of attached task item views
*/
private ArrayList<TaskItemView> getTaskViews() {
int taskCount = mTaskRecyclerView.getChildCount();
ArrayList<TaskItemView> itemViews = new ArrayList<>();
for (int i = 0; i < taskCount; i ++) {
View child = mTaskRecyclerView.getChildAt(i);
if (child instanceof TaskItemView) {
itemViews.add((TaskItemView) child);
}
}
return itemViews;
}
/**
* Update the content view so that the appropriate view is shown based off the current list
* of tasks.
*/
private void updateContentViewVisibility() {
int taskListSize = mTaskAdapter.getItemCount() - TASKS_START_POSITION;
if (mShowingContentView != mEmptyView && taskListSize == 0) {
mShowingContentView = mEmptyView;
crossfadeViews(mEmptyView, mContentView);
}
if (mShowingContentView != mContentView && taskListSize > 0) {
mShowingContentView = mContentView;
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.animate().cancel();
fadeInView.setVisibility(VISIBLE);
fadeInView.setAlpha(0f);
fadeInView.animate()
.alpha(1f)
.setDuration(CROSSFADE_DURATION)
.setListener(null);
fadeOutView.animate().cancel();
fadeOutView.animate()
.alpha(0f)
.setDuration(CROSSFADE_DURATION)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
fadeOutView.setVisibility(GONE);
}
});
}
/**
* Schedule a one-shot layout animation on the next layout. Separate from
* {@link #scheduleLayoutAnimation()} as the animation is {@link Animator} based and acts on the
* view properties themselves, allowing more controllable behavior and making it easier to
* manage when the animation conflicts with another animation.
*/
private void scheduleFadeInLayoutAnimation() {
mTaskRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
animateFadeInLayoutAnimation();
mTaskRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
/**
* Start animating the layout animation where items fade in.
*/
private void animateFadeInLayoutAnimation() {
if (mLayoutAnimation != null) {
// If layout animation still in progress, cancel and restart.
mLayoutAnimation.cancel();
}
ArrayList<TaskItemView> views = getTaskViews();
int delay = 0;
mLayoutAnimation = new AnimatorSet();
for (int i = 0, size = views.size(); i < size; i++) {
TaskItemView view = views.get(i);
view.setAlpha(0.0f);
Animator alphaAnim = ObjectAnimator.ofFloat(view, ALPHA, 0.0f, 1.0f);
alphaAnim.setDuration(LAYOUT_ITEM_ANIMATE_IN_DURATION).setStartDelay(delay);
alphaAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setAlpha(1.0f);
mLayingOutViews.remove(view);
}
});
delay += LAYOUT_ITEM_ANIMATE_IN_DELAY_BETWEEN;
mLayoutAnimation.play(alphaAnim);
mLayingOutViews.add(view);
}
mLayoutAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLayoutAnimation = null;
}
});
mLayoutAnimation.start();
mStartedEnterAnimation = true;
}
/**
* Play remote app to recents animation when the app is the home activity. We use a simple
* cross-fade here. Note this is only used if the home activity is a separate app than the
* recents activity.
*
* @param anim animator set
* @param homeTarget the home surface thats closing
* @param recentsTarget the surface containing recents
*/
public void playRemoteHomeToRecentsAnimation(@NonNull AnimatorSet anim,
@NonNull RemoteAnimationTargetCompat homeTarget,
@NonNull RemoteAnimationTargetCompat recentsTarget) {
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
new SyncRtSurfaceTransactionApplierCompat(this);
SurfaceParams[] params = new SurfaceParams[2];
int boostedMode = MODE_CLOSING;
ValueAnimator remoteHomeAnim = ValueAnimator.ofFloat(0, 1);
remoteHomeAnim.setDuration(REMOTE_APP_TO_OVERVIEW_DURATION);
remoteHomeAnim.addUpdateListener(valueAnimator -> {
float val = (float) valueAnimator.getAnimatedValue();
float alpha;
RemoteAnimationTargetCompat visibleTarget;
RemoteAnimationTargetCompat invisibleTarget;
if (val < .5f) {
visibleTarget = homeTarget;
invisibleTarget = recentsTarget;
alpha = 1 - (val * 2);
} else {
visibleTarget = recentsTarget;
invisibleTarget = homeTarget;
alpha = (val - .5f) * 2;
}
params[0] = new SurfaceParams(visibleTarget.leash, alpha, null /* matrix */,
null /* windowCrop */, getLayer(visibleTarget, boostedMode),
0 /* cornerRadius */);
params[1] = new SurfaceParams(invisibleTarget.leash, 0.0f, null /* matrix */,
null /* windowCrop */, getLayer(invisibleTarget, boostedMode),
0 /* cornerRadius */);
surfaceApplier.scheduleApply(params);
});
anim.play(remoteHomeAnim);
animateFadeInLayoutAnimation();
}
/**
* Play remote animation from app to recents. This should scale the currently closing app down
* to the recents thumbnail.
*
* @param anim animator set
* @param appTarget the app surface thats closing
* @param recentsTarget the surface containing recents
*/
public void playRemoteAppToRecentsAnimation(@NonNull AnimatorSet anim,
@NonNull RemoteAnimationTargetCompat appTarget,
@NonNull RemoteAnimationTargetCompat recentsTarget) {
TaskItemView bottomView = getBottomTaskView();
if (bottomView == null) {
// This can be null if there were previously 0 tasks and the recycler view has not had
// enough time to take in the data change, bind a new view, and lay out the new view.
// TODO: Have a fallback to animate to
anim.play(ValueAnimator.ofInt(0, 1).setDuration(REMOTE_APP_TO_OVERVIEW_DURATION));
return;
}
final Matrix appMatrix = new Matrix();
playRemoteTransYAnim(anim, appMatrix);
playRemoteAppScaleDownAnim(anim, appMatrix, appTarget, recentsTarget,
bottomView.getThumbnailView());
playRemoteTaskListFadeIn(anim, bottomView);
mStartedEnterAnimation = true;
}
/**
* Play translation Y animation for the remote app to recents animation. Animates over all task
* views as well as the closing app, easing them into their final vertical positions.
*
* @param anim animator set to play on
* @param appMatrix transformation matrix for the closing app surface
*/
private void playRemoteTransYAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix) {
final ArrayList<TaskItemView> views = getTaskViews();
// Start Y translation from about halfway through the tasks list to the bottom thumbnail.
float taskHeight = getResources().getDimension(R.dimen.task_item_height);
float totalTransY = -(MAX_TASKS_TO_DISPLAY / 2.0f - 1) * taskHeight;
for (int i = 0, size = views.size(); i < size; i++) {
views.get(i).setTranslationY(totalTransY);
}
ValueAnimator transYAnim = ValueAnimator.ofFloat(totalTransY, 0);
transYAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION);
transYAnim.setInterpolator(FAST_OUT_SLOW_IN_2);
transYAnim.addUpdateListener(valueAnimator -> {
float transY = (float) valueAnimator.getAnimatedValue();
for (int i = 0, size = views.size(); i < size; i++) {
views.get(i).setTranslationY(transY);
}
appMatrix.postTranslate(0, transY - totalTransY);
});
transYAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
for (int i = 0, size = views.size(); i < size; i++) {
views.get(i).setTranslationY(0);
}
}
});
anim.play(transYAnim);
}
/**
* Play the scale down animation for the remote app to recents animation where the app surface
* scales down to where the thumbnail is.
*
* @param anim animator set to play on
* @param appMatrix transformation matrix for the app surface
* @param appTarget closing app target
* @param recentsTarget opening recents target
* @param thumbnailView thumbnail view to animate to
*/
private void playRemoteAppScaleDownAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix,
@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;
RectF currentAppRect = new RectF();
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
new SyncRtSurfaceTransactionApplierCompat(this);
// Keep recents visible throughout the animation.
SurfaceParams[] params = new SurfaceParams[2];
// Closing app should stay on top.
int boostedMode = MODE_CLOSING;
params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */);
ValueAnimator remoteAppAnim = ValueAnimator.ofInt(0, 1);
remoteAppAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION);
remoteAppAnim.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, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
FAST_OUT_SLOW_IN_1);
float endScaleY = ((float) endRect.height()) / appBounds.height();
mScaleY = new FloatProp(1f, endScaleY, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
FAST_OUT_SLOW_IN_1);
float endTranslationX = endRect.left -
(appBounds.width() - thumbnailView.getWidth()) / 2.0f;
mTranslationX = new FloatProp(0, endTranslationX, 0,
REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_1);
float endTranslationY = endRect.top -
(appBounds.height() - thumbnailView.getHeight()) / 2.0f;
mTranslationY = new FloatProp(0, endTranslationY, 0,
REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_2);
mAlpha = new FloatProp(1.0f, 0, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
ACCEL_2);
}
@Override
public void onUpdate(float percent) {
Matrix m = new Matrix();
m.preScale(mScaleX.value, mScaleY.value,
appBounds.width() / 2.0f, appBounds.height() / 2.0f);
m.postTranslate(mTranslationX.value, mTranslationY.value);
appMatrix.preConcat(m);
params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, appMatrix,
null /* windowCrop */, getLayer(appTarget, boostedMode),
0 /* cornerRadius */);
surfaceApplier.scheduleApply(params);
m.mapRect(currentAppRect, new RectF(appBounds));
setViewToRect(thumbnailView, new RectF(endRect), currentAppRect);
appMatrix.reset();
}
});
remoteAppAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
thumbnailView.setTranslationY(0);
thumbnailView.setTranslationX(0);
thumbnailView.setScaleX(1);
thumbnailView.setScaleY(1);
}
});
anim.play(remoteAppAnim);
}
/**
* Play task list fade in animation as part of remote app to recents animation. This animation
* ensures that the task views in the recents list fade in from bottom to top.
*
* @param anim animator set to play on
* @param appTaskView the task view associated with the remote app closing
*/
private void playRemoteTaskListFadeIn(@NonNull AnimatorSet anim,
@NonNull TaskItemView appTaskView) {
long delay = REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY;
int childCount = mTaskRecyclerView.getChildCount();
for (int i = 0; i < childCount; i++) {
ValueAnimator fadeAnim = ValueAnimator.ofFloat(0, 1.0f);
fadeAnim.setDuration(REMOTE_TO_RECENTS_ITEM_FADE_DURATION).setInterpolator(OUT_SLOW_IN);
fadeAnim.setStartDelay(delay);
View view = mTaskRecyclerView.getChildAt(i);
if (Objects.equals(view, appTaskView)) {
// Only animate icon and text for the view with snapshot animating in
final View icon = appTaskView.getIconView();
final View label = appTaskView.getLabelView();
icon.setAlpha(0.0f);
label.setAlpha(0.0f);
fadeAnim.addUpdateListener(alphaVal -> {
float val = alphaVal.getAnimatedFraction();
icon.setAlpha(val);
label.setAlpha(val);
});
fadeAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
icon.setAlpha(1.0f);
label.setAlpha(1.0f);
}
});
} else {
// Otherwise, fade in the entire view.
view.setAlpha(0.0f);
fadeAnim.addUpdateListener(alphaVal -> {
float val = alphaVal.getAnimatedFraction();
view.setAlpha(val);
});
fadeAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setAlpha(1.0f);
}
});
}
anim.play(fadeAnim);
int itemType = mTaskRecyclerView.getChildViewHolder(view).getItemViewType();
if (itemType == ITEM_TYPE_CLEAR_ALL) {
// Don't add delay. Clear all should animate at same time as next view.
continue;
}
delay += REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY;
}
}
/**
* Set view properties so that the view fits to the target rect.
*
* @param view view to set
* @param origRect original rect that view was located
* @param targetRect rect to set to
*/
private void setViewToRect(View view, RectF origRect, RectF targetRect) {
float dX = targetRect.left - origRect.left;
float dY = targetRect.top - origRect.top;
view.setTranslationX(dX);
view.setTranslationY(dY);
float scaleX = targetRect.width() / origRect.width();
float scaleY = targetRect.height() / origRect.height();
view.setPivotX(0);
view.setPivotY(0);
view.setScaleX(scaleX);
view.setScaleY(scaleY);
}
@Override
public void setInsets(Rect insets) {
mInsets = insets;
mTaskRecyclerView.setPadding(insets.left, insets.top, insets.right, insets.bottom);
mTaskRecyclerView.invalidateItemDecorations();
if (mInsets.top != 0) {
updateStatusBarScrim();
}
}
/**
* Callback for when this view is ready for a remote animation from app to overview.
*/
public interface onReadyForRemoteAnimCallback {
void onReadyForRemoteAnim();
}
}

View File

@ -1,216 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.views;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.quickstep.ThumbnailDrawable;
import com.android.systemui.shared.recents.model.ThumbnailData;
/**
* View representing an individual task item with the icon + thumbnail adjacent to the task label.
*/
public final class TaskItemView extends LinearLayout {
private static final String EMPTY_LABEL = "";
private static final String DEFAULT_LABEL = "...";
private final Drawable mDefaultIcon;
private final Drawable mDefaultThumbnail;
private final TaskLayerDrawable mIconDrawable;
private final TaskLayerDrawable mThumbnailDrawable;
private View mTaskIconThumbnailView;
private TextView mLabelView;
private ImageView mIconView;
private ImageView mThumbnailView;
private float mContentTransitionProgress;
private int mDisplayedOrientation;
/**
* Property representing the content transition progress of the view. 1.0f represents that the
* currently bound icon, thumbnail, and label are fully animated in and visible.
*/
public static FloatProperty CONTENT_TRANSITION_PROGRESS =
new FloatProperty<TaskItemView>("taskContentTransitionProgress") {
@Override
public void setValue(TaskItemView view, float progress) {
view.setContentTransitionProgress(progress);
}
@Override
public Float get(TaskItemView view) {
return view.mContentTransitionProgress;
}
};
public TaskItemView(Context context, AttributeSet attrs) {
super(context, attrs);
Resources res = context.getResources();
mDefaultIcon = res.getDrawable(android.R.drawable.sym_def_app_icon, context.getTheme());
mDefaultThumbnail = res.getDrawable(R.drawable.default_thumbnail, context.getTheme());
mIconDrawable = new TaskLayerDrawable(context);
mThumbnailDrawable = new TaskLayerDrawable(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mLabelView = findViewById(R.id.task_label);
mTaskIconThumbnailView = findViewById(R.id.task_icon_and_thumbnail);
mThumbnailView = findViewById(R.id.task_thumbnail);
mIconView = findViewById(R.id.task_icon);
mThumbnailView.setImageDrawable(mThumbnailDrawable);
mIconView.setImageDrawable(mIconDrawable);
resetToEmptyUi();
CONTENT_TRANSITION_PROGRESS.setValue(this, 1.0f);
}
/**
* Resets task item view to empty, loading UI.
*/
public void resetToEmptyUi() {
mIconDrawable.resetDrawable();
mThumbnailDrawable.resetDrawable();
setLabel(EMPTY_LABEL);
}
/**
* Set the label for the task item. Sets to a default label if null.
*
* @param label task label
*/
public void setLabel(@Nullable String label) {
mLabelView.setText(getSafeLabel(label));
// TODO: Animation for label
}
/**
* Set the icon for the task item. Sets to a default icon if null.
*
* @param icon task icon
*/
public void setIcon(@Nullable 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.
mIconDrawable.setCurrentDrawable(getSafeIcon(icon));
}
/**
* Set the task thumbnail for the task. Sets to a default thumbnail if null.
*
* @param thumbnailData task thumbnail data for the task
*/
public void setThumbnail(@Nullable ThumbnailData thumbnailData) {
mThumbnailDrawable.setCurrentDrawable(getSafeThumbnail(thumbnailData));
}
public View getThumbnailView() {
return mThumbnailView;
}
public View getIconView() {
return mIconView;
}
public View getLabelView() {
return mLabelView;
}
/**
* Start a new animation from the current task content to the specified new content. The caller
* is responsible for the actual animation control via the property
* {@link #CONTENT_TRANSITION_PROGRESS}.
*
* @param endIcon the icon to animate to
* @param endThumbnail the thumbnail to animate to
* @param endLabel the label to animate to
*/
public void startContentAnimation(@Nullable Drawable endIcon,
@Nullable ThumbnailData endThumbnail, @Nullable String endLabel) {
mIconDrawable.startNewTransition(getSafeIcon(endIcon));
mThumbnailDrawable.startNewTransition(getSafeThumbnail(endThumbnail));
// TODO: Animation for label
setContentTransitionProgress(0.0f);
}
private void setContentTransitionProgress(float progress) {
mContentTransitionProgress = progress;
mIconDrawable.setTransitionProgress(progress);
mThumbnailDrawable.setTransitionProgress(progress);
// TODO: Animation for label
}
private @NonNull Drawable getSafeIcon(@Nullable Drawable icon) {
return (icon != null) ? icon : mDefaultIcon;
}
private @NonNull Drawable getSafeThumbnail(@Nullable ThumbnailData thumbnailData) {
if (thumbnailData == null || thumbnailData.thumbnail == null) {
return mDefaultThumbnail;
}
int orientation = getResources().getConfiguration().orientation;
return new ThumbnailDrawable(getResources(), thumbnailData,
orientation /* requestedOrientation */);
}
private @NonNull String getSafeLabel(@Nullable String label) {
return (label != null) ? label : DEFAULT_LABEL;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
onOrientationChanged(getResources().getConfiguration().orientation);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
onOrientationChanged(newConfig.orientation);
}
private void onOrientationChanged(int newOrientation) {
if (mDisplayedOrientation == newOrientation) {
return;
}
mDisplayedOrientation = newOrientation;
int layerCount = mThumbnailDrawable.getNumberOfLayers();
for (int i = 0; i < layerCount; i++) {
Drawable drawable = mThumbnailDrawable.getDrawable(i);
if (drawable instanceof ThumbnailDrawable) {
((ThumbnailDrawable) drawable).setRequestedOrientation(newOrientation);
}
}
mTaskIconThumbnailView.forceLayout();
}
}

View File

@ -1,101 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.views;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import androidx.annotation.NonNull;
import com.android.launcher3.R;
/**
* A layer drawable for task content that transitions between two drawables by crossfading. Similar
* to {@link android.graphics.drawable.TransitionDrawable} but allows callers to control transition
* progress and provides a default, empty drawable.
*/
public final class TaskLayerDrawable extends LayerDrawable {
private final Drawable mEmptyDrawable;
private float mProgress;
public TaskLayerDrawable(Context context) {
super(new Drawable[0]);
// Use empty drawable for both layers initially.
mEmptyDrawable = context.getResources().getDrawable(
R.drawable.empty_content_box, context.getTheme());
addLayer(mEmptyDrawable);
addLayer(mEmptyDrawable);
setTransitionProgress(1.0f);
}
/**
* Immediately set the front-most drawable layer.
*
* @param drawable drawable to set
*/
public void setCurrentDrawable(@NonNull Drawable drawable) {
setDrawable(0, drawable);
applyTransitionProgress(mProgress);
}
/**
* Immediately reset the drawable to showing the empty drawable.
*/
public void resetDrawable() {
setCurrentDrawable(mEmptyDrawable);
}
/**
* Prepare to start animating the transition by pushing the current drawable to the back and
* setting a new drawable to the front layer and making it invisible.
*
* @param endDrawable drawable to animate to
*/
public void startNewTransition(@NonNull Drawable endDrawable) {
Drawable oldDrawable = getDrawable(0);
setDrawable(1, oldDrawable);
setDrawable(0, endDrawable);
setTransitionProgress(0.0f);
}
/**
* Set the progress of the transition animation to crossfade the two drawables.
*
* @param progress current transition progress between 0 (front view invisible) and 1
* (front view visible)
*/
public void setTransitionProgress(float progress) {
if (progress > 1 || progress < 0) {
throw new IllegalArgumentException("Transition progress should be between 0 and 1");
}
mProgress = progress;
applyTransitionProgress(progress);
}
private void applyTransitionProgress(float progress) {
int drawableAlpha = (int) (progress * 255);
getDrawable(0).setAlpha(drawableAlpha);
if (getDrawable(0) != getDrawable(1)) {
// Only do this if it's a different drawable so that it fades out.
// Otherwise, we'd just be overwriting the front drawable's alpha.
getDrawable(1).setAlpha(255 - drawableAlpha);
}
invalidateSelf();
}
}

View File

@ -1,107 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.views;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import com.android.launcher3.R;
/**
* Square view that holds thumbnail and icon and shrinks them appropriately so that both fit nicely
* within the view. Side length is determined by height.
*/
final class TaskThumbnailIconView extends ViewGroup {
private final Rect mTmpFrameRect = new Rect();
private final Rect mTmpChildRect = new Rect();
private View mThumbnailView;
private View mIconView;
private static final float SUBITEM_FRAME_RATIO = .6f;
public TaskThumbnailIconView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mThumbnailView = findViewById(R.id.task_thumbnail);
mIconView = findViewById(R.id.task_icon);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
int width = height;
setMeasuredDimension(width, height);
int subItemSize = (int) (SUBITEM_FRAME_RATIO * height);
if (mThumbnailView.getVisibility() != GONE) {
boolean isPortrait =
(getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT);
int thumbnailHeightSpec =
makeMeasureSpec(isPortrait ? height : subItemSize, MeasureSpec.EXACTLY);
int thumbnailWidthSpec =
makeMeasureSpec(isPortrait ? subItemSize : width, MeasureSpec.EXACTLY);
measureChild(mThumbnailView, thumbnailWidthSpec, thumbnailHeightSpec);
}
if (mIconView.getVisibility() != GONE) {
int iconHeightSpec = makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY);
int iconWidthSpec = makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY);
measureChild(mIconView, iconWidthSpec, iconHeightSpec);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mTmpFrameRect.left = getPaddingLeft();
mTmpFrameRect.right = right - left - getPaddingRight();
mTmpFrameRect.top = getPaddingTop();
mTmpFrameRect.bottom = bottom - top - getPaddingBottom();
// Layout the thumbnail to the top-start corner of the view
if (mThumbnailView.getVisibility() != GONE) {
final int width = mThumbnailView.getMeasuredWidth();
final int height = mThumbnailView.getMeasuredHeight();
final int thumbnailGravity = Gravity.TOP | Gravity.START;
Gravity.apply(thumbnailGravity, width, height, mTmpFrameRect, mTmpChildRect);
mThumbnailView.layout(mTmpChildRect.left, mTmpChildRect.top,
mTmpChildRect.right, mTmpChildRect.bottom);
}
// Layout the icon to the bottom-end corner of the view
if (mIconView.getVisibility() != GONE) {
final int width = mIconView.getMeasuredWidth();
final int height = mIconView.getMeasuredHeight();
int thumbnailGravity = Gravity.BOTTOM | Gravity.END;
Gravity.apply(thumbnailGravity, width, height, mTmpFrameRect, mTmpChildRect);
mIconView.layout(mTmpChildRect.left, mTmpChildRect.top,
mTmpChildRect.right, mTmpChildRect.bottom);
}
}
}