Merging from ub-launcher3-master @ build 6075050

Test: manual, presubmit on the source branch
http://x20/teams/android-launcher/merge/ub-launcher3-master_6075050.html

Change-Id: Iefe48be1562428d98ebf08c5f68a01856dbefad2
This commit is contained in:
Winson Chung
2019-12-15 21:57:02 -08:00
169 changed files with 1643 additions and 5699 deletions
+1 -47
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 := \
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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"/>
@@ -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>
@@ -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>
-20
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>
-30
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>
-28
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>
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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()]);
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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();
}
}
}
}
@@ -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() { }
}
@@ -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();
}
}
@@ -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.
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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;
}
}
}
@@ -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();
}
}
@@ -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();
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -1,176 +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.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Region;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.view.MotionEvent;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.util.DefaultDisplay;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.SystemUiProxy;
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;
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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);
}
}
}
@@ -32,6 +32,7 @@ import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.allapps.AllAppsStore;
@@ -45,11 +46,13 @@ import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;
/**
* Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
@@ -73,6 +76,7 @@ public class HotseatPredictionController implements DragController.DragListener,
private DropTarget.DragObject mDragObject;
private int mHotSeatItemsCount;
private int mPredictedSpotsCount = 0;
private Launcher mLauncher;
private Hotseat mHotseat;
@@ -86,6 +90,8 @@ public class HotseatPredictionController implements DragController.DragListener,
private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
private static HotseatPredictionController sInstance;
public HotseatPredictionController(Launcher launcher) {
mLauncher = launcher;
mHotseat = launcher.getHotseat();
@@ -95,6 +101,7 @@ public class HotseatPredictionController implements DragController.DragListener,
mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
launcher.getDeviceProfile().inv.addOnChangeListener(this);
mHotseat.addOnAttachStateChangeListener(this);
sInstance = this;
}
@Override
@@ -135,7 +142,7 @@ public class HotseatPredictionController implements DragController.DragListener,
}
WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
if (isPredictedIcon(child)) {
if (isPredictedIcon(child) && child.isEnabled()) {
PredictedAppIcon icon = (PredictedAppIcon) child;
icon.applyFromWorkspaceItem(predictedItem);
icon.finishBinding();
@@ -144,6 +151,7 @@ public class HotseatPredictionController implements DragController.DragListener,
}
preparePredictionInfo(predictedItem, rank);
}
mPredictedSpotsCount = predictionIndex;
bindItems(newItems, animate, callback);
}
@@ -310,6 +318,7 @@ public class HotseatPredictionController implements DragController.DragListener,
int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
icon.setEnabled(false);
icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
@@ -420,6 +429,26 @@ public class HotseatPredictionController implements DragController.DragListener,
}
}
/**
* Fill in predicted_rank field based on app prediction.
* Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
*/
public static void fillInHybridHotseatRank(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
if (sInstance == null || itemInfo.getTargetComponent() == null
|| itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
return;
}
final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List<ComponentKeyMapper> predictedApps = sInstance.mComponentKeyMappers;
IntStream.range(0, predictedApps.size())
.filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
.findFirst()
.ifPresent((rank) -> target.predictedRank =
Integer.parseInt(sInstance.mPredictedSpotsCount + "0" + rank));
}
private static boolean isPredictedIcon(View view) {
return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) view.getTag()).container
@@ -24,6 +24,7 @@ import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.drawable.ShapeDrawable;
import android.os.Handler;
import android.os.UserManager;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
@@ -33,6 +34,8 @@ import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
@@ -41,13 +44,10 @@ import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.TriangleShape;
import com.android.systemui.shared.system.LauncherEventUtil;
import androidx.core.content.ContextCompat;
/**
* All apps tip view aligned just above prediction apps, shown to users that enter all apps for the
* first time.
@@ -151,7 +151,7 @@ public class AllAppsTipView extends AbstractFloatingView {
TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE) != null
|| !launcher.isInState(ALL_APPS)
|| hasSeenAllAppsTip(launcher)
|| UserManagerCompat.getInstance(launcher).isDemoUser()
|| launcher.getSystemService(UserManager.class).isDemoUser()
|| Utilities.IS_RUNNING_IN_TEST_HARNESS) {
return false;
}
@@ -26,6 +26,7 @@ import android.content.Context;
import androidx.annotation.NonNull;
import com.android.launcher3.HotseatPredictionController;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.ItemInfo;
@@ -316,6 +317,11 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe
&& itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
return;
}
if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
HotseatPredictionController.fillInHybridHotseatRank(itemInfo, target);
return;
}
final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List<ComponentKeyMapper> predictedApps = manager.getCurrentState().apps;
IntStream.range(0, predictedApps.size())
@@ -98,6 +98,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
applyFromWorkspaceItem(info);
setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
mIsPinned = true;
((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
invalidate();
}
@@ -112,6 +113,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
}
return true;
});
((CellLayout.LayoutParams) getLayoutParams()).canReorder = false;
setTextVisibility(false);
verifyHighRes();
}
@@ -64,7 +64,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
*/
public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
public static final RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
@Override
public void mapRect(int left, int top, int right, int bottom, Rect out) {
out.left = top;
@@ -90,7 +90,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
}
}
};
public static RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
public static final RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
@Override
public void mapRect(int left, int top, int right, int bottom, Rect out) {
out.left = bottom;
@@ -44,6 +44,7 @@ import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
@@ -140,6 +141,10 @@ public class OverviewState extends LauncherState {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
} else {
if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
}
boolean hasAllAppsHeaderExtra = launcher.getAppsView() != null
&& launcher.getAppsView().getFloatingHeaderView().hasVisibleContent();
return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON |
@@ -430,20 +430,6 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
anim.addOnUpdateListener(new RectFSpringAnim.OnUpdateListener() {
// Alpha interpolates between [1, 0] between progress values [start, end]
final float start = 0f;
final float end = 0.85f;
private float getWindowAlpha(float progress) {
if (progress <= start) {
return 1f;
}
if (progress >= end) {
return 0f;
}
return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
}
@Override
public void onUpdate(RectF currentRect, float progress) {
homeAnim.setPlayFraction(progress);
@@ -484,6 +470,24 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
return anim;
}
/**
* @param progress The progress of the animation to the home screen.
* @return The current alpha to set on the animating app window.
*/
protected float getWindowAlpha(float progress) {
// Alpha interpolates between [1, 0] between progress values [start, end]
final float start = 0f;
final float end = 0.85f;
if (progress <= start) {
return 1f;
}
if (progress >= end) {
return 0f;
}
return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
}
public interface Factory {
BaseSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs,
@@ -15,6 +15,8 @@
*/
package com.android.quickstep;
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -34,6 +36,7 @@ import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Bundle;
import android.util.ArrayMap;
import android.view.MotionEvent;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -86,7 +89,7 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, Fa
mLauncherAlpha = launcherAlpha;
}
}
private static ArrayMap<GestureEndTarget, EndTargetAnimationParams>
private final ArrayMap<GestureEndTarget, EndTargetAnimationParams>
mEndTargetAnimationParams = new ArrayMap();
private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
@@ -97,6 +100,7 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, Fa
private final boolean mContinuingLastGesture;
private final boolean mRunningOverHome;
private final boolean mSwipeUpOverHome;
private boolean mTouchedHomeDuringTransition;
private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
private RunningWindowAnim mFinishAnimation;
@@ -105,13 +109,14 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, Fa
GestureState gestureState, InputConsumerController inputConsumer,
boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
super(context, deviceState, gestureState, inputConsumer);
mLauncherAlpha.value = 1;
mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
mContinuingLastGesture = continuingLastGesture;
mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
// Keep the home launcher invisible until we decide to land there.
mLauncherAlpha.value = mRunningOverHome ? 1 : 0;
if (mSwipeUpOverHome) {
mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
} else {
@@ -198,15 +203,27 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, Fa
@Override
protected InputConsumer createNewInputProxyHandler() {
// Just consume all input on the active task
return InputConsumer.NO_OP;
return new InputConsumer() {
@Override
public int getType() {
return InputConsumer.TYPE_NO_OP;
}
@Override
public void onMotionEvent(MotionEvent ev) {
mTouchedHomeDuringTransition = true;
}
};
}
@Override
public void onMotionPauseChanged(boolean isPaused) {
if (!mInQuickSwitchMode) {
mIsMotionPaused = isPaused;
mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
.setDuration(150).start();
if (mSwipeUpOverHome) {
mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
.setDuration(150).start();
}
performHapticFeedback();
}
}
@@ -266,7 +283,7 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, Fa
@Override
public void onConsumerAboutToBeSwitched() {
if (mInQuickSwitchMode && mGestureState.getEndTarget() != null) {
mGestureState.setEndTarget(HOME);
mGestureState.setEndTarget(NEW_TASK);
mCanceled = true;
mCurrentShift.cancelAnimation();
@@ -315,7 +332,14 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, Fa
// Send a home intent to clear the task stack
mContext.startActivity(mGestureState.getHomeIntent());
} else {
mRecentsAnimationController.finish(true, null, true);
mRecentsAnimationController.finish(true, () -> {
if (!mTouchedHomeDuringTransition) {
// If the user hasn't interacted with the screen during the transition,
// send a home intent so launcher can go to the default home screen.
// (If they are trying to touch something, we don't want to interfere.)
mContext.startActivity(mGestureState.getHomeIntent());
}
}, true);
}
break;
}
@@ -389,6 +413,8 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, Fa
};
if (mGestureState.getEndTarget() == HOME && !mRunningOverHome) {
mRecentsAnimationController.enableInputProxy(mInputConsumer,
this::createNewInputProxyHandler);
RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
anim.addAnimatorListener(endListener);
anim.start(mEndVelocityPxPerMs);
@@ -445,11 +471,18 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, Fa
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
AnimatorSet anim = new AnimatorSet();
anim.play(mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1));
Animator fadeInLauncher = mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1);
fadeInLauncher.setInterpolator(ACCEL_2);
anim.play(fadeInLauncher);
anim.setDuration(duration);
return AnimatorPlaybackController.wrap(anim, duration);
}
};
return createWindowAnimationToHome(startProgress, factory);
}
@Override
protected float getWindowAlpha(float progress) {
return 1 - ACCEL_1_5.getInterpolation(progress);
}
}
@@ -544,7 +544,7 @@ public class LauncherSwipeHandler<T extends BaseDraggingActivity>
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (mRecentsAnimationTargets != null) {
LiveTileOverlay.getInstance().update(
LiveTileOverlay.INSTANCE.update(
mAppWindowAnimationHelper.getCurrentRectWithInsets(),
mAppWindowAnimationHelper.getCurrentCornerRadius());
}
@@ -829,7 +829,7 @@ public class LauncherSwipeHandler<T extends BaseDraggingActivity>
setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
} else if (endTarget == RECENTS) {
LiveTileOverlay.getInstance().startIconAnimation();
LiveTileOverlay.INSTANCE.startIconAnimation();
if (mRecentsView != null) {
int nearestPage = mRecentsView.getPageNearestToCenterOfScreen();
if (mRecentsView.getNextPage() != nearestPage) {
@@ -1231,13 +1231,13 @@ public class LauncherSwipeHandler<T extends BaseDraggingActivity>
}
private void addLiveTileOverlay() {
if (LiveTileOverlay.getInstance().attach(mActivity.getRootView().getOverlay())) {
if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
mRecentsView.setLiveTileOverlayAttached(true);
}
}
private void removeLiveTileOverlay() {
LiveTileOverlay.getInstance().detach(mActivity.getRootView().getOverlay());
LiveTileOverlay.INSTANCE.detach(mActivity.getRootView().getOverlay());
mRecentsView.setLiveTileOverlayAttached(false);
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
import android.view.View;
import com.android.launcher3.R;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
/**
* Overview actions are shown in overview underneath the task snapshot. This factory class is
* overrideable in an overlay. The {@link OverviewActions} class provides the view that should be
* shown in the Overview.
*/
public class OverviewActionsFactory implements ResourceBasedOverride {
public static final MainThreadInitializedObject<OverviewActionsFactory> INSTANCE =
forOverride(OverviewActionsFactory.class, R.string.overview_actions_factory_class);
/** Create a new Overview Actions for interacting between the actions and overview. */
public OverviewActions createOverviewActions() {
return new OverviewActions();
}
/** Overlay overrideable, base class does nothing. */
public static class OverviewActions {
/** Get the view to show in the overview. */
public View getView() {
return null;
}
}
}
@@ -18,6 +18,7 @@ package com.android.quickstep;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -38,6 +39,7 @@ import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAnimationRunner;
import com.android.launcher3.R;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.util.ObjectWrapper;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.fallback.FallbackRecentsView;
@@ -217,6 +219,12 @@ public final class RecentsActivity extends BaseRecentsActivity {
mFallbackRecentsView.reset();
}
@Override
protected void onResume() {
super.onResume();
AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL);
}
public void onTaskLaunched() {
mFallbackRecentsView.resetTaskVisuals();
}
@@ -559,10 +559,13 @@ public class TouchInteractionService extends Service implements PluginListener<O
final boolean shouldDefer;
final BaseSwipeUpHandler.Factory factory;
if (mDeviceState.isFullyGesturalNavMode()
&& !mOverviewComponentObserver.isHomeAndOverviewSame()) {
if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
shouldDefer = previousGestureState.getFinishingRecentsAnimationTaskId() < 0;
factory = mFallbackSwipeHandlerFactory;
if (mDeviceState.isFullyGesturalNavMode()) {
factory = mFallbackSwipeHandlerFactory;
} else {
factory = this::determineFallbackTwoButtonSwipeHandler;
}
} else {
shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState,
event);
@@ -575,6 +578,23 @@ public class TouchInteractionService extends Service implements PluginListener<O
mInputMonitorCompat, disableHorizontalSwipe, factory);
}
/**
* Determines whether to use the LauncherSwipeHandler or FallbackSwipeHandler at runtime.
* We need to use the FallbackSwipeHandler to handle quick switch from home, otherwise the
* normal LauncherSwipeHandler works.
*/
private BaseSwipeUpHandler determineFallbackTwoButtonSwipeHandler(GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
boolean runningOverHome = gestureState.getRunningTask() == null
|| ActivityManagerWrapper.isHomeTask(gestureState.getRunningTask());
boolean isQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
BaseSwipeUpHandler.Factory factory = runningOverHome && isQuickSwitchMode
? mFallbackSwipeHandlerFactory
: mLauncherSwipeHandlerFactory;
return factory.newHandler(gestureState, touchTimeMs, continuingLastGesture,
isLikelyToStartNewTask);
}
private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) {
if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) {
return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager,
@@ -37,18 +37,11 @@ public class LiveTileOverlay extends Drawable {
}
};
private static LiveTileOverlay sInstance;
public static LiveTileOverlay getInstance() {
if (sInstance == null) {
sInstance = new LiveTileOverlay();
}
return sInstance;
}
public static final LiveTileOverlay INSTANCE = new LiveTileOverlay();
private final Paint mPaint = new Paint();
private final Rect mBoundsRect = new Rect();
private Rect mBoundsRect = new Rect();
private RectF mCurrentRect;
private float mCornerRadius;
private Drawable mIcon;
@@ -110,6 +110,7 @@ import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.ViewUtils;
import com.android.quickstep.util.AppWindowAnimationHelper;
import com.android.quickstep.util.LayoutUtils;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -343,8 +344,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
mTaskTopMargin = getResources()
.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
mTaskBottomMargin = getResources().getDimensionPixelSize(
R.dimen.task_thumbnail_bottom_margin);
mTaskBottomMargin = LayoutUtils.thumbnailBottomMargin(getResources());
mSquaredTouchSlop = squaredTouchSlop(context);
mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
@@ -885,7 +885,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
public void onSwipeUpAnimationSuccess() {
if (getRunningTaskView() != null) {
float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlayAttached
? LiveTileOverlay.getInstance().cancelIconAnimation()
? LiveTileOverlay.INSTANCE.cancelIconAnimation()
: 0f;
animateUpRunningTaskIconScale(startProgress);
}
@@ -1738,7 +1738,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
public void updateLiveTileIcon(Drawable icon) {
if (mLiveTileOverlayAttached) {
LiveTileOverlay.getInstance().setIcon(icon);
LiveTileOverlay.INSTANCE.setIcon(icon);
}
}
@@ -61,9 +61,9 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
*/
public class TaskThumbnailView extends View implements PluginListener<OverviewScreenshotActions> {
private final static ColorMatrix COLOR_MATRIX = new ColorMatrix();
private final static ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
private final static RectF EMPTY_RECT_F = new RectF();
private static final ColorMatrix COLOR_MATRIX = new ColorMatrix();
private static final ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
private static final RectF EMPTY_RECT_F = new RectF();
public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
new FloatProperty<TaskThumbnailView>("dimAlpha") {
@@ -47,11 +47,14 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -59,11 +62,13 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.OverviewActionsFactory;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.TaskCornerRadius;
import com.android.quickstep.views.RecentsView.PageCallbacks;
import com.android.quickstep.views.RecentsView.ScrollState;
@@ -159,6 +164,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
private final float mWindowCornerRadius;
private final BaseDraggingActivity mActivity;
private OverviewActionsFactory.OverviewActions mOverviewActions;
@Nullable private View mActionsView;
private ObjectAnimator mIconAndDimAnimator;
private float mIconScaleAnimStartProgress = 0;
private float mFocusTransitionProgress = 1;
@@ -214,6 +222,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius);
mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
mOverviewActions = OverviewActionsFactory.INSTANCE.get(context).createOverviewActions();
mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams);
setOutlineProvider(mOutlineProvider);
}
@@ -223,6 +232,21 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
super.onFinishInflate();
mSnapshotView = findViewById(R.id.snapshot);
mIconView = findViewById(R.id.icon);
TaskView.LayoutParams thumbnailParams = (LayoutParams) mSnapshotView.getLayoutParams();
thumbnailParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(getResources());
mSnapshotView.setLayoutParams(thumbnailParams);
if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
mActionsView = mOverviewActions.getView();
if (mActionsView != null) {
TaskView.LayoutParams params = new TaskView.LayoutParams(LayoutParams.MATCH_PARENT,
getResources().getDimensionPixelSize(R.dimen.overview_actions_height),
Gravity.BOTTOM);
addView(mActionsView, params);
}
}
}
public TaskMenuView getMenuView() {
@@ -422,6 +446,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
mIconView.setScaleX(scale);
mIconView.setScaleY(scale);
if (mActionsView != null) {
mActionsView.setAlpha(scale);
}
mFooterVerticalOffset = 1.0f - scale;
for (FooterWrapper footer : mFooters) {
if (footer != null) {
@@ -626,7 +654,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) {
mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
mMarginBottom = res.getDimensionPixelSize(R.dimen.task_thumbnail_bottom_margin);
mMarginBottom = LayoutUtils.thumbnailBottomMargin(res);
mFullscreenParams = fullscreenParams;
}
@@ -783,6 +811,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
/**
* Hides the icon and shows insets when this TaskView is about to be shown fullscreen.
*
* @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
*/
public void setFullscreenProgress(float progress) {
@@ -793,6 +822,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
mFullscreenProgress = progress;
boolean isFullscreen = mFullscreenProgress > 0;
mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
if (mActionsView != null) {
mActionsView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
}
setClipChildren(!isFullscreen);
setClipToPadding(!isFullscreen);
@@ -873,4 +905,5 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
mScale = scale;
}
}
}
+1 -2
View File
@@ -25,8 +25,7 @@
android:id="@+id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/task_thumbnail_top_margin"
android:layout_marginBottom="@dimen/task_thumbnail_bottom_margin"/>
android:layout_marginTop="@dimen/task_thumbnail_top_margin"/>
<com.android.quickstep.views.IconView
android:id="@+id/icon"
+2
View File
@@ -15,6 +15,8 @@
-->
<resources>
<string name="task_overlay_factory_class" translatable="false"></string>
<!-- Class name for factory object that creates the overview actions UI when enabled. -->
<string name="overview_actions_factory_class" translatable="false" />
<!-- Activity which blocks home gesture -->
<string name="gesture_blocking_activity" translatable="false"></string>
+4 -2
View File
@@ -18,12 +18,13 @@
<dimen name="task_thumbnail_top_margin">24dp</dimen>
<dimen name="task_thumbnail_half_top_margin">12dp</dimen>
<!-- Can be overridden in overlays. -->
<dimen name="task_thumbnail_bottom_margin">0dp</dimen>
<dimen name="task_thumbnail_icon_size">48dp</dimen>
<!-- For screens without rounded corners -->
<dimen name="task_corner_radius_small">2dp</dimen>
<!-- Overrideable in overlay that provides the Overview Actions. -->
<dimen name="overview_actions_height">0dp</dimen>
<dimen name="recents_page_spacing">10dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
<dimen name="overview_peek_distance">96dp</dimen>
@@ -58,6 +59,7 @@
<dimen name="task_card_menu_shadow_height">3dp</dimen>
<dimen name="task_card_menu_horizontal_padding">0dp</dimen>
<dimen name="portrait_task_card_horz_space">136dp</dimen>
<dimen name="portrait_task_card_horz_space_big_overview">24dp</dimen>
<dimen name="landscape_task_card_horz_space">200dp</dimen>
<dimen name="multi_window_task_card_horz_space">100dp</dimen>
<!-- Copied from framework resource:
@@ -265,7 +265,7 @@ public abstract class BaseQuickstepLauncher extends Launcher
// Instantiate and initialize WellbeingModel now that its loading won't interfere with
// populating workspace.
// TODO: Find a better place for this
WellbeingModel.get(this);
WellbeingModel.INSTANCE.get(this);
}
@Override
@@ -43,13 +43,13 @@ import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.popup.RemoteActionShortcut;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -78,7 +78,8 @@ public final class WellbeingModel {
private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
private static final String EXTRA_PACKAGES = "packages";
private static WellbeingModel sInstance;
public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
new MainThreadInitializedObject<>(WellbeingModel::new);
private final Context mContext;
private final String mWellbeingProviderPkg;
@@ -155,15 +156,6 @@ public final class WellbeingModel {
updateWellbeingData();
}
@MainThread
public static WellbeingModel get(@NonNull Context context) {
Preconditions.assertUIThread();
if (sInstance == null) {
sInstance = new WellbeingModel(context.getApplicationContext());
}
return sInstance;
}
@MainThread
private SystemShortcut getShortcutForApp(String packageName, int userId,
BaseDraggingActivity activity, ItemInfo info) {
@@ -334,8 +326,8 @@ public final class WellbeingModel {
/**
* Shortcut factory for generating wellbeing action
*/
public static final SystemShortcut.Factory SHORTCUT_FACTORY = (activity, info) ->
(info.getTargetComponent() == null) ? null : WellbeingModel.get(activity)
public static final SystemShortcut.Factory SHORTCUT_FACTORY =
(activity, info) -> (info.getTargetComponent() == null) ? null : INSTANCE.get(activity)
.getShortcutForApp(
info.getTargetComponent().getPackageName(), info.user.getIdentifier(),
activity, info);
@@ -16,46 +16,13 @@
package com.android.launcher3.uioverrides;
import android.app.Activity;
import android.app.Person;
import android.content.pm.ShortcutInfo;
import android.util.Base64;
import com.android.launcher3.Utilities;
import com.android.systemui.shared.system.ActivityCompat;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.zip.Deflater;
public class ApiWrapper {
public static boolean dumpActivity(Activity activity, PrintWriter writer) {
if (!Utilities.IS_DEBUG_DEVICE) {
return false;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
if (!(new ActivityCompat(activity).encodeViewHierarchy(out))) {
return false;
}
Deflater deflater = new Deflater();
deflater.setInput(out.toByteArray());
deflater.finish();
out.reset();
byte[] buffer = new byte[1024];
while (!deflater.finished()) {
int count = deflater.deflate(buffer); // returns the generated code... index
out.write(buffer, 0, count);
}
writer.println("--encoded-view-dump-v0--");
writer.println(Base64.encodeToString(
out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING));
return true;
}
public static Person[] getPersons(ShortcutInfo si) {
Person[] persons = si.getPersons();
return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
@@ -27,6 +27,7 @@ import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.shared.system.TonalCompat;
import com.android.systemui.shared.system.TonalCompat.ExtractionInfo;
@@ -39,17 +40,8 @@ public class WallpaperColorInfo implements OnColorsChangedListener {
private static final int MAIN_COLOR_DARK = 0xff202124;
private static final int MAIN_COLOR_REGULAR = 0xff000000;
private static final Object sInstanceLock = new Object();
private static WallpaperColorInfo sInstance;
public static WallpaperColorInfo getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
sInstance = new WallpaperColorInfo(context.getApplicationContext());
}
return sInstance;
}
}
public static final MainThreadInitializedObject<WallpaperColorInfo> INSTANCE =
new MainThreadInitializedObject<>(WallpaperColorInfo::new);
private final ArrayList<OnChangeListener> mListeners = new ArrayList<>();
private final WallpaperManager mWallpaperManager;
@@ -25,17 +25,18 @@ import android.util.FloatProperty;
*/
public class AnimatedFloat {
public static FloatProperty<AnimatedFloat> VALUE = new FloatProperty<AnimatedFloat>("value") {
@Override
public void setValue(AnimatedFloat obj, float v) {
obj.updateValue(v);
}
public static final FloatProperty<AnimatedFloat> VALUE =
new FloatProperty<AnimatedFloat>("value") {
@Override
public void setValue(AnimatedFloat obj, float v) {
obj.updateValue(v);
}
@Override
public Float get(AnimatedFloat obj) {
return obj.value;
}
};
@Override
public Float get(AnimatedFloat obj) {
return obj.value;
}
};
private final Runnable mUpdateCallback;
private ObjectAnimator mValueAnimator;
@@ -43,7 +43,8 @@ import java.io.PrintWriter;
*/
public abstract class BaseRecentsActivity extends BaseDraggingActivity {
public static ActivityTracker<BaseRecentsActivity> ACTIVITY_TRACKER = new ActivityTracker<>();
public static final ActivityTracker<BaseRecentsActivity> ACTIVITY_TRACKER =
new ActivityTracker<>();
private Configuration mOldConfig;
@Override
@@ -56,7 +57,7 @@ public abstract class BaseRecentsActivity extends BaseDraggingActivity {
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
ACTIVITY_TRACKER.handleCreate((RecentsActivity) this);
ACTIVITY_TRACKER.handleCreate(this);
}
/**
@@ -17,10 +17,8 @@ package com.android.quickstep;
import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
import static com.android.launcher3.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE;
import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@@ -42,12 +40,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Process;
import android.provider.Settings;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.MotionEvent;
@@ -58,7 +55,6 @@ import androidx.annotation.BinderThread;
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DefaultDisplay;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
@@ -79,7 +75,6 @@ public class RecentsAnimationDeviceState implements
DefaultDisplay.DisplayInfoChangeListener {
private final Context mContext;
private final UserManagerCompat mUserManager;
private final SysUINavigationMode mSysUiNavMode;
private final DefaultDisplay mDefaultDisplay;
private final int mDisplayId;
@@ -117,14 +112,14 @@ public class RecentsAnimationDeviceState implements
public RecentsAnimationDeviceState(Context context) {
final ContentResolver resolver = context.getContentResolver();
mContext = context;
mUserManager = UserManagerCompat.getInstance(context);
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
mDisplayId = mDefaultDisplay.getInfo().id;
runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this));
// Register for user unlocked if necessary
mIsUserUnlocked = mUserManager.isUserUnlocked(Process.myUserHandle());
mIsUserUnlocked = context.getSystemService(UserManager.class)
.isUserUnlocked(Process.myUserHandle());
if (!mIsUserUnlocked) {
mContext.registerReceiver(mUserUnlockedReceiver,
new IntentFilter(ACTION_USER_UNLOCKED));
@@ -52,7 +52,7 @@ public class SysUINavigationMode {
return INSTANCE.get(context).getMode();
}
public static MainThreadInitializedObject<SysUINavigationMode> INSTANCE =
public static final MainThreadInitializedObject<SysUINavigationMode> INSTANCE =
new MainThreadInitializedObject<>(SysUINavigationMode::new);
private static final String TAG = "SysUINavigationMode";
@@ -23,7 +23,7 @@ import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.Log;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.systemui.shared.recents.model.Task;
@@ -78,7 +78,7 @@ public final class TaskUtils {
if (currentUserId == UserHandle.myUserId()) {
return true;
}
List<UserHandle> allUsers = UserManagerCompat.getInstance(context).getUserProfiles();
List<UserHandle> allUsers = UserCache.INSTANCE.get(context).getUserProfiles();
for (int i = allUsers.size() - 1; i >= 0; i--) {
if (currentUserId == allUsers.get(i).getIdentifier()) {
return true;
@@ -26,6 +26,7 @@ import androidx.annotation.IntDef;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.SysUINavigationMode;
import java.lang.annotation.Retention;
@@ -57,9 +58,16 @@ public class LayoutUtils {
} else {
Resources res = context.getResources();
extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
//TODO: this needs to account for the swipe gesture height and accessibility
// UI when shown.
extraSpace = 0;
} else {
extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
+ res.getDimensionPixelSize(
R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
}
}
calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
}
@@ -99,13 +107,20 @@ public class LayoutUtils {
} else {
taskWidth = dp.availableWidthPx;
taskHeight = dp.availableHeightPx;
paddingHorz = res.getDimension(dp.isVerticalBarLayout()
? R.dimen.landscape_task_card_horz_space
: R.dimen.portrait_task_card_horz_space);
final int paddingResId;
if (dp.isVerticalBarLayout()) {
paddingResId = R.dimen.landscape_task_card_horz_space;
} else if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
} else {
paddingResId = R.dimen.portrait_task_card_horz_space;
}
paddingHorz = res.getDimension(paddingResId);
}
float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
float bottomMargin = res.getDimension(R.dimen.task_thumbnail_bottom_margin);
float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
float bottomMargin = thumbnailBottomMargin(res);
float paddingVert = res.getDimension(R.dimen.task_card_vert_space);
// Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
@@ -136,4 +151,16 @@ public class LayoutUtils {
R.dimen.task_card_vert_space);
return shelfHeight + spaceBetweenShelfAndRecents;
}
/**
* Get the margin that the task thumbnail view should use.
* @return the margin in pixels.
*/
public static int thumbnailBottomMargin(Resources resources) {
if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
return resources.getDimensionPixelSize(R.dimen.overview_actions_height);
} else {
return 0;
}
}
}
@@ -31,7 +31,7 @@ import com.android.quickstep.SysUINavigationMode;
*/
public class NavBarPosition {
public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
public static final RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
@Override
public void mapRect(int left, int top, int right, int bottom, Rect out) {
out.left = top;
@@ -58,7 +58,7 @@ public class NavBarPosition {
}
};
public static RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
public static final RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
@Override
public void mapRect(int left, int top, int right, int bottom, Rect out) {
out.left = bottom;
+2 -2
View File
@@ -27,7 +27,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
mockito-robolectric-prebuilt \
truth-prebuilt
LOCAL_JAVA_LIBRARIES := \
platform-robolectric-3.6.1-prebuilt
platform-robolectric-4.3-prebuilt
LOCAL_JAVA_RESOURCE_DIRS := resources config
@@ -54,4 +54,4 @@ LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src \
LOCAL_ROBOTEST_TIMEOUT := 36000
include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
include prebuilts/misc/common/robolectric/4.3/run_robotests.mk
@@ -1,5 +1,7 @@
package com.android.launcher3.model;
import static com.android.launcher3.shadows.ShadowLooperExecutor.reinitializeStaticExecutors;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
@@ -29,6 +31,7 @@ import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.cache.CachingLogic;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.TestLauncherProvider;
@@ -53,7 +56,7 @@ import java.util.function.Supplier;
public class BaseModelUpdateTaskTestCase {
public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
private TestLauncherProvider mProvider;
public TestLauncherProvider provider;
public Context targetContext;
public UserHandle myUser;
@@ -71,9 +74,11 @@ public class BaseModelUpdateTaskTestCase {
@Before
public void setUp() throws Exception {
ShadowLog.stream = System.out;
reinitializeStaticExecutors();
InstallSessionHelper.INSTANCE.initializeForTesting(null);
mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
provider = Robolectric.setupContentProvider(TestLauncherProvider.class);
ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, provider);
callbacks = mock(Callbacks.class);
appState = mock(LauncherAppState.class);
@@ -0,0 +1,167 @@
/*
* 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.model;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.util.ReflectionHelpers.setField;
import android.content.ComponentName;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.net.Uri;
import android.provider.Settings;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.shadows.LShadowLauncherApps;
import com.android.launcher3.shadows.LShadowUserManager;
import com.android.launcher3.shadows.ShadowLooperExecutor;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;
import org.robolectric.shadows.ShadowPackageManager;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
* Tests for layout parser for remote layout
*/
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {LShadowUserManager.class, LShadowLauncherApps.class, ShadowLooperExecutor.class})
@LooperMode(Mode.PAUSED)
public class DefaultLayoutProviderTest extends BaseModelUpdateTaskTestCase {
private static final String SETTINGS_APP = "com.android.settings";
private static final String TEST_PROVIDER_AUTHORITY =
DefaultLayoutProviderTest.class.getName().toLowerCase();
private static final int BITMAP_SIZE = 10;
private static final int GRID_SIZE = 4;
@Before
public void setUp() throws Exception {
super.setUp();
InvariantDeviceProfile.INSTANCE.initializeForTesting(idp);
CustomWidgetManager.INSTANCE.initializeForTesting(mock(CustomWidgetManager.class));
idp.numRows = idp.numColumns = idp.numHotseatIcons = GRID_SIZE;
idp.iconBitmapSize = BITMAP_SIZE;
provider.setAllowLoadDefaultFavorites(true);
Settings.Secure.putString(targetContext.getContentResolver(),
"launcher3.layout.provider", TEST_PROVIDER_AUTHORITY);
ShadowPackageManager spm = shadowOf(targetContext.getPackageManager());
spm.addProviderIfNotPresent(new ComponentName("com.test", "Dummy")).authority =
TEST_PROVIDER_AUTHORITY;
spm.addActivityIfNotPresent(new ComponentName(SETTINGS_APP, SETTINGS_APP));
}
@After
public void cleanup() {
InvariantDeviceProfile.INSTANCE.initializeForTesting(null);
CustomWidgetManager.INSTANCE.initializeForTesting(null);
InstallSessionHelper.INSTANCE.initializeForTesting(null);
}
@Test
public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0)
.putApp(SETTINGS_APP, SETTINGS_APP));
// Verify one item in hotseat
assertEquals(1, bgDataModel.workspaceItems.size());
ItemInfo info = bgDataModel.workspaceItems.get(0);
assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, info.itemType);
}
@Test
public void testCustomProfileLoaded_with_folder() throws Exception {
writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
.addApp(SETTINGS_APP, SETTINGS_APP)
.addApp(SETTINGS_APP, SETTINGS_APP)
.addApp(SETTINGS_APP, SETTINGS_APP)
.build());
// Verify folder
assertEquals(1, bgDataModel.workspaceItems.size());
ItemInfo info = bgDataModel.workspaceItems.get(0);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
assertEquals(3, ((FolderInfo) info).contents.size());
}
@Test
public void testCustomProfileLoaded_with_widget() throws Exception {
String pendingAppPkg = "com.test.pending";
// Add a dummy session info so that the widget exists
SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(pendingAppPkg);
PackageInstaller installer = targetContext.getPackageManager().getPackageInstaller();
int sessionId = installer.createSession(params);
SessionInfo sessionInfo = installer.getSessionInfo(sessionId);
setField(sessionInfo, "installerPackageName", "com.test");
setField(sessionInfo, "appIcon", BitmapInfo.LOW_RES_ICON);
writeLayoutAndLoad(new LauncherLayoutBuilder().atWorkspace(0, 1, 0)
.putWidget(pendingAppPkg, "DummyWidget", 2, 2));
// Verify widget
assertEquals(1, bgDataModel.appWidgets.size());
ItemInfo info = bgDataModel.appWidgets.get(0);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, info.itemType);
assertEquals(2, info.spanX);
assertEquals(2, info.spanY);
}
private void writeLayoutAndLoad(LauncherLayoutBuilder builder) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
builder.build(new OutputStreamWriter(bos));
Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, targetContext);
shadowOf(targetContext.getContentResolver()).registerInputStream(layoutUri,
new ByteArrayInputStream(bos.toByteArray()));
LoaderResults results = new LoaderResults(appState, bgDataModel, allAppsList, 0,
new WeakReference<>(callbacks));
LoaderTask task = new LoaderTask(appState, allAppsList, bgDataModel, results);
Executors.MODEL_EXECUTOR.submit(() -> task.loadWorkspace(new ArrayList<>())).get();
}
}
@@ -0,0 +1,96 @@
/*
* 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.shadows;
import static org.robolectric.util.ReflectionHelpers.ClassParameter;
import static org.robolectric.util.ReflectionHelpers.callConstructor;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.os.Process;
import android.os.UserHandle;
import android.util.ArraySet;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowLauncherApps;
import java.util.Collections;
import java.util.List;
/**
* Extension of {@link ShadowLauncherApps} with missing shadow methods
*/
@Implements(value = LauncherApps.class)
public class LShadowLauncherApps extends ShadowLauncherApps {
public final ArraySet<PackageUserKey> disabledApps = new ArraySet<>();
public final ArraySet<ComponentKey> disabledActivities = new ArraySet<>();
@Implementation
@Override
protected List<ShortcutInfo> getShortcuts(LauncherApps.ShortcutQuery query, UserHandle user) {
try {
return super.getShortcuts(query, user);
} catch (UnsupportedOperationException e) {
return Collections.emptyList();
}
}
@Implementation
protected boolean isPackageEnabled(String packageName, UserHandle user) {
return !disabledApps.contains(new PackageUserKey(packageName, user));
}
@Implementation
protected boolean isActivityEnabled(ComponentName component, UserHandle user) {
return !disabledActivities.contains(new ComponentKey(component, user));
}
@Implementation
protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
ResolveInfo ri = RuntimeEnvironment.application.getPackageManager()
.resolveActivity(intent, 0);
return getLauncherActivityInfo(ri.activityInfo);
}
public LauncherActivityInfo getLauncherActivityInfo(ActivityInfo activityInfo) {
return callConstructor(LauncherActivityInfo.class,
ClassParameter.from(Context.class, RuntimeEnvironment.application),
ClassParameter.from(ActivityInfo.class, activityInfo),
ClassParameter.from(UserHandle.class, Process.myUserHandle()));
}
@Implementation
public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user)
throws PackageManager.NameNotFoundException {
return RuntimeEnvironment.application.getPackageManager()
.getApplicationInfo(packageName, flags);
}
}
@@ -0,0 +1,53 @@
/*
* 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.shadows;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.SparseBooleanArray;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowUserManager;
/**
* Extension of {@link ShadowUserManager} with missing shadow methods
*/
@Implements(value = UserManager.class)
public class LShadowUserManager extends ShadowUserManager {
private final SparseBooleanArray mQuietUsers = new SparseBooleanArray();
private final SparseBooleanArray mLockedUsers = new SparseBooleanArray();
@Implementation
protected boolean isQuietModeEnabled(UserHandle userHandle) {
return mQuietUsers.get(userHandle.hashCode());
}
public void setQuietModeEnabled(UserHandle userHandle, boolean enabled) {
mQuietUsers.put(userHandle.hashCode(), enabled);
}
@Implementation
protected boolean isUserUnlocked(UserHandle userHandle) {
return !mLockedUsers.get(userHandle.hashCode());
}
public void setUserLocked(UserHandle userHandle, boolean enabled) {
mLockedUsers.put(userHandle.hashCode(), enabled);
}
}
@@ -0,0 +1,68 @@
/*
* 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.shadows;
import static com.android.launcher3.util.Executors.createAndStartNewLooper;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
import static org.robolectric.util.ReflectionHelpers.setField;
import android.os.Handler;
import android.os.Looper;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.LooperExecutor;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
/**
* Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
*/
@Implements(value = LooperExecutor.class, isInAndroidSdk = false)
public class ShadowLooperExecutor {
// Keep reference to all created Loopers so they can be torn down after test
private static Set<LooperExecutor> executors =
Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
@RealObject private LooperExecutor realExecutor;
@Implementation
protected void __constructor__(Looper looper) {
invokeConstructor(LooperExecutor.class, realExecutor, from(Looper.class, looper));
executors.add(realExecutor);
}
/**
* Re-initializes any executor which may have been reset when a test finished
*/
public static void reinitializeStaticExecutors() {
for (LooperExecutor executor : new ArrayList<>(executors)) {
setField(executor, "mHandler",
new Handler(createAndStartNewLooper(executor.getThread().getName())));
}
}
}
@@ -10,6 +10,8 @@ import com.android.launcher3.LauncherProvider;
*/
public class TestLauncherProvider extends LauncherProvider {
private boolean mAllowLoadDefaultFavorites;
@Override
public boolean onCreate() {
return true;
@@ -18,18 +20,26 @@ public class TestLauncherProvider extends LauncherProvider {
@Override
protected synchronized void createDbIfNotExists() {
if (mOpenHelper == null) {
mOpenHelper = new MyDatabaseHelper(getContext());
mOpenHelper = new MyDatabaseHelper(getContext(), mAllowLoadDefaultFavorites);
}
}
public void setAllowLoadDefaultFavorites(boolean allowLoadDefaultFavorites) {
mAllowLoadDefaultFavorites = allowLoadDefaultFavorites;
}
public SQLiteDatabase getDb() {
createDbIfNotExists();
return mOpenHelper.getWritableDatabase();
}
private static class MyDatabaseHelper extends DatabaseHelper {
public MyDatabaseHelper(Context context) {
private final boolean mAllowLoadDefaultFavorites;
MyDatabaseHelper(Context context, boolean allowLoadDefaultFavorites) {
super(context, null);
mAllowLoadDefaultFavorites = allowLoadDefaultFavorites;
initIds();
}
@@ -39,7 +49,11 @@ public class TestLauncherProvider extends LauncherProvider {
}
@Override
protected void onEmptyDbCreated() { }
protected void onEmptyDbCreated() {
if (mAllowLoadDefaultFavorites) {
super.onEmptyDbCreated();
}
}
@Override
protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
+4 -4
View File
@@ -24,8 +24,8 @@ import android.content.pm.LauncherActivityInfo;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
@@ -36,8 +36,8 @@ import java.util.Comparator;
*/
public class AppInfo extends ItemInfoWithIcon {
public static AppInfo[] EMPTY_ARRAY = new AppInfo[0];
public static Comparator<AppInfo> COMPONENT_KEY_COMPARATOR = (a, b) -> {
public static final AppInfo[] EMPTY_ARRAY = new AppInfo[0];
public static final Comparator<AppInfo> COMPONENT_KEY_COMPARATOR = (a, b) -> {
int uc = a.user.hashCode() - b.user.hashCode();
return uc != 0 ? uc : a.componentName.compareTo(b.componentName);
};
@@ -65,7 +65,7 @@ public class AppInfo extends ItemInfoWithIcon {
* Must not hold the Context.
*/
public AppInfo(Context context, LauncherActivityInfo info, UserHandle user) {
this(info, user, UserManagerCompat.getInstance(context).isQuietModeEnabled(user));
this(info, user, context.getSystemService(UserManager.class).isQuietModeEnabled(user));
}
public AppInfo(LauncherActivityInfo info, UserHandle user, boolean quietModeEnabled) {
@@ -22,6 +22,7 @@ import android.view.ViewGroup;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.FocusLogic;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import java.util.ArrayList;
@@ -35,7 +36,12 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O
private static final Rect sTmpRect = new Rect();
// Represents the cell size on the grid in the two orientations.
private static Point[] sCellSize;
private static final MainThreadInitializedObject<Point[]> CELL_SIZE =
new MainThreadInitializedObject<>(c -> {
InvariantDeviceProfile inv = LauncherAppState.getIDP(c);
return new Point[] {inv.landscapeProfile.getCellSize(),
inv.portraitProfile.getCellSize()};
});
private static final int HANDLE_COUNT = 4;
private static final int INDEX_LEFT = 0;
@@ -352,27 +358,19 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O
}
public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY, Rect rect) {
if (sCellSize == null) {
InvariantDeviceProfile inv = LauncherAppState.getIDP(context);
// Initiate cell sizes.
sCellSize = new Point[2];
sCellSize[0] = inv.landscapeProfile.getCellSize();
sCellSize[1] = inv.portraitProfile.getCellSize();
}
if (rect == null) {
rect = new Rect();
}
final float density = context.getResources().getDisplayMetrics().density;
final Point[] cellSize = CELL_SIZE.get(context);
// Compute landscape size
int landWidth = (int) ((spanX * sCellSize[0].x) / density);
int landHeight = (int) ((spanY * sCellSize[0].y) / density);
int landWidth = (int) ((spanX * cellSize[0].x) / density);
int landHeight = (int) ((spanY * cellSize[0].y) / density);
// Compute portrait size
int portWidth = (int) ((spanX * sCellSize[1].x) / density);
int portHeight = (int) ((spanY * sCellSize[1].y) / density);
int portWidth = (int) ((spanX * cellSize[1].x) / density);
int portHeight = (int) ((spanY * cellSize[1].y) / density);
rect.set(portWidth, landHeight, landWidth, portHeight);
return rect;
}
@@ -12,15 +12,15 @@ import android.content.Intent;
import android.database.Cursor;
import android.util.Log;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.util.ContentWriter;
import androidx.annotation.WorkerThread;
public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
private static final String TAG = "AWRestoredReceiver";
@@ -82,7 +82,7 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
// b/135926478: Work profile widget restore is broken in platform. This forces us to
// recreate the widget during loading with the correct host provider.
long mainProfileId = UserManagerCompat.getInstance(context)
long mainProfileId = UserCache.INSTANCE.get(context)
.getSerialNumberForUser(myUserHandle());
String oldWidgetId = Integer.toString(oldWidgetIds[i]);
int result = new ContentWriter(context, new ContentWriter.CommitParams(
@@ -35,13 +35,11 @@ import com.android.launcher3.logging.StatsLogUtils;
import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.ViewCache;
import com.android.launcher3.views.ActivityContext;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.util.ArrayList;
@@ -305,13 +303,6 @@ public abstract class BaseActivity extends Activity
void onMultiWindowModeChanged(boolean isInMultiWindowMode);
}
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
if (!ApiWrapper.dumpActivity(this, writer)) {
super.dump(prefix, fd, writer, args);
}
}
protected void dumpMisc(String prefix, PrintWriter writer) {
writer.println(prefix + "deviceProfile isTransposed="
+ getDeviceProfile().isVerticalBarLayout());
@@ -73,8 +73,7 @@ public abstract class BaseDraggingActivity extends BaseActivity
mRotationListener = new DisplayRotationListener(this, this::onDeviceRotationChanged);
// Update theme
WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this);
wallpaperColorInfo.addOnChangeListener(this);
WallpaperColorInfo.INSTANCE.get(this).addOnChangeListener(this);
int themeRes = Themes.getActivityThemeRes(this);
if (themeRes != mThemeRes) {
mThemeRes = themeRes;
@@ -234,7 +233,7 @@ public abstract class BaseDraggingActivity extends BaseActivity
@Override
protected void onDestroy() {
super.onDestroy();
WallpaperColorInfo.getInstance(this).removeOnChangeListener(this);
WallpaperColorInfo.INSTANCE.get(this).removeOnChangeListener(this);
mRotationListener.disable();
}
+1 -1
View File
@@ -179,7 +179,7 @@ public class CellLayout extends ViewGroup implements Transposable {
private final Rect mTempRect = new Rect();
private final static Paint sPaint = new Paint();
private static final Paint sPaint = new Paint();
// Related to accessible drag and drop
private DragAndDropAccessibilityDelegate mTouchHelper;
@@ -44,11 +44,11 @@ import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.PackageManagerHelper;
@@ -422,7 +422,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
.object()
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
.key(DEEPSHORTCUT_TYPE_KEY).value(true)
.key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
.key(USER_HANDLE_KEY).value(UserCache.INSTANCE.get(mContext)
.getSerialNumberForUser(user))
.endObject().toString();
} else if (providerInfo != null) {
@@ -432,7 +432,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
.object()
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
.key(APP_WIDGET_TYPE_KEY).value(true)
.key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
.key(USER_HANDLE_KEY).value(UserCache.INSTANCE.get(mContext)
.getSerialNumberForUser(user))
.endObject().toString();
}
@@ -460,7 +460,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
.key(NAME_KEY).value(name)
.key(USER_HANDLE_KEY).value(
UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user))
UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user))
.key(APP_SHORTCUT_TYPE_KEY).value(isActivity);
if (icon != null) {
byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon);
@@ -598,7 +598,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
private Decoder(String encoded, Context context) throws JSONException, URISyntaxException {
super(encoded);
launcherIntent = Intent.parseUri(getString(LAUNCH_INTENT_KEY), 0);
user = has(USER_HANDLE_KEY) ? UserManagerCompat.getInstance(context)
user = has(USER_HANDLE_KEY) ? UserCache.INSTANCE.get(context)
.getUserForSerialNumber(getLong(USER_HANDLE_KEY))
: Process.myUserHandle();
if (user == null) {
+7 -3
View File
@@ -95,7 +95,6 @@ import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
@@ -122,6 +121,7 @@ import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
@@ -157,6 +157,7 @@ import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetAddFlowHandler;
import com.android.launcher3.widget.WidgetHostViewLoader;
import com.android.launcher3.widget.WidgetListRowEntry;
import com.android.launcher3.widget.WidgetManagerHelper;
import com.android.launcher3.widget.WidgetsFullSheet;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import com.android.systemui.plugins.OverlayPlugin;
@@ -250,7 +251,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
DragLayer mDragLayer;
private DragController mDragController;
private AppWidgetManagerCompat mAppWidgetManager;
private WidgetManagerHelper mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
private final int[] mTmpAddItemCellCoordinates = new int[2];
@@ -360,7 +361,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
mAllAppsController = new AllAppsTransitionController(this);
mStateManager = new LauncherStateManager(this);
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
mAppWidgetManager = new WidgetManagerHelper(this);
mAppWidgetHost = new LauncherAppWidgetHost(this,
appWidgetId -> getWorkspace().removeWidget(appWidgetId));
mAppWidgetHost.startListening();
@@ -2636,6 +2637,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
// Setting the touch point to (-1, -1) will show the options popup in the center of
// the screen.
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up");
}
OptionsPopupView.showDefaultOptions(this, -1, -1);
}
return true;
@@ -27,14 +27,14 @@ import android.content.pm.LauncherApps;
import android.os.Handler;
import android.util.Log;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.InstallSessionTracker;
import com.android.launcher3.pm.PackageInstallerCompat;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SafeCloseable;
@@ -60,6 +60,7 @@ public class LauncherAppState {
private final InstallSessionTracker mInstallSessionTracker;
private final SimpleBroadcastReceiver mModelChangeReceiver;
private final SafeCloseable mCalendarChangeTracker;
private final SafeCloseable mUserChangeListener;
public static LauncherAppState getInstance(final Context context) {
return INSTANCE.get(context);
@@ -87,8 +88,6 @@ public class LauncherAppState {
mContext.getSystemService(LauncherApps.class).registerCallback(mModel);
mModelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_MANAGED_PROFILE_ADDED,
Intent.ACTION_MANAGED_PROFILE_REMOVED,
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
@@ -104,11 +103,13 @@ public class LauncherAppState {
CustomWidgetManager.INSTANCE.get(mContext)
.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
UserManagerCompat.getInstance(mContext).enableAndResetCache();
mUserChangeListener = UserCache.INSTANCE.get(mContext)
.addUserChangeListener(mModel::forceReload);
mInvariantDeviceProfile.addOnChangeListener(this::onIdpChanged);
new Handler().post( () -> mInvariantDeviceProfile.verifyConfigChangedInBackground(context));
mInstallSessionTracker = PackageInstallerCompat.getInstance(context)
mInstallSessionTracker = InstallSessionHelper.INSTANCE.get(context)
.registerInstallTracker(mModel, MODEL_EXECUTOR);
if (!mContext.getResources().getBoolean(R.bool.notification_dots_enabled)) {
@@ -151,6 +152,7 @@ public class LauncherAppState {
mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
mInstallSessionTracker.unregister();
mCalendarChangeTracker.close();
mUserChangeListener.close();
CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
if (mNotificationDotsObserver != null) {
+2 -6
View File
@@ -34,7 +34,6 @@ import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
@@ -54,6 +53,7 @@ import com.android.launcher3.model.ShortcutsChangedTask;
import com.android.launcher3.model.UserLockStateChangedTask;
import com.android.launcher3.pm.InstallSessionTracker;
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -234,10 +234,6 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
// If we have changed locale we need to clear out the labels in all apps/workspace.
forceReload();
} else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
|| Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
UserManagerCompat.getInstance(mApp.getContext()).enableAndResetCache();
forceReload();
} else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
@@ -462,7 +458,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
/**
* Refreshes the cached shortcuts if the shortcut permission has changed.
* Current implementation simply reloads the workspace, but it can be optimized to
* use partial updates similar to {@link UserManagerCompat}
* use partial updates similar to {@link UserCache}
*/
public void refreshShortcutsIfRequired() {
MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable);
+17 -14
View File
@@ -47,6 +47,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.BaseColumns;
import android.provider.Settings;
import android.text.TextUtils;
@@ -55,10 +56,10 @@ import android.util.Xml;
import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DbDowngradeHelper;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.provider.RestoreDbTask;
@@ -484,8 +485,6 @@ public class LauncherProvider extends ContentProvider {
*/
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
Context ctx = getContext();
InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx);
String authority = Settings.Secure.getString(ctx.getContentResolver(),
"launcher3.layout.provider");
if (TextUtils.isEmpty(authority)) {
@@ -497,13 +496,7 @@ public class LauncherProvider extends ContentProvider {
Log.e(TAG, "No provider found for authority " + authority);
return null;
}
Uri uri = new Uri.Builder().scheme("content").authority(authority).path("launcher_layout")
.appendQueryParameter("version", "1")
.appendQueryParameter("gridWidth", Integer.toString(grid.numColumns))
.appendQueryParameter("gridHeight", Integer.toString(grid.numRows))
.appendQueryParameter("hotseatSize", Integer.toString(grid.numHotseatIcons))
.build();
Uri uri = getLayoutUri(authority, ctx);
try (InputStream in = ctx.getContentResolver().openInputStream(uri)) {
// Read the full xml so that we fail early in case of any IO error.
String layout = new String(IOUtils.toByteArray(in));
@@ -520,12 +513,22 @@ public class LauncherProvider extends ContentProvider {
}
}
public static Uri getLayoutUri(String authority, Context ctx) {
InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx);
return new Uri.Builder().scheme("content").authority(authority).path("launcher_layout")
.appendQueryParameter("version", "1")
.appendQueryParameter("gridWidth", Integer.toString(grid.numColumns))
.appendQueryParameter("gridHeight", Integer.toString(grid.numRows))
.appendQueryParameter("hotseatSize", Integer.toString(grid.numHotseatIcons))
.build();
}
private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) {
InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
int defaultLayout = idp.defaultLayoutId;
UserManagerCompat um = UserManagerCompat.getInstance(getContext());
if (um.isDemoUser() && idp.demoModeLayoutId != 0) {
if (getContext().getSystemService(UserManager.class).isDemoUser()
&& idp.demoModeLayoutId != 0) {
defaultLayout = idp.demoModeLayoutId;
}
@@ -606,7 +609,7 @@ public class LauncherProvider extends ContentProvider {
}
public long getSerialNumberForUser(UserHandle user) {
return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user);
return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
}
public long getDefaultUserSerial() {
@@ -637,7 +640,7 @@ public class LauncherProvider extends ContentProvider {
*/
protected void handleOneTimeDataUpgrade(SQLiteDatabase db) {
// Remove "profile extra"
UserManagerCompat um = UserManagerCompat.getInstance(mContext);
UserCache um = UserCache.INSTANCE.get(mContext);
for (UserHandle user : um.getUserProfiles()) {
long serial = um.getSerialNumberForUser(user);
String sql = "update favorites set intent = replace(intent, "
+2 -11
View File
@@ -53,21 +53,12 @@ public class Partner {
public static final String RES_GRID_NUM_COLUMNS = "grid_num_columns";
public static final String RES_GRID_ICON_SIZE_DP = "grid_icon_size_dp";
private static boolean sSearched = false;
private static Partner sPartner;
/**
* Find and return partner details, or {@code null} if none exists.
*/
public static synchronized Partner get(PackageManager pm) {
if (!sSearched) {
Pair<String, Resources> apkInfo = findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm);
if (apkInfo != null) {
sPartner = new Partner(apkInfo.first, apkInfo.second);
}
sSearched = true;
}
return sPartner;
Pair<String, Resources> apkInfo = findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm);
return apkInfo != null ? new Partner(apkInfo.first, apkInfo.second) : null;
}
private final String mPackageName;
+12 -1
View File
@@ -29,7 +29,7 @@ public class ResourceUtils {
return getDimenByName(resName, res, 48);
}
private static int getDimenByName(String resName, Resources res, int defaultValue) {
public static int getDimenByName(String resName, Resources res, int defaultValue) {
final int frameSize;
final int frameSizeResID = res.getIdentifier(resName, "dimen", "android");
if (frameSizeResID != 0) {
@@ -40,6 +40,17 @@ public class ResourceUtils {
return frameSize;
}
public static boolean getBoolByName(String resName, Resources res, boolean defaultValue) {
final boolean val;
final int resId = res.getIdentifier(resName, "bool", "android");
if (resId != 0) {
val = res.getBoolean(resId);
} else {
val = defaultValue;
}
return val;
}
public static int pxFromDp(float size, DisplayMetrics metrics) {
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
}
@@ -16,7 +16,7 @@
package com.android.launcher3;
import static com.android.launcher3.pm.PackageInstallerCompat.getUserHandle;
import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
@@ -40,7 +40,7 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import com.android.launcher3.pm.PackageInstallerCompat;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.util.Executors;
import java.util.List;
@@ -77,7 +77,7 @@ public class SessionCommitReceiver extends BroadcastReceiver {
return;
}
PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context);
InstallSessionHelper packageInstallerCompat = InstallSessionHelper.INSTANCE.get(context);
if (TextUtils.isEmpty(info.getAppPackageName())
|| info.getInstallReason() != PackageManager.INSTALL_REASON_USER
|| packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) {
@@ -37,14 +37,13 @@ import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.ShadowGenerator;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.PackageUserKey;
@@ -52,6 +51,7 @@ import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SQLiteCacheHelper;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.ArrayList;
import java.util.Collections;
@@ -78,7 +78,7 @@ public class WidgetPreviewLoader {
private final Context mContext;
private final IconCache mIconCache;
private final UserManagerCompat mUserManager;
private final UserCache mUserCache;
private final CacheDb mDb;
private final UserHandle mMyUser = Process.myUserHandle();
@@ -87,7 +87,7 @@ public class WidgetPreviewLoader {
public WidgetPreviewLoader(Context context, IconCache iconCache) {
mContext = context;
mIconCache = iconCache;
mUserManager = UserManagerCompat.getInstance(context);
mUserCache = UserCache.INSTANCE.get(context);
mDb = new CacheDb(context);
}
@@ -197,7 +197,7 @@ public class WidgetPreviewLoader {
@Thunk void writeToDb(WidgetCacheKey key, long[] versions, Bitmap preview) {
ContentValues values = new ContentValues();
values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString());
values.put(CacheDb.COLUMN_USER, mUserManager.getSerialNumberForUser(key.user));
values.put(CacheDb.COLUMN_USER, mUserCache.getSerialNumberForUser(key.user));
values.put(CacheDb.COLUMN_SIZE, key.size);
values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName());
values.put(CacheDb.COLUMN_VERSION, versions[0]);
@@ -207,7 +207,7 @@ public class WidgetPreviewLoader {
}
public void removePackage(String packageName, UserHandle user) {
removePackage(packageName, user, mUserManager.getSerialNumberForUser(user));
removePackage(packageName, user, mUserCache.getSerialNumberForUser(user));
}
private void removePackage(String packageName, UserHandle user, long userSerial) {
@@ -236,7 +236,7 @@ public class WidgetPreviewLoader {
LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
for (ComponentKey key : list) {
final long userId = mUserManager.getSerialNumberForUser(key.user);
final long userId = mUserCache.getSerialNumberForUser(key.user);
HashSet<String> packages = validPackages.get(userId);
if (packages == null) {
packages = new HashSet<>();
@@ -247,7 +247,7 @@ public class WidgetPreviewLoader {
LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>();
long passedUserId = packageUser == null ? 0
: mUserManager.getSerialNumberForUser(packageUser.mUser);
: mUserCache.getSerialNumberForUser(packageUser.mUser);
Cursor c = null;
try {
c = mDb.query(
@@ -286,7 +286,7 @@ public class WidgetPreviewLoader {
for (int i = 0; i < packagesToDelete.size(); i++) {
long userId = packagesToDelete.keyAt(i);
UserHandle user = mUserManager.getUserForSerialNumber(userId);
UserHandle user = mUserCache.getUserForSerialNumber(userId);
for (String pkg : packagesToDelete.valueAt(i)) {
removePackage(pkg, user, userId);
}
@@ -312,7 +312,7 @@ public class WidgetPreviewLoader {
+ CacheDb.COLUMN_SIZE + " = ?",
new String[]{
key.componentName.flattenToShortString(),
Long.toString(mUserManager.getSerialNumberForUser(key.user)),
Long.toString(mUserCache.getSerialNumberForUser(key.user)),
key.size
});
// If cancelled, skip getting the blob and decoding it into a bitmap
@@ -364,7 +364,7 @@ public class WidgetPreviewLoader {
}
/**
* Generates the widget preview from either the {@link AppWidgetManagerCompat} or cache
* Generates the widget preview from either the {@link WidgetManagerHelper} or cache
* and add badge at the bottom right corner.
*
* @param launcher
+7 -6
View File
@@ -66,7 +66,6 @@ import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.DragController;
@@ -100,6 +99,7 @@ import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetManagerHelper;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
import java.util.ArrayList;
@@ -1641,7 +1641,9 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
return false;
}
boolean aboveShortcut = (dropOverView.getTag() instanceof WorkspaceItemInfo);
boolean aboveShortcut = (dropOverView.getTag() instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) dropOverView.getTag()).container
!= LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
boolean willBecomeShortcut =
(info.itemType == ITEM_TYPE_APPLICATION ||
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
@@ -3231,12 +3233,11 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
LauncherAppWidgetInfo item = changedInfo.get(0);
final AppWidgetProviderInfo widgetInfo;
WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext());
if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
widgetInfo = AppWidgetManagerCompat
.getInstance(mLauncher).findProvider(item.providerName, item.user);
widgetInfo = widgetHelper.findProvider(item.providerName, item.user);
} else {
widgetInfo = AppWidgetManagerCompat.getInstance(mLauncher)
.getLauncherAppWidgetInfo(item.appWidgetId);
widgetInfo = widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId);
}
if (widgetInfo != null) {
@@ -26,25 +26,25 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.List;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.List;
/**
* The grid view adapter of all the apps.
*/
@@ -312,7 +312,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
WorkModeSwitch workModeToggle = holder.itemView.findViewById(R.id.work_mode_toggle);
workModeToggle.refresh();
TextView managedByLabel = holder.itemView.findViewById(R.id.managed_by_label);
boolean anyProfileQuietModeEnabled = UserManagerCompat.getInstance(
boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get(
managedByLabel.getContext()).isAnyProfileQuietModeEnabled();
managedByLabel.setText(anyProfileQuietModeEnabled
? R.string.work_mode_off_label : R.string.work_mode_on_label);
@@ -20,7 +20,7 @@ import android.os.Process;
import android.os.UserHandle;
import com.android.launcher3.AppInfo;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.LabelComparator;
import java.util.Comparator;
@@ -30,12 +30,12 @@ import java.util.Comparator;
*/
public class AppInfoComparator implements Comparator<AppInfo> {
private final UserManagerCompat mUserManager;
private final UserCache mUserManager;
private final UserHandle mMyUser;
private final LabelComparator mLabelComparator;
public AppInfoComparator(Context context) {
mUserManager = UserManagerCompat.getInstance(context);
mUserManager = UserCache.INSTANCE.get(context);
mMyUser = Process.myUserHandle();
mLabelComparator = new LabelComparator();
}

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