Merging ub-launcher3-qt-dev, build 5505393
Test: Manual Bug:111411630 Dark theme popup blends in with the normal background Bug:113952590 [STOP SHIP BUG] WM Experiment: Slim the navigation bar Bug:114136250 Have a more spartan RecentsActivity on android go Bug:117106893 Enable all @Ignore-d Launcher tests Bug:118678948 A11Y: TalkBack: After adding shortcut app icon on the homescreen, sometimes shortcuts app icons are not accessible with TalkBack. Bug:122700646 Launcher crashed after uninstalling app Bug:123985787 Respect dialogCornerRadius for all apps / widgets / settings / shortcut menu / dwb / task / overview (everything) Bug:126229665 Launcher home screen should properly use Headline/ body font Bug:126450861 Tab structure update and tips for Theme Picker Bug:126587956 Create an app to use with Launcher testing Bug:126596502 [touchflow] Use platform long press in launcher Bug:126738409 Icon packs for launcher Bug:126822309 Launcher > Overview > "No recent item" is not using theme font Bug:127689526 [icons] hourglass_bottom and hourglass_top Bug:127840207 [logging] quickswitch usage Bug:128531133 Make TAPL diagnostics clearer Bug:128649657 Define overlayable.xml for icon packs Bug:128808922 [QT-QP1A] Click to change orientation in GCA secure mode will lead to unlock screen. Bug:128857973 With dark display and black accent device theme, App list header not visible on all apps screen. Bug:129145824 When predictions are disabled, the hotseat does not appear in quickstep Bug:129146717 Do not allow overlays to change attribute format Bug:129297464 [Gesture Nav] Exclude edges from most Launcher / Overview states. Bug:129328259 [Regression] Suspended app icons turn full colour during open animation Bug:129421279 FloatingIconView should be removed when user interacts with launcher Bug:129434166 Lab-only flake: drag to workspace doesn't happen Bug:129785904 [Gesture Nav] Verify that gestures are disabled appropriately in transient states Bug:129955150 Launcher settings' icon is not using consistent grey Bug:129985827 [Fully Gestural Navigation] Delay Recents animation when swiping up Bug:130053407 [Tests broken] testLearn: settings is not present in the prediction row Bug:130160876 Full gesture navigation with fast switcher shows part of "clear all" message Bug:130415177 Launcher displays app as suspended when it is not if setPackagesSuspended is called multiple times Bug:130440957 Recents Go view visibility not updating while loading UI is up Bug:130442915 Jank observed while navigating from long press app shortcut. Bug:130562632 DefaultLayoutProviderTest.testCustomProfileLoaded_with_folder is flaking Bug:130580680 Recents Go does not load views right after boot Bug:130635650 Recents Go thumbnails not updating on app => recents transition Bug:130735711 Should fix overview shortcut in Recents Go landscape mode Bug:130739123 Tests failure: Attempt to invoke virtual method 'int com.android.launcher3.views.BaseDragLayer.getChildCount()' on a null object reference Bug:130740246 Recents Go crashes from ViewTreeObserver being dead Bug:130746661 Fix NPE for getting task from loading task UI in Recents Go Bug:130820737 Recents Go remove animation doesn't always work Bug:130914022 Add a test for DWB toast Bug:130917995 onConfigChanged when apps are auto installed Bug:131095241 Recents Go should support landscape app => landscape thumbnail transition Bug:131102753 crosshatch_svelte doesn't boot on ToT master on 4/22 because of FATAL exception in launcher3 Bug:131170582 [Flaky test] TaplTestsLauncher3.testDragShortcut Bug:131250710 ControlType.CLEAL_ALL_BUTTON logging is gone Bug:78240090 (Quickstep) Swiping down on the shelf while scrolling carousel Change-Id: I4ef9beb24fab6be4fcff44b330e30d486fc01f03
This commit is contained in:
+3
-2
@@ -251,7 +251,7 @@ LOCAL_PROGUARD_ENABLED := full
|
|||||||
LOCAL_PACKAGE_NAME := Launcher3QuickStepGo
|
LOCAL_PACKAGE_NAME := Launcher3QuickStepGo
|
||||||
LOCAL_PRIVILEGED_MODULE := true
|
LOCAL_PRIVILEGED_MODULE := true
|
||||||
LOCAL_PRODUCT_MODULE := true
|
LOCAL_PRODUCT_MODULE := true
|
||||||
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
|
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep Launcher3GoIconRecents
|
||||||
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
|
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
|
||||||
|
|
||||||
LOCAL_FULL_LIBS_MANIFEST_FILES := \
|
LOCAL_FULL_LIBS_MANIFEST_FILES := \
|
||||||
@@ -297,7 +297,8 @@ LOCAL_PROGUARD_ENABLED := full
|
|||||||
|
|
||||||
LOCAL_PACKAGE_NAME := Launcher3GoIconRecents
|
LOCAL_PACKAGE_NAME := Launcher3GoIconRecents
|
||||||
LOCAL_PRIVILEGED_MODULE := true
|
LOCAL_PRIVILEGED_MODULE := true
|
||||||
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
|
LOCAL_PRODUCT_MODULE := true
|
||||||
|
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep Launcher3QuickStepGo
|
||||||
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
|
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
|
||||||
|
|
||||||
LOCAL_FULL_LIBS_MANIFEST_FILES := \
|
LOCAL_FULL_LIBS_MANIFEST_FILES := \
|
||||||
|
|||||||
@@ -14,4 +14,9 @@
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<merge/>
|
<shape
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@android:color/darker_gray"/>
|
||||||
|
<corners android:radius="2dp"/>
|
||||||
|
</shape>
|
||||||
+6
-6
@@ -14,10 +14,10 @@
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<com.android.quickstep.hints.ChipsContainer
|
<shape
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:shape="rectangle">
|
||||||
android:layout_height="@dimen/chip_hint_height"
|
<solid android:color="@android:color/transparent"/>
|
||||||
android:layout_gravity="bottom"
|
<stroke android:color="@android:color/white" android:width="4px"/>
|
||||||
android:gravity="center_horizontal"
|
<corners android:radius="2dp"/>
|
||||||
android:orientation="horizontal"/>
|
</shape>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?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.ClearAllItemView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/clear_all_item_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<Button
|
||||||
|
android:id="@+id/clear_all_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="16dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:paddingHorizontal="32dp"
|
||||||
|
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"/>
|
||||||
|
</com.android.quickstep.views.ClearAllItemView>
|
||||||
@@ -19,31 +19,11 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
<LinearLayout
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recent_task_content_view"
|
android:id="@+id/recent_task_recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:scrollbars="none"/>
|
||||||
android:visibility="gone">
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/recent_task_recycler_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:scrollbars="none"/>
|
|
||||||
<Button
|
|
||||||
android:id="@+id/clear_all_button"
|
|
||||||
android:layout_width="@dimen/clear_all_button_width"
|
|
||||||
android:layout_height="@dimen/clear_all_button_height"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginVertical="@dimen/task_item_half_vert_margin"
|
|
||||||
android:background="@drawable/clear_all_button"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/recents_clear_all"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
android:textColor="@color/clear_all_button_text"
|
|
||||||
android:textSize="14sp"/>
|
|
||||||
</LinearLayout>
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/recent_task_empty_view"
|
android:id="@+id/recent_task_empty_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -17,26 +17,23 @@
|
|||||||
<com.android.quickstep.views.TaskItemView
|
<com.android.quickstep.views.TaskItemView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
<FrameLayout
|
<com.android.quickstep.views.TaskThumbnailIconView
|
||||||
android:id="@+id/task_icon_and_thumbnail"
|
android:id="@+id/task_icon_and_thumbnail"
|
||||||
android:layout_width="@dimen/task_thumbnail_and_icon_view_size"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/task_thumbnail_and_icon_view_size"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginHorizontal="8dp"
|
android:layout_marginHorizontal="8dp"
|
||||||
android:layout_marginVertical="@dimen/task_item_half_vert_margin">
|
android:layout_marginTop="16dp">
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/task_thumbnail"
|
android:id="@+id/task_thumbnail"
|
||||||
android:layout_width="@dimen/task_thumbnail_width"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/task_thumbnail_height"
|
android:layout_height="match_parent"/>
|
||||||
android:layout_gravity="top|start"/>
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/task_icon"
|
android:id="@+id/task_icon"
|
||||||
android:layout_width="@dimen/task_icon_size"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/task_icon_size"
|
android:layout_height="match_parent"/>
|
||||||
android:layout_gravity="bottom|end"/>
|
</com.android.quickstep.views.TaskThumbnailIconView>
|
||||||
</FrameLayout>
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/task_label"
|
android:id="@+id/task_label"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -1,26 +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="task_item_half_vert_margin">8dp</dimen>
|
|
||||||
<dimen name="task_thumbnail_and_icon_view_size">60dp</dimen>
|
|
||||||
<dimen name="task_thumbnail_height">60dp</dimen>
|
|
||||||
<dimen name="task_thumbnail_width">36dp</dimen>
|
|
||||||
<dimen name="task_icon_size">36dp</dimen>
|
|
||||||
|
|
||||||
<dimen name="clear_all_button_width">106dp</dimen>
|
|
||||||
<dimen name="clear_all_button_height">36dp</dimen>
|
|
||||||
</resources>
|
|
||||||
@@ -18,6 +18,7 @@ package com.android.launcher3.uioverrides.states;
|
|||||||
|
|
||||||
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
|
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
|
||||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
|
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
|
||||||
|
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
|
||||||
|
|
||||||
import com.android.launcher3.DeviceProfile;
|
import com.android.launcher3.DeviceProfile;
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
@@ -51,6 +52,9 @@ public class OverviewState extends LauncherState {
|
|||||||
public void onStateEnabled(Launcher launcher) {
|
public void onStateEnabled(Launcher launcher) {
|
||||||
IconRecentsView recentsView = launcher.getOverviewPanel();
|
IconRecentsView recentsView = launcher.getOverviewPanel();
|
||||||
recentsView.onBeginTransitionToOverview();
|
recentsView.onBeginTransitionToOverview();
|
||||||
|
// Request orientation be set to unspecified, letting the system decide the best
|
||||||
|
// orientation.
|
||||||
|
launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -131,10 +131,11 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
|
|||||||
return anim;
|
return anim;
|
||||||
}
|
}
|
||||||
|
|
||||||
View thumbnailView = mRecentsView.getThumbnailViewForTask(mTargetTaskId);
|
View thumbnailView = mRecentsView.getBottomThumbnailView();
|
||||||
if (thumbnailView == null) {
|
if (thumbnailView == null) {
|
||||||
// TODO: We should either 1) guarantee the view is loaded before attempting this
|
// This can be null if there were previously 0 tasks and the recycler view has not had
|
||||||
// or 2) have a backup animation.
|
// 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
|
||||||
if (Log.isLoggable(TAG, Log.WARN)) {
|
if (Log.isLoggable(TAG, Log.WARN)) {
|
||||||
Log.w(TAG, "No thumbnail view for running task. Using stub animation.");
|
Log.w(TAG, "No thumbnail view for running task. Using stub animation.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
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) {
|
||||||
|
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--) {
|
||||||
|
PendingAnimation pendAnim = mPendingAnims.get(i);
|
||||||
|
if (pendAnim.viewHolder == item) {
|
||||||
|
mPendingAnims.remove(i);
|
||||||
|
switch (pendAnim.animType) {
|
||||||
|
case ANIM_TYPE_REMOVE:
|
||||||
|
dispatchRemoveFinished(item);
|
||||||
|
break;
|
||||||
|
case ANIM_TYPE_CHANGE:
|
||||||
|
dispatchChangeFinished(item, true /* oldItem */);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispatchFinishedWhenDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endAnimations() {
|
||||||
|
for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
|
||||||
|
PendingAnimation pendAnim = mPendingAnims.get(i);
|
||||||
|
ViewHolder item = pendAnim.viewHolder;
|
||||||
|
switch (pendAnim.animType) {
|
||||||
|
case ANIM_TYPE_REMOVE:
|
||||||
|
dispatchRemoveFinished(item);
|
||||||
|
break;
|
||||||
|
case ANIM_TYPE_CHANGE:
|
||||||
|
dispatchChangeFinished(item, true /* oldItem */);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mPendingAnims.remove(i);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < mRunningAnims.size(); i++) {
|
||||||
|
ObjectAnimator anim = mRunningAnims.get(i);
|
||||||
|
anim.end();
|
||||||
|
}
|
||||||
|
dispatchAnimationsFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,9 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.quickstep;
|
package com.android.quickstep;
|
||||||
|
|
||||||
|
import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION;
|
||||||
|
|
||||||
import android.app.ActivityOptions;
|
import android.app.ActivityOptions;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.android.quickstep.views.TaskItemView;
|
import com.android.quickstep.views.TaskItemView;
|
||||||
import com.android.systemui.shared.recents.model.Task;
|
import com.android.systemui.shared.recents.model.Task;
|
||||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||||
@@ -37,12 +41,12 @@ public final class TaskActionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch the task associated with the task holder, animating into the app.
|
* Launch the task associated with the task holder, animating into the app from the task view.
|
||||||
*
|
*
|
||||||
* @param viewHolder the task view holder to launch
|
* @param viewHolder the task view holder to launch
|
||||||
*/
|
*/
|
||||||
public void launchTask(TaskHolder viewHolder) {
|
public void launchTaskFromView(@NonNull TaskHolder viewHolder) {
|
||||||
if (viewHolder.getTask() == null) {
|
if (!viewHolder.getTask().isPresent()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TaskItemView itemView = (TaskItemView) (viewHolder.itemView);
|
TaskItemView itemView = (TaskItemView) (viewHolder.itemView);
|
||||||
@@ -53,8 +57,20 @@ public final class TaskActionController {
|
|||||||
int height = v.getMeasuredHeight();
|
int height = v.getMeasuredHeight();
|
||||||
|
|
||||||
ActivityOptions opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
|
ActivityOptions opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
|
||||||
ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(viewHolder.getTask().key,
|
ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(
|
||||||
opts, null /* resultCallback */, null /* resultCallbackHandler */);
|
viewHolder.getTask().get().key, opts, null /* resultCallback */,
|
||||||
|
null /* resultCallbackHandler */);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 */);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,11 +79,11 @@ public final class TaskActionController {
|
|||||||
* @param viewHolder the task view holder to remove
|
* @param viewHolder the task view holder to remove
|
||||||
*/
|
*/
|
||||||
public void removeTask(TaskHolder viewHolder) {
|
public void removeTask(TaskHolder viewHolder) {
|
||||||
if (viewHolder.getTask() == null) {
|
if (!viewHolder.getTask().isPresent()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int position = viewHolder.getAdapterPosition();
|
int position = viewHolder.getAdapterPosition();
|
||||||
Task task = viewHolder.getTask();
|
Task task = viewHolder.getTask().get();
|
||||||
ActivityManagerWrapper.getInstance().removeTask(task.key.id);
|
ActivityManagerWrapper.getInstance().removeTask(task.key.id);
|
||||||
mLoader.removeTask(task);
|
mLoader.removeTask(task);
|
||||||
mAdapter.notifyItemRemoved(position);
|
mAdapter.notifyItemRemoved(position);
|
||||||
@@ -80,6 +96,6 @@ public final class TaskActionController {
|
|||||||
int count = mAdapter.getItemCount();
|
int count = mAdapter.getItemCount();
|
||||||
ActivityManagerWrapper.getInstance().removeAllRecentTasks();
|
ActivityManagerWrapper.getInstance().removeAllRecentTasks();
|
||||||
mLoader.clearAllTasks();
|
mLoader.clearAllTasks();
|
||||||
mAdapter.notifyItemRangeRemoved(0 /* positionStart */, count);
|
mAdapter.notifyItemRangeRemoved(TASKS_START_POSITION /* positionStart */, count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.quickstep;
|
package com.android.quickstep;
|
||||||
|
|
||||||
import android.util.ArrayMap;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView.Adapter;
|
import androidx.recyclerview.widget.RecyclerView.Adapter;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||||
|
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
import com.android.quickstep.views.TaskItemView;
|
import com.android.quickstep.views.TaskItemView;
|
||||||
@@ -29,18 +31,25 @@ import com.android.systemui.shared.recents.model.Task;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
|
* Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
|
||||||
* appropriate {@link Task} from the recents task list.
|
* appropriate {@link Task} from the recents task list.
|
||||||
*/
|
*/
|
||||||
public final class TaskAdapter extends Adapter<TaskHolder> {
|
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 int MAX_TASKS_TO_DISPLAY = 6;
|
|
||||||
private static final String TAG = "TaskAdapter";
|
private static final String TAG = "TaskAdapter";
|
||||||
private final TaskListLoader mLoader;
|
private final TaskListLoader mLoader;
|
||||||
private final ArrayMap<Integer, TaskItemView> mTaskIdToViewMap = new ArrayMap<>();
|
|
||||||
private TaskActionController mTaskActionController;
|
private TaskActionController mTaskActionController;
|
||||||
|
private OnClickListener mClearAllListener;
|
||||||
private boolean mIsShowingLoadingUi;
|
private boolean mIsShowingLoadingUi;
|
||||||
|
|
||||||
public TaskAdapter(@NonNull TaskListLoader loader) {
|
public TaskAdapter(@NonNull TaskListLoader loader) {
|
||||||
@@ -51,6 +60,10 @@ public final class TaskAdapter extends Adapter<TaskHolder> {
|
|||||||
mTaskActionController = 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.
|
* 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
|
* This changes the task adapter's view of the data, so the appropriate notify events should be
|
||||||
@@ -63,75 +76,104 @@ public final class TaskAdapter extends Adapter<TaskHolder> {
|
|||||||
mIsShowingLoadingUi = isShowingLoadingUi;
|
mIsShowingLoadingUi = isShowingLoadingUi;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Get task item view for a given task id if it's attached to the view.
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
*
|
switch (viewType) {
|
||||||
* @param taskId task id to search for
|
case ITEM_TYPE_TASK:
|
||||||
* @return corresponding task item view if it's attached, null otherwise
|
TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
|
||||||
*/
|
.inflate(R.layout.task_item_view, parent, false);
|
||||||
public @Nullable TaskItemView getTaskItemView(int taskId) {
|
TaskHolder taskHolder = new TaskHolder(itemView);
|
||||||
return mTaskIdToViewMap.get(taskId);
|
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
|
@Override
|
||||||
public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
|
onBindViewHolderInternal(holder, position, false /* willAnimate */);
|
||||||
.inflate(R.layout.task_item_view, parent, false);
|
|
||||||
TaskHolder holder = new TaskHolder(itemView);
|
|
||||||
itemView.setOnClickListener(view -> mTaskActionController.launchTask(holder));
|
|
||||||
return holder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(TaskHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position,
|
||||||
if (mIsShowingLoadingUi) {
|
@NonNull List<Object> payloads) {
|
||||||
holder.bindEmptyUi();
|
if (payloads.isEmpty()) {
|
||||||
|
super.onBindViewHolder(holder, position, payloads);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<Task> tasks = mLoader.getCurrentTaskList();
|
int changeType = (int) payloads.get(0);
|
||||||
if (position >= tasks.size()) {
|
if (changeType == CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT) {
|
||||||
// Task list has updated.
|
// Bind in preparation for animation
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
Task task = tasks.get(position);
|
|
||||||
holder.bindTask(task);
|
|
||||||
mLoader.loadTaskIconAndLabel(task, () -> {
|
|
||||||
// Ensure holder still has the same task.
|
|
||||||
if (Objects.equals(task, holder.getTask())) {
|
|
||||||
holder.getTaskItemView().setIcon(task.icon);
|
|
||||||
holder.getTaskItemView().setLabel(task.titleDescription);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mLoader.loadTaskThumbnail(task, () -> {
|
|
||||||
if (Objects.equals(task, holder.getTask())) {
|
|
||||||
holder.getTaskItemView().setThumbnail(task.thumbnail.thumbnail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewAttachedToWindow(@NonNull TaskHolder holder) {
|
public int getItemViewType(int position) {
|
||||||
if (holder.getTask() == null) {
|
// Bottom is always clear all button.
|
||||||
return;
|
return (position == 0) ? ITEM_TYPE_CLEAR_ALL : ITEM_TYPE_TASK;
|
||||||
}
|
|
||||||
mTaskIdToViewMap.put(holder.getTask().key.id, (TaskItemView) holder.itemView);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewDetachedFromWindow(@NonNull TaskHolder holder) {
|
|
||||||
if (holder.getTask() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mTaskIdToViewMap.remove(holder.getTask().key.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
|
int itemCount = TASKS_START_POSITION;
|
||||||
if (mIsShowingLoadingUi) {
|
if (mIsShowingLoadingUi) {
|
||||||
// Show loading version of all items.
|
// Show loading version of all items.
|
||||||
return MAX_TASKS_TO_DISPLAY;
|
itemCount += MAX_TASKS_TO_DISPLAY;
|
||||||
} else {
|
} else {
|
||||||
return Math.min(mLoader.getCurrentTaskList().size(), MAX_TASKS_TO_DISPLAY);
|
itemCount += Math.min(mLoader.getCurrentTaskList().size(), MAX_TASKS_TO_DISPLAY);
|
||||||
}
|
}
|
||||||
|
return itemCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.quickstep;
|
package com.android.quickstep;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||||
|
|
||||||
import com.android.quickstep.views.TaskItemView;
|
import com.android.quickstep.views.TaskItemView;
|
||||||
import com.android.systemui.shared.recents.model.Task;
|
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,
|
* A recycler view holder that holds the task view and binds {@link Task} content (app title, icon,
|
||||||
* etc.) to the view.
|
* etc.) to the view.
|
||||||
@@ -40,13 +42,27 @@ public final class TaskHolder extends ViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind a task to the holder, resetting the view and preparing it for content to load in.
|
* 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 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(Task task) {
|
public void bindTask(@NonNull Task task, boolean willAnimate) {
|
||||||
mTask = task;
|
mTask = task;
|
||||||
mTaskItemView.resetTaskItemView();
|
if (willAnimate) {
|
||||||
|
mTaskItemView.startContentAnimation(task.icon, task.thumbnail, task.titleDescription);
|
||||||
|
} else {
|
||||||
|
mTaskItemView.setIcon(task.icon);
|
||||||
|
mTaskItemView.setThumbnail(task.thumbnail);
|
||||||
|
mTaskItemView.setLabel(task.titleDescription);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,10 +71,7 @@ public final class TaskHolder extends ViewHolder {
|
|||||||
*/
|
*/
|
||||||
public void bindEmptyUi() {
|
public void bindEmptyUi() {
|
||||||
mTask = null;
|
mTask = null;
|
||||||
// TODO: Set the task view to a loading, empty UI.
|
mTaskItemView.resetToEmptyUi();
|
||||||
// Temporarily using the one below for visual confirmation but should be swapped out to new
|
|
||||||
// UI later.
|
|
||||||
mTaskItemView.resetTaskItemView();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +79,7 @@ public final class TaskHolder extends ViewHolder {
|
|||||||
*
|
*
|
||||||
* @return the current task
|
* @return the current task
|
||||||
*/
|
*/
|
||||||
public @Nullable Task getTask() {
|
public Optional<Task> getTask() {
|
||||||
return mTask;
|
return Optional.ofNullable(mTask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,23 +38,9 @@ public final class TaskListLoader {
|
|||||||
|
|
||||||
private ArrayList<Task> mTaskList = new ArrayList<>();
|
private ArrayList<Task> mTaskList = new ArrayList<>();
|
||||||
private int mTaskListChangeId;
|
private int mTaskListChangeId;
|
||||||
private RecentsModel.TaskThumbnailChangeListener listener = (taskId, thumbnailData) -> {
|
|
||||||
Task foundTask = null;
|
|
||||||
for (Task task : mTaskList) {
|
|
||||||
if (task.key.id == taskId) {
|
|
||||||
foundTask = task;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (foundTask != null) {
|
|
||||||
foundTask.thumbnail = thumbnailData;
|
|
||||||
}
|
|
||||||
return foundTask;
|
|
||||||
};
|
|
||||||
|
|
||||||
public TaskListLoader(Context context) {
|
public TaskListLoader(Context context) {
|
||||||
mRecentsModel = RecentsModel.INSTANCE.get(context);
|
mRecentsModel = RecentsModel.INSTANCE.get(context);
|
||||||
mRecentsModel.addThumbnailChangeListener(listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ package com.android.quickstep;
|
|||||||
|
|
||||||
import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT;
|
import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT;
|
||||||
|
|
||||||
|
import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||||
@@ -45,4 +48,14 @@ public final class TaskSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
|||||||
mTaskActionController.removeTask((TaskHolder) viewHolder);
|
mTaskActionController.removeTask((TaskHolder) viewHolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* 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.graphics.Bitmap;
|
||||||
|
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.drawable.Drawable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
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 int mRequestedOrientation;
|
||||||
|
|
||||||
|
public ThumbnailDrawable(@NonNull ThumbnailData thumbnailData, int requestedOrientation) {
|
||||||
|
mThumbnailData = thumbnailData;
|
||||||
|
mRequestedOrientation = requestedOrientation;
|
||||||
|
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.drawBitmap(mThumbnailData.thumbnail, mMatrix, mPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBoundsChange(Rect bounds) {
|
||||||
|
super.onBoundsChange(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.quickstep.views.TaskLayoutUtils.getClearAllItemHeight;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recycler view item that lays out the clear all button and measures the space it takes based on
|
||||||
|
* the device height.
|
||||||
|
*/
|
||||||
|
public final class ClearAllItemView extends FrameLayout {
|
||||||
|
|
||||||
|
public ClearAllItemView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
int buttonHeight = getClearAllItemHeight(getContext());
|
||||||
|
int newHeightSpec = MeasureSpec.makeMeasureSpec(buttonHeight, MeasureSpec.EXACTLY);
|
||||||
|
super.onMeasure(widthMeasureSpec, newHeightSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,8 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.quickstep.views;
|
package com.android.quickstep.views;
|
||||||
|
|
||||||
|
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
|
||||||
|
|
||||||
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
|
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
|
||||||
|
|
||||||
|
import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
|
||||||
|
import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.animation.AnimatorSet;
|
import android.animation.AnimatorSet;
|
||||||
@@ -24,30 +29,39 @@ import android.animation.ObjectAnimator;
|
|||||||
import android.animation.PropertyValuesHolder;
|
import android.animation.PropertyValuesHolder;
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.ArraySet;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.FloatProperty;
|
import android.util.FloatProperty;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewDebug;
|
import android.view.ViewDebug;
|
||||||
import android.view.animation.AlphaAnimation;
|
import android.view.ViewTreeObserver;
|
||||||
import android.view.animation.Animation;
|
|
||||||
import android.view.animation.AnimationSet;
|
|
||||||
import android.view.animation.LayoutAnimationController;
|
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
|
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
|
||||||
|
|
||||||
|
import com.android.launcher3.BaseActivity;
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
|
import com.android.quickstep.ContentFillItemAnimator;
|
||||||
|
import com.android.quickstep.RecentsModel;
|
||||||
import com.android.quickstep.RecentsToActivityHelper;
|
import com.android.quickstep.RecentsToActivityHelper;
|
||||||
import com.android.quickstep.TaskActionController;
|
import com.android.quickstep.TaskActionController;
|
||||||
import com.android.quickstep.TaskAdapter;
|
import com.android.quickstep.TaskAdapter;
|
||||||
import com.android.quickstep.TaskHolder;
|
import com.android.quickstep.TaskHolder;
|
||||||
import com.android.quickstep.TaskListLoader;
|
import com.android.quickstep.TaskListLoader;
|
||||||
import com.android.quickstep.TaskSwipeCallback;
|
import com.android.quickstep.TaskSwipeCallback;
|
||||||
|
import com.android.systemui.shared.recents.model.Task;
|
||||||
|
|
||||||
|
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
|
* Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code
|
||||||
@@ -89,24 +103,48 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final TaskListLoader mTaskLoader;
|
private final TaskListLoader mTaskLoader;
|
||||||
private final TaskAdapter mTaskAdapter;
|
private final TaskAdapter mTaskAdapter;
|
||||||
|
private final LinearLayoutManager mTaskLayoutManager;
|
||||||
private final TaskActionController mTaskActionController;
|
private final TaskActionController mTaskActionController;
|
||||||
private final LayoutAnimationController mLayoutAnimation;
|
private final DefaultItemAnimator mDefaultItemAnimator = new DefaultItemAnimator();
|
||||||
|
private final ContentFillItemAnimator mLoadingContentItemAnimator =
|
||||||
|
new ContentFillItemAnimator();
|
||||||
|
|
||||||
private RecentsToActivityHelper mActivityHelper;
|
private RecentsToActivityHelper mActivityHelper;
|
||||||
private RecyclerView mTaskRecyclerView;
|
private RecyclerView mTaskRecyclerView;
|
||||||
|
private View mShowingContentView;
|
||||||
private View mEmptyView;
|
private View mEmptyView;
|
||||||
private View mContentView;
|
private View mContentView;
|
||||||
private View mClearAllView;
|
|
||||||
private boolean mTransitionedFromApp;
|
private boolean mTransitionedFromApp;
|
||||||
|
private AnimatorSet mLayoutAnimation;
|
||||||
|
private final ArraySet<View> mLayingOutViews = new ArraySet<>();
|
||||||
|
private final RecentsModel.TaskThumbnailChangeListener listener = (taskId, 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;
|
||||||
|
};
|
||||||
|
|
||||||
public IconRecentsView(Context context, AttributeSet attrs) {
|
public IconRecentsView(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
|
BaseActivity activity = BaseActivity.fromContext(context);
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mTaskLoader = new TaskListLoader(mContext);
|
mTaskLoader = new TaskListLoader(mContext);
|
||||||
mTaskAdapter = new TaskAdapter(mTaskLoader);
|
mTaskAdapter = new TaskAdapter(mTaskLoader);
|
||||||
|
mTaskAdapter.setOnClearAllClickListener(view -> animateClearAllTasks());
|
||||||
mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter);
|
mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter);
|
||||||
mTaskAdapter.setActionController(mTaskActionController);
|
mTaskAdapter.setActionController(mTaskActionController);
|
||||||
mLayoutAnimation = createLayoutAnimation();
|
mTaskLayoutManager = new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */);
|
||||||
|
RecentsModel.INSTANCE.get(context).addThumbnailChangeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -115,15 +153,30 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
if (mTaskRecyclerView == null) {
|
if (mTaskRecyclerView == null) {
|
||||||
mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
|
mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
|
||||||
mTaskRecyclerView.setAdapter(mTaskAdapter);
|
mTaskRecyclerView.setAdapter(mTaskAdapter);
|
||||||
mTaskRecyclerView.setLayoutManager(
|
mTaskRecyclerView.setLayoutManager(mTaskLayoutManager);
|
||||||
new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
|
|
||||||
ItemTouchHelper helper = new ItemTouchHelper(
|
ItemTouchHelper helper = new ItemTouchHelper(
|
||||||
new TaskSwipeCallback(mTaskActionController));
|
new TaskSwipeCallback(mTaskActionController));
|
||||||
helper.attachToRecyclerView(mTaskRecyclerView);
|
helper.attachToRecyclerView(mTaskRecyclerView);
|
||||||
mTaskRecyclerView.setLayoutAnimation(mLayoutAnimation);
|
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()));
|
||||||
|
|
||||||
mEmptyView = findViewById(R.id.recent_task_empty_view);
|
mEmptyView = findViewById(R.id.recent_task_empty_view);
|
||||||
mContentView = findViewById(R.id.recent_task_content_view);
|
mContentView = mTaskRecyclerView;
|
||||||
mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() {
|
mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() {
|
||||||
@Override
|
@Override
|
||||||
public void onChanged() {
|
public void onChanged() {
|
||||||
@@ -135,19 +188,17 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
updateContentViewVisibility();
|
updateContentViewVisibility();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mClearAllView = findViewById(R.id.clear_all_button);
|
// TODO: Move layout param logic into onMeasure
|
||||||
mClearAllView.setOnClickListener(v -> animateClearAllTasks());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
super.setEnabled(enabled);
|
super.setEnabled(enabled);
|
||||||
TaskItemView[] itemViews = getTaskViews();
|
int childCount = mTaskRecyclerView.getChildCount();
|
||||||
for (TaskItemView itemView : itemViews) {
|
for (int i = 0; i < childCount; i++) {
|
||||||
itemView.setEnabled(enabled);
|
mTaskRecyclerView.getChildAt(i).setEnabled(enabled);
|
||||||
}
|
}
|
||||||
mClearAllView.setEnabled(enabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,8 +216,13 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
* becomes visible.
|
* becomes visible.
|
||||||
*/
|
*/
|
||||||
public void onBeginTransitionToOverview() {
|
public void onBeginTransitionToOverview() {
|
||||||
mTaskRecyclerView.scheduleLayoutAnimation();
|
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 */);
|
||||||
|
}
|
||||||
|
scheduleFadeInLayoutAnimation();
|
||||||
// Load any task changes
|
// Load any task changes
|
||||||
if (!mTaskLoader.needsToLoad()) {
|
if (!mTaskLoader.needsToLoad()) {
|
||||||
return;
|
return;
|
||||||
@@ -174,9 +230,24 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
mTaskAdapter.setIsShowingLoadingUi(true);
|
mTaskAdapter.setIsShowingLoadingUi(true);
|
||||||
mTaskAdapter.notifyDataSetChanged();
|
mTaskAdapter.notifyDataSetChanged();
|
||||||
mTaskLoader.loadTaskList(tasks -> {
|
mTaskLoader.loadTaskList(tasks -> {
|
||||||
|
int numEmptyItems = mTaskAdapter.getItemCount() - TASKS_START_POSITION;
|
||||||
mTaskAdapter.setIsShowingLoadingUi(false);
|
mTaskAdapter.setIsShowingLoadingUi(false);
|
||||||
// TODO: Animate the loading UI out and the loaded data in.
|
int numActualItems = mTaskAdapter.getItemCount() - TASKS_START_POSITION;
|
||||||
mTaskAdapter.notifyDataSetChanged();
|
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.
|
||||||
|
if (!mTaskRecyclerView.hasPendingAdapterUpdates()) {
|
||||||
|
// 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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,35 +265,47 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
* the app. In that case, we launch the next most recent.
|
* the app. In that case, we launch the next most recent.
|
||||||
*/
|
*/
|
||||||
public void handleOverviewCommand() {
|
public void handleOverviewCommand() {
|
||||||
int childCount = mTaskRecyclerView.getChildCount();
|
List<Task> tasks = mTaskLoader.getCurrentTaskList();
|
||||||
if (childCount == 0) {
|
int tasksSize = tasks.size();
|
||||||
|
if (tasksSize == 0) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TaskHolder taskToLaunch;
|
Task taskToLaunch;
|
||||||
if (mTransitionedFromApp && childCount > 1) {
|
if (mTransitionedFromApp && tasksSize > 1) {
|
||||||
// Launch the next most recent app
|
// Launch the next most recent app
|
||||||
TaskItemView itemView = (TaskItemView) mTaskRecyclerView.getChildAt(1);
|
taskToLaunch = tasks.get(1);
|
||||||
taskToLaunch = (TaskHolder) mTaskRecyclerView.getChildViewHolder(itemView);
|
|
||||||
} else {
|
} else {
|
||||||
// Launch the most recent app
|
// Launch the most recent app
|
||||||
TaskItemView itemView = (TaskItemView) mTaskRecyclerView.getChildAt(0);
|
taskToLaunch = tasks.get(0);
|
||||||
taskToLaunch = (TaskHolder) mTaskRecyclerView.getChildViewHolder(itemView);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
mTaskActionController.launchTask(taskToLaunch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the thumbnail view associated with a task for the purposes of animation.
|
* Get the bottom most thumbnail view to animate to.
|
||||||
*
|
*
|
||||||
* @param taskId task id of thumbnail view to get
|
* @return the thumbnail view if laid out
|
||||||
* @return the thumbnail view for the task if attached, null otherwise
|
|
||||||
*/
|
*/
|
||||||
public @Nullable View getThumbnailViewForTask(int taskId) {
|
public @Nullable View getBottomThumbnailView() {
|
||||||
TaskItemView view = mTaskAdapter.getTaskItemView(taskId);
|
ArrayList<TaskItemView> taskViews = getTaskViews();
|
||||||
if (view == null) {
|
if (taskViews.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
TaskItemView view = taskViews.get(0);
|
||||||
return view.getThumbnailView();
|
return view.getThumbnailView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,13 +314,14 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
*/
|
*/
|
||||||
private void animateClearAllTasks() {
|
private void animateClearAllTasks() {
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
TaskItemView[] itemViews = getTaskViews();
|
ArrayList<TaskItemView> itemViews = getTaskViews();
|
||||||
|
|
||||||
AnimatorSet clearAnim = new AnimatorSet();
|
AnimatorSet clearAnim = new AnimatorSet();
|
||||||
long currentDelay = 0;
|
long currentDelay = 0;
|
||||||
|
|
||||||
// Animate each item view to the right and fade out.
|
// Animate each item view to the right and fade out.
|
||||||
for (TaskItemView itemView : itemViews) {
|
for (int i = 0, size = itemViews.size(); i < size; i++) {
|
||||||
|
TaskItemView itemView = itemViews.get(i);
|
||||||
PropertyValuesHolder transXproperty = PropertyValuesHolder.ofFloat(TRANSLATION_X,
|
PropertyValuesHolder transXproperty = PropertyValuesHolder.ofFloat(TRANSLATION_X,
|
||||||
0, itemView.getWidth() * ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO);
|
0, itemView.getWidth() * ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO);
|
||||||
PropertyValuesHolder alphaProperty = PropertyValuesHolder.ofFloat(ALPHA, 1.0f, 0f);
|
PropertyValuesHolder alphaProperty = PropertyValuesHolder.ofFloat(ALPHA, 1.0f, 0f);
|
||||||
@@ -272,7 +356,8 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
clearAnim.addListener(new AnimatorListenerAdapter() {
|
clearAnim.addListener(new AnimatorListenerAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animator animation) {
|
public void onAnimationEnd(Animator animation) {
|
||||||
for (TaskItemView itemView : itemViews) {
|
for (int i = 0, size = itemViews.size(); i < size; i++) {
|
||||||
|
TaskItemView itemView = itemViews.get(i);
|
||||||
itemView.setTranslationX(0);
|
itemView.setTranslationX(0);
|
||||||
itemView.setAlpha(1.0f);
|
itemView.setAlpha(1.0f);
|
||||||
}
|
}
|
||||||
@@ -287,13 +372,16 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
/**
|
/**
|
||||||
* Get attached task item views ordered by most recent.
|
* Get attached task item views ordered by most recent.
|
||||||
*
|
*
|
||||||
* @return array of attached task item views
|
* @return array list of attached task item views
|
||||||
*/
|
*/
|
||||||
private TaskItemView[] getTaskViews() {
|
private ArrayList<TaskItemView> getTaskViews() {
|
||||||
int taskCount = mTaskRecyclerView.getChildCount();
|
int taskCount = mTaskRecyclerView.getChildCount();
|
||||||
TaskItemView[] itemViews = new TaskItemView[taskCount];
|
ArrayList<TaskItemView> itemViews = new ArrayList<>();
|
||||||
for (int i = 0; i < taskCount; i ++) {
|
for (int i = 0; i < taskCount; i ++) {
|
||||||
itemViews[i] = (TaskItemView) mTaskRecyclerView.getChildAt(i);
|
View child = mTaskRecyclerView.getChildAt(i);
|
||||||
|
if (child instanceof TaskItemView) {
|
||||||
|
itemViews.add((TaskItemView) child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return itemViews;
|
return itemViews;
|
||||||
}
|
}
|
||||||
@@ -303,12 +391,14 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
* of tasks.
|
* of tasks.
|
||||||
*/
|
*/
|
||||||
private void updateContentViewVisibility() {
|
private void updateContentViewVisibility() {
|
||||||
int taskListSize = mTaskLoader.getCurrentTaskList().size();
|
int taskListSize = mTaskAdapter.getItemCount() - TASKS_START_POSITION;
|
||||||
if (mEmptyView.getVisibility() != VISIBLE && taskListSize == 0) {
|
if (mShowingContentView != mEmptyView && taskListSize == 0) {
|
||||||
|
mShowingContentView = mEmptyView;
|
||||||
crossfadeViews(mEmptyView, mContentView);
|
crossfadeViews(mEmptyView, mContentView);
|
||||||
mActivityHelper.leaveRecents();
|
mActivityHelper.leaveRecents();
|
||||||
}
|
}
|
||||||
if (mContentView.getVisibility() != VISIBLE && taskListSize > 0) {
|
if (mShowingContentView != mContentView && taskListSize > 0) {
|
||||||
|
mShowingContentView = mContentView;
|
||||||
crossfadeViews(mContentView, mEmptyView);
|
crossfadeViews(mContentView, mEmptyView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,6 +410,7 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
* @param fadeOutView view that should fade out
|
* @param fadeOutView view that should fade out
|
||||||
*/
|
*/
|
||||||
private void crossfadeViews(View fadeInView, View fadeOutView) {
|
private void crossfadeViews(View fadeInView, View fadeOutView) {
|
||||||
|
fadeInView.animate().cancel();
|
||||||
fadeInView.setVisibility(VISIBLE);
|
fadeInView.setVisibility(VISIBLE);
|
||||||
fadeInView.setAlpha(0f);
|
fadeInView.setAlpha(0f);
|
||||||
fadeInView.animate()
|
fadeInView.animate()
|
||||||
@@ -327,6 +418,7 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
.setDuration(CROSSFADE_DURATION)
|
.setDuration(CROSSFADE_DURATION)
|
||||||
.setListener(null);
|
.setListener(null);
|
||||||
|
|
||||||
|
fadeOutView.animate().cancel();
|
||||||
fadeOutView.animate()
|
fadeOutView.animate()
|
||||||
.alpha(0f)
|
.alpha(0f)
|
||||||
.setDuration(CROSSFADE_DURATION)
|
.setDuration(CROSSFADE_DURATION)
|
||||||
@@ -338,17 +430,56 @@ public final class IconRecentsView extends FrameLayout {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LayoutAnimationController createLayoutAnimation() {
|
/**
|
||||||
AnimationSet anim = new AnimationSet(false /* shareInterpolator */);
|
* 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Animation alphaAnim = new AlphaAnimation(0, 1);
|
/**
|
||||||
alphaAnim.setDuration(LAYOUT_ITEM_ANIMATE_IN_DURATION);
|
* Start animating the layout animation where items fade in.
|
||||||
anim.addAnimation(alphaAnim);
|
*/
|
||||||
|
private void animateFadeInLayoutAnimation() {
|
||||||
LayoutAnimationController layoutAnim = new LayoutAnimationController(anim);
|
if (mLayoutAnimation != null) {
|
||||||
layoutAnim.setDelay(
|
// If layout animation still in progress, cancel and restart.
|
||||||
(float) LAYOUT_ITEM_ANIMATE_IN_DELAY_BETWEEN / LAYOUT_ITEM_ANIMATE_IN_DURATION);
|
mLayoutAnimation.cancel();
|
||||||
|
}
|
||||||
return layoutAnim;
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,35 +15,66 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.quickstep.views;
|
package com.android.quickstep.views;
|
||||||
|
|
||||||
|
import static com.android.quickstep.views.TaskLayoutUtils.getTaskHeight;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Color;
|
import android.content.res.Resources;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.FloatProperty;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.launcher3.R;
|
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.
|
* View representing an individual task item with the icon + thumbnail adjacent to the task label.
|
||||||
*/
|
*/
|
||||||
public final class TaskItemView extends LinearLayout {
|
public final class TaskItemView extends LinearLayout {
|
||||||
|
|
||||||
|
private static final String EMPTY_LABEL = "";
|
||||||
private static final String DEFAULT_LABEL = "...";
|
private static final String DEFAULT_LABEL = "...";
|
||||||
private final Drawable mDefaultIcon;
|
private final Drawable mDefaultIcon;
|
||||||
|
private final Drawable mDefaultThumbnail;
|
||||||
|
private final TaskLayerDrawable mIconDrawable;
|
||||||
|
private final TaskLayerDrawable mThumbnailDrawable;
|
||||||
private TextView mLabelView;
|
private TextView mLabelView;
|
||||||
private ImageView mIconView;
|
private ImageView mIconView;
|
||||||
private ImageView mThumbnailView;
|
private ImageView mThumbnailView;
|
||||||
|
private float mContentTransitionProgress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
public TaskItemView(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
mDefaultIcon = context.getResources().getDrawable(
|
Resources res = context.getResources();
|
||||||
android.R.drawable.sym_def_app_icon, context.getTheme());
|
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
|
@Override
|
||||||
@@ -52,15 +83,28 @@ public final class TaskItemView extends LinearLayout {
|
|||||||
mLabelView = findViewById(R.id.task_label);
|
mLabelView = findViewById(R.id.task_label);
|
||||||
mThumbnailView = findViewById(R.id.task_thumbnail);
|
mThumbnailView = findViewById(R.id.task_thumbnail);
|
||||||
mIconView = findViewById(R.id.task_icon);
|
mIconView = findViewById(R.id.task_icon);
|
||||||
|
|
||||||
|
mThumbnailView.setImageDrawable(mThumbnailDrawable);
|
||||||
|
mIconView.setImageDrawable(mIconDrawable);
|
||||||
|
|
||||||
|
resetToEmptyUi();
|
||||||
|
CONTENT_TRANSITION_PROGRESS.setValue(this, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
int taskHeight = getTaskHeight(getContext());
|
||||||
|
int newHeightSpec = MeasureSpec.makeMeasureSpec(taskHeight,MeasureSpec.EXACTLY);
|
||||||
|
super.onMeasure(widthMeasureSpec, newHeightSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets task item view to default values.
|
* Resets task item view to empty, loading UI.
|
||||||
*/
|
*/
|
||||||
public void resetTaskItemView() {
|
public void resetToEmptyUi() {
|
||||||
setLabel(DEFAULT_LABEL);
|
mIconDrawable.resetDrawable();
|
||||||
setIcon(null);
|
mThumbnailDrawable.resetDrawable();
|
||||||
setThumbnail(null);
|
setLabel(EMPTY_LABEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,11 +113,8 @@ public final class TaskItemView extends LinearLayout {
|
|||||||
* @param label task label
|
* @param label task label
|
||||||
*/
|
*/
|
||||||
public void setLabel(@Nullable String label) {
|
public void setLabel(@Nullable String label) {
|
||||||
if (label == null) {
|
mLabelView.setText(getSafeLabel(label));
|
||||||
mLabelView.setText(DEFAULT_LABEL);
|
// TODO: Animation for label
|
||||||
return;
|
|
||||||
}
|
|
||||||
mLabelView.setText(label);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,29 +127,72 @@ public final class TaskItemView extends LinearLayout {
|
|||||||
// The icon proper is actually smaller than the drawable and has "padding" on the side for
|
// 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
|
// 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.
|
// view if we want the icon to be flush with the bottom of the thumbnail.
|
||||||
if (icon == null) {
|
mIconDrawable.setCurrentDrawable(getSafeIcon(icon));
|
||||||
mIconView.setImageDrawable(mDefaultIcon);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mIconView.setImageDrawable(icon);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the task thumbnail for the task. Sets to a default thumbnail if null.
|
* Set the task thumbnail for the task. Sets to a default thumbnail if null.
|
||||||
*
|
*
|
||||||
* @param thumbnail task thumbnail for the task
|
* @param thumbnailData task thumbnail data for the task
|
||||||
*/
|
*/
|
||||||
public void setThumbnail(@Nullable Bitmap thumbnail) {
|
public void setThumbnail(@Nullable ThumbnailData thumbnailData) {
|
||||||
if (thumbnail == null) {
|
mThumbnailDrawable.setCurrentDrawable(getSafeThumbnail(thumbnailData));
|
||||||
mThumbnailView.setImageBitmap(null);
|
|
||||||
mThumbnailView.setBackgroundColor(Color.GRAY);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mThumbnailView.setBackgroundColor(Color.TRANSPARENT);
|
|
||||||
mThumbnailView.setImageBitmap(thumbnail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public View getThumbnailView() {
|
public View getThumbnailView() {
|
||||||
return mThumbnailView;
|
return mThumbnailView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(thumbnailData, orientation /* requestedOrientation */);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull String getSafeLabel(@Nullable String label) {
|
||||||
|
return (label != null) ? label : DEFAULT_LABEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
int layerCount = mThumbnailDrawable.getNumberOfLayers();
|
||||||
|
for (int i = 0; i < layerCount; i++) {
|
||||||
|
Drawable drawable = mThumbnailDrawable.getDrawable(i);
|
||||||
|
if (drawable instanceof ThumbnailDrawable) {
|
||||||
|
((ThumbnailDrawable) drawable).setRequestedOrientation(newConfig.orientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.quickstep.TaskAdapter.MAX_TASKS_TO_DISPLAY;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.android.launcher3.InvariantDeviceProfile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utils to determine dynamically task and view sizes based off the device height and width.
|
||||||
|
*/
|
||||||
|
public final class TaskLayoutUtils {
|
||||||
|
|
||||||
|
private static final float CLEAR_ALL_ITEM_TO_HEIGHT_RATIO = 7.0f / 64;
|
||||||
|
|
||||||
|
private TaskLayoutUtils() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate task height based off the available height in portrait mode such that when the
|
||||||
|
* recents list is full, the total height fills in the available device height perfectly. In
|
||||||
|
* landscape mode, we keep the same task height so that tasks scroll off the top.
|
||||||
|
*
|
||||||
|
* @param context current context
|
||||||
|
* @return task height
|
||||||
|
*/
|
||||||
|
public static int getTaskHeight(Context context) {
|
||||||
|
final int availableHeight =
|
||||||
|
InvariantDeviceProfile.INSTANCE.get(context).portraitProfile.availableHeightPx;
|
||||||
|
final int availableTaskSpace = availableHeight - getClearAllItemHeight(context);
|
||||||
|
return (int) (availableTaskSpace * 1.0f / MAX_TASKS_TO_DISPLAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate clear all item height scaled to available height in portrait mode.
|
||||||
|
*
|
||||||
|
* @param context current context
|
||||||
|
* @return clear all item height
|
||||||
|
*/
|
||||||
|
public static int getClearAllItemHeight(Context context) {
|
||||||
|
final int availableHeight =
|
||||||
|
InvariantDeviceProfile.INSTANCE.get(context).portraitProfile.availableHeightPx;
|
||||||
|
return (int) (CLEAR_ALL_ITEM_TO_HEIGHT_RATIO * availableHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* 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.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) {
|
||||||
|
int thumbnailHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
|
||||||
|
int thumbnailWidthSpec = MeasureSpec.makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY);
|
||||||
|
measureChild(mThumbnailView, thumbnailWidthSpec, thumbnailHeightSpec);
|
||||||
|
}
|
||||||
|
if (mIconView.getVisibility() != GONE) {
|
||||||
|
int iconHeightSpec = MeasureSpec.makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY);
|
||||||
|
int iconWidthSpec = MeasureSpec.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ package com.android.launcher3.shortcuts;
|
|||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.pm.ShortcutInfo;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -55,7 +56,7 @@ public class DeepShortcutManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onShortcutsChanged(List<ShortcutInfoCompat> shortcuts) {
|
public void onShortcutsChanged(List<ShortcutInfo> shortcuts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,7 +65,7 @@ public class DeepShortcutManager {
|
|||||||
* This method is intended to get the full details for shortcuts when they are added or updated,
|
* This method is intended to get the full details for shortcuts when they are added or updated,
|
||||||
* because we only get "key" fields in onShortcutsChanged().
|
* because we only get "key" fields in onShortcutsChanged().
|
||||||
*/
|
*/
|
||||||
public List<ShortcutInfoCompat> queryForFullDetails(String packageName,
|
public List<ShortcutInfo> queryForFullDetails(String packageName,
|
||||||
List<String> shortcutIds, UserHandle user) {
|
List<String> shortcutIds, UserHandle user) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,7 @@ public class DeepShortcutManager {
|
|||||||
* Gets all the manifest and dynamic shortcuts associated with the given package and user,
|
* Gets all the manifest and dynamic shortcuts associated with the given package and user,
|
||||||
* to be displayed in the shortcuts container on long press.
|
* to be displayed in the shortcuts container on long press.
|
||||||
*/
|
*/
|
||||||
public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
|
public List<ShortcutInfo> queryForShortcutsContainer(ComponentName activity,
|
||||||
UserHandle user) {
|
UserHandle user) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@@ -96,7 +97,7 @@ public class DeepShortcutManager {
|
|||||||
Bundle startActivityOptions, UserHandle user) {
|
Bundle startActivityOptions, UserHandle user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
|
public Drawable getShortcutIconDrawable(ShortcutInfo shortcutInfo, int density) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,16 +106,16 @@ public class DeepShortcutManager {
|
|||||||
*
|
*
|
||||||
* If packageName is null, returns all pinned shortcuts regardless of package.
|
* If packageName is null, returns all pinned shortcuts regardless of package.
|
||||||
*/
|
*/
|
||||||
public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName, UserHandle user) {
|
public List<ShortcutInfo> queryForPinnedShortcuts(String packageName, UserHandle user) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName,
|
public List<ShortcutInfo> queryForPinnedShortcuts(String packageName,
|
||||||
List<String> shortcutIds, UserHandle user) {
|
List<String> shortcutIds, UserHandle user) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandle user) {
|
public List<ShortcutInfo> queryForAllShortcuts(UserHandle user) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<solid android:color="?android:attr/colorAccent" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<merge
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="4dp"
|
||||||
|
android:background="@drawable/arrow_toast_rounded_background"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:elevation="2dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="16sp"/>
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/dismiss"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:layout_marginStart="2dp"
|
||||||
|
android:layout_marginEnd="2dp"
|
||||||
|
android:alpha="0.7"
|
||||||
|
android:src="@drawable/ic_remove_no_shadow"
|
||||||
|
android:tint="@android:color/white"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/accessibility_close_task"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/arrow"
|
||||||
|
android:elevation="2dp"
|
||||||
|
android:layout_width="10dp"
|
||||||
|
android:layout_height="8dp"
|
||||||
|
android:layout_marginTop="-2dp"
|
||||||
|
android:layout_gravity="center_horizontal"/>
|
||||||
|
</merge>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<com.android.launcher3.appprediction.PredictionRowView
|
||||||
|
android:id="@+id/prediction_row"
|
||||||
|
android:accessibilityPaneTitle="@string/title_app_suggestions"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<com.android.launcher3.appprediction.AppsDividerView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/apps_divider_view" />
|
||||||
|
</merge>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
android:layout_marginRight="20dp"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:indeterminateOnly="true"
|
||||||
|
android:indeterminateTint="?workspaceTextColor" />
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="chip_hint_foreground_color">#fff</color>
|
<color name="chip_hint_foreground_color">#fff</color>
|
||||||
|
|
||||||
|
<color name="all_apps_label_text">#61000000</color>
|
||||||
|
<color name="all_apps_label_text_dark">#61FFFFFF</color>
|
||||||
|
<color name="all_apps_prediction_row_separator">#3c000000</color>
|
||||||
|
<color name="all_apps_prediction_row_separator_dark">#3cffffff</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -12,4 +12,13 @@
|
|||||||
<dimen name="chip_text_top_padding">4dp</dimen>
|
<dimen name="chip_text_top_padding">4dp</dimen>
|
||||||
<dimen name="chip_text_start_padding">10dp</dimen>
|
<dimen name="chip_text_start_padding">10dp</dimen>
|
||||||
<dimen name="chip_text_size">14sp</dimen>
|
<dimen name="chip_text_size">14sp</dimen>
|
||||||
|
|
||||||
|
<dimen name="all_apps_prediction_row_divider_height">17dp</dimen>
|
||||||
|
<dimen name="all_apps_label_top_padding">16dp</dimen>
|
||||||
|
<dimen name="all_apps_label_bottom_padding">8dp</dimen>
|
||||||
|
<dimen name="all_apps_label_text_size">14sp</dimen>
|
||||||
|
<dimen name="all_apps_tip_bottom_margin">8dp</dimen>
|
||||||
|
<!-- The size of corner radius of the arrow in the arrow toast. -->
|
||||||
|
<dimen name="arrow_toast_corner_radius">2dp</dimen>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
<string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
|
<string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
|
||||||
|
|
||||||
|
<string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
|
||||||
|
|
||||||
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
|
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.android.launcher3;
|
||||||
|
|
||||||
|
import com.android.launcher3.appprediction.PredictionUiStateManager;
|
||||||
|
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
|
||||||
|
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
|
||||||
|
public class LauncherInitListenerEx extends LauncherInitListener {
|
||||||
|
|
||||||
|
public LauncherInitListenerEx(BiPredicate<Launcher, Boolean> onInitListener) {
|
||||||
|
super(onInitListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean init(Launcher launcher, boolean alreadyOnHome) {
|
||||||
|
PredictionUiStateManager.INSTANCE.get(launcher).switchClient(Client.OVERVIEW);
|
||||||
|
return super.init(launcher, alreadyOnHome);
|
||||||
|
}
|
||||||
|
}
|
||||||
+207
@@ -0,0 +1,207 @@
|
|||||||
|
/**
|
||||||
|
* 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.appprediction;
|
||||||
|
|
||||||
|
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||||
|
import static com.android.quickstep.logging.UserEventDispatcherExtension.ALL_APPS_PREDICTION_TIPS;
|
||||||
|
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.CornerPathEffect;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.android.launcher3.AbstractFloatingView;
|
||||||
|
import com.android.launcher3.Launcher;
|
||||||
|
import com.android.launcher3.LauncherState;
|
||||||
|
import com.android.launcher3.LauncherStateManager;
|
||||||
|
import com.android.launcher3.R;
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
public class AllAppsTipView extends AbstractFloatingView {
|
||||||
|
|
||||||
|
private static final String ALL_APPS_TIP_SEEN = "launcher.all_apps_tip_seen";
|
||||||
|
private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
|
||||||
|
private static final long SHOW_DELAY_MS = 200;
|
||||||
|
private static final long SHOW_DURATION_MS = 300;
|
||||||
|
private static final long HIDE_DURATION_MS = 100;
|
||||||
|
|
||||||
|
private final Launcher mLauncher;
|
||||||
|
private final Handler mHandler = new Handler();
|
||||||
|
|
||||||
|
private AllAppsTipView(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AllAppsTipView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
mLauncher = Launcher.getLauncher(context);
|
||||||
|
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
||||||
|
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
|
close(true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleClose(boolean animate) {
|
||||||
|
if (mIsOpen) {
|
||||||
|
if (animate) {
|
||||||
|
animate().alpha(0f)
|
||||||
|
.withLayer()
|
||||||
|
.setStartDelay(0)
|
||||||
|
.setDuration(HIDE_DURATION_MS)
|
||||||
|
.setInterpolator(Interpolators.ACCEL)
|
||||||
|
.withEndAction(() -> mLauncher.getDragLayer().removeView(this))
|
||||||
|
.start();
|
||||||
|
} else {
|
||||||
|
animate().cancel();
|
||||||
|
mLauncher.getDragLayer().removeView(this);
|
||||||
|
}
|
||||||
|
mLauncher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
|
||||||
|
mIsOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logActionCommand(int command) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isOfType(int type) {
|
||||||
|
return (type & TYPE_ON_BOARD_POPUP) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context) {
|
||||||
|
inflate(context, R.layout.arrow_toast, this);
|
||||||
|
|
||||||
|
TextView textView = findViewById(R.id.text);
|
||||||
|
textView.setText(R.string.all_apps_prediction_tip);
|
||||||
|
|
||||||
|
View dismissButton = findViewById(R.id.dismiss);
|
||||||
|
dismissButton.setOnClickListener(view -> {
|
||||||
|
mLauncher.getUserEventDispatcher().logActionTip(
|
||||||
|
LauncherEventUtil.DISMISS, ALL_APPS_PREDICTION_TIPS);
|
||||||
|
handleClose(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
View arrowView = findViewById(R.id.arrow);
|
||||||
|
ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
|
||||||
|
ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
|
||||||
|
arrowLp.width, arrowLp.height, false));
|
||||||
|
Paint arrowPaint = arrowDrawable.getPaint();
|
||||||
|
TypedValue typedValue = new TypedValue();
|
||||||
|
context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
|
||||||
|
arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId));
|
||||||
|
// The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
|
||||||
|
arrowPaint.setPathEffect(new CornerPathEffect(
|
||||||
|
context.getResources().getDimension(R.dimen.arrow_toast_corner_radius)));
|
||||||
|
arrowView.setBackground(arrowDrawable);
|
||||||
|
|
||||||
|
mIsOpen = true;
|
||||||
|
|
||||||
|
mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean showAllAppsTipIfNecessary(Launcher launcher) {
|
||||||
|
FloatingHeaderView floatingHeaderView = launcher.getAppsView().getFloatingHeaderView();
|
||||||
|
if (!floatingHeaderView.hasVisibleContent()
|
||||||
|
|| AbstractFloatingView.getOpenView(launcher,
|
||||||
|
TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE) != null
|
||||||
|
|| !launcher.isInState(ALL_APPS)
|
||||||
|
|| hasSeenAllAppsTip(launcher)
|
||||||
|
|| UserManagerCompat.getInstance(launcher).isDemoUser()
|
||||||
|
|| ActivityManager.isRunningInTestHarness()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllAppsTipView allAppsTipView = new AllAppsTipView(launcher.getAppsView().getContext(),
|
||||||
|
null);
|
||||||
|
launcher.getDragLayer().addView(allAppsTipView);
|
||||||
|
|
||||||
|
DragLayer.LayoutParams params = (DragLayer.LayoutParams) allAppsTipView.getLayoutParams();
|
||||||
|
params.gravity = Gravity.CENTER_HORIZONTAL;
|
||||||
|
|
||||||
|
int top = floatingHeaderView.findFixedRowByType(PredictionRowView.class).getTop();
|
||||||
|
allAppsTipView.setY(top - launcher.getResources().getDimensionPixelSize(
|
||||||
|
R.dimen.all_apps_tip_bottom_margin));
|
||||||
|
|
||||||
|
allAppsTipView.setAlpha(0);
|
||||||
|
allAppsTipView.animate()
|
||||||
|
.alpha(1f)
|
||||||
|
.withLayer()
|
||||||
|
.setStartDelay(SHOW_DELAY_MS)
|
||||||
|
.setDuration(SHOW_DURATION_MS)
|
||||||
|
.setInterpolator(Interpolators.DEACCEL)
|
||||||
|
.start();
|
||||||
|
|
||||||
|
launcher.getUserEventDispatcher().logActionTip(
|
||||||
|
LauncherEventUtil.VISIBLE, ALL_APPS_PREDICTION_TIPS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasSeenAllAppsTip(Launcher launcher) {
|
||||||
|
return launcher.getSharedPrefs().getBoolean(ALL_APPS_TIP_SEEN, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void scheduleShowIfNeeded(Launcher launcher) {
|
||||||
|
if (!hasSeenAllAppsTip(launcher)) {
|
||||||
|
launcher.getStateManager().addStateListener(
|
||||||
|
new LauncherStateManager.StateListener() {
|
||||||
|
@Override
|
||||||
|
public void onStateTransitionStart(LauncherState toState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStateTransitionComplete(LauncherState finalState) {
|
||||||
|
if (finalState == ALL_APPS) {
|
||||||
|
if (showAllAppsTipIfNecessary(launcher)) {
|
||||||
|
launcher.getStateManager().removeStateListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+308
@@ -0,0 +1,308 @@
|
|||||||
|
/**
|
||||||
|
* 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.appprediction;
|
||||||
|
|
||||||
|
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.Layout;
|
||||||
|
import android.text.StaticLayout;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
|
import com.android.launcher3.DeviceProfile;
|
||||||
|
import com.android.launcher3.Launcher;
|
||||||
|
import com.android.launcher3.LauncherState;
|
||||||
|
import com.android.launcher3.LauncherStateManager;
|
||||||
|
import com.android.launcher3.R;
|
||||||
|
import com.android.launcher3.allapps.FloatingHeaderRow;
|
||||||
|
import com.android.launcher3.allapps.FloatingHeaderView;
|
||||||
|
import com.android.launcher3.anim.PropertySetter;
|
||||||
|
import com.android.launcher3.util.Themes;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A view which shows a horizontal divider
|
||||||
|
*/
|
||||||
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
|
public class AppsDividerView extends View implements LauncherStateManager.StateListener,
|
||||||
|
FloatingHeaderRow {
|
||||||
|
|
||||||
|
private static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
|
||||||
|
private static final int SHOW_ALL_APPS_LABEL_ON_ALL_APPS_VISITED_COUNT = 20;
|
||||||
|
|
||||||
|
public enum DividerType {
|
||||||
|
NONE,
|
||||||
|
LINE,
|
||||||
|
ALL_APPS_LABEL
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Launcher mLauncher;
|
||||||
|
private final TextPaint mPaint = new TextPaint();
|
||||||
|
private DividerType mDividerType = DividerType.NONE;
|
||||||
|
|
||||||
|
private final @ColorInt int mStrokeColor;
|
||||||
|
private final @ColorInt int mAllAppsLabelTextColor;
|
||||||
|
|
||||||
|
private Layout mAllAppsLabelLayout;
|
||||||
|
private boolean mShowAllAppsLabel;
|
||||||
|
|
||||||
|
private FloatingHeaderView mParent;
|
||||||
|
private boolean mTabsHidden;
|
||||||
|
private FloatingHeaderRow[] mRows = FloatingHeaderRow.NO_ROWS;
|
||||||
|
|
||||||
|
private boolean mIsScrolledOut = false;
|
||||||
|
|
||||||
|
public AppsDividerView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppsDividerView(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppsDividerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
mLauncher = Launcher.getLauncher(context);
|
||||||
|
|
||||||
|
boolean isMainColorDark = Themes.getAttrBoolean(context, R.attr.isMainColorDark);
|
||||||
|
mPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
|
||||||
|
|
||||||
|
mStrokeColor = ContextCompat.getColor(context, isMainColorDark
|
||||||
|
? R.color.all_apps_prediction_row_separator_dark
|
||||||
|
: R.color.all_apps_prediction_row_separator);
|
||||||
|
|
||||||
|
mAllAppsLabelTextColor = ContextCompat.getColor(context, isMainColorDark
|
||||||
|
? R.color.all_apps_label_text_dark
|
||||||
|
: R.color.all_apps_label_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
|
||||||
|
mParent = parent;
|
||||||
|
mTabsHidden = tabsHidden;
|
||||||
|
mRows = rows;
|
||||||
|
updateDividerType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getExpectedHeight() {
|
||||||
|
return getPaddingTop() + getPaddingBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldDraw() {
|
||||||
|
return mDividerType != DividerType.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasVisibleContent() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDividerType() {
|
||||||
|
final DividerType dividerType;
|
||||||
|
if (!mTabsHidden) {
|
||||||
|
dividerType = DividerType.NONE;
|
||||||
|
} else {
|
||||||
|
// Check how many sections above me.
|
||||||
|
int sectionCount = 0;
|
||||||
|
for (FloatingHeaderRow row : mRows) {
|
||||||
|
if (row == this) {
|
||||||
|
break;
|
||||||
|
} else if (row.shouldDraw()) {
|
||||||
|
sectionCount ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mShowAllAppsLabel && sectionCount > 0) {
|
||||||
|
dividerType = DividerType.ALL_APPS_LABEL;
|
||||||
|
} else if (sectionCount == 1) {
|
||||||
|
dividerType = DividerType.LINE;
|
||||||
|
} else {
|
||||||
|
dividerType = DividerType.NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mDividerType != dividerType) {
|
||||||
|
mDividerType = dividerType;
|
||||||
|
int topPadding;
|
||||||
|
int bottomPadding;
|
||||||
|
switch (dividerType) {
|
||||||
|
case LINE:
|
||||||
|
topPadding = 0;
|
||||||
|
bottomPadding = getResources()
|
||||||
|
.getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height);
|
||||||
|
mPaint.setColor(mStrokeColor);
|
||||||
|
break;
|
||||||
|
case ALL_APPS_LABEL:
|
||||||
|
topPadding = getAllAppsLabelLayout().getHeight() + getResources()
|
||||||
|
.getDimensionPixelSize(R.dimen.all_apps_label_top_padding);
|
||||||
|
bottomPadding = getResources()
|
||||||
|
.getDimensionPixelSize(R.dimen.all_apps_label_bottom_padding);
|
||||||
|
mPaint.setColor(mAllAppsLabelTextColor);
|
||||||
|
break;
|
||||||
|
case NONE:
|
||||||
|
default:
|
||||||
|
topPadding = bottomPadding = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
setPadding(getPaddingLeft(), topPadding, getPaddingRight(), bottomPadding);
|
||||||
|
updateViewVisibility();
|
||||||
|
invalidate();
|
||||||
|
requestLayout();
|
||||||
|
if (mParent != null) {
|
||||||
|
mParent.onHeightUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateViewVisibility() {
|
||||||
|
setVisibility(mDividerType == DividerType.NONE
|
||||||
|
? GONE
|
||||||
|
: (mIsScrolledOut ? INVISIBLE : VISIBLE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
if (mDividerType == DividerType.LINE) {
|
||||||
|
int side = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
|
||||||
|
int y = getHeight() - (getPaddingBottom() / 2);
|
||||||
|
int x1 = getPaddingLeft() + side;
|
||||||
|
int x2 = getWidth() - getPaddingRight() - side;
|
||||||
|
canvas.drawLine(x1, y, x2, y, mPaint);
|
||||||
|
} else if (mDividerType == DividerType.ALL_APPS_LABEL) {
|
||||||
|
Layout textLayout = getAllAppsLabelLayout();
|
||||||
|
int x = getWidth() / 2 - textLayout.getWidth() / 2;
|
||||||
|
int y = getHeight() - getPaddingBottom() - textLayout.getHeight();
|
||||||
|
canvas.translate(x, y);
|
||||||
|
textLayout.draw(canvas);
|
||||||
|
canvas.translate(-x, -y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Layout getAllAppsLabelLayout() {
|
||||||
|
if (mAllAppsLabelLayout == null) {
|
||||||
|
mPaint.setAntiAlias(true);
|
||||||
|
mPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL));
|
||||||
|
mPaint.setTextSize(
|
||||||
|
getResources().getDimensionPixelSize(R.dimen.all_apps_label_text_size));
|
||||||
|
|
||||||
|
CharSequence allAppsLabelText = getResources().getText(R.string.all_apps_label);
|
||||||
|
mAllAppsLabelLayout = StaticLayout.Builder.obtain(
|
||||||
|
allAppsLabelText, 0, allAppsLabelText.length(), mPaint,
|
||||||
|
Math.round(mPaint.measureText(allAppsLabelText.toString())))
|
||||||
|
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||||
|
.setMaxLines(1)
|
||||||
|
.setIncludePad(true)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return mAllAppsLabelLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasOverlappingRendering() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
|
||||||
|
getPaddingBottom() + getPaddingTop());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
|
||||||
|
if (shouldShowAllAppsLabel()) {
|
||||||
|
mShowAllAppsLabel = true;
|
||||||
|
mLauncher.getStateManager().addStateListener(this);
|
||||||
|
updateDividerType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
mLauncher.getStateManager().removeStateListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStateTransitionStart(LauncherState toState) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStateTransitionComplete(LauncherState finalState) {
|
||||||
|
if (finalState == ALL_APPS) {
|
||||||
|
setAllAppsVisitedCount(getAllAppsVisitedCount() + 1);
|
||||||
|
} else {
|
||||||
|
if (mShowAllAppsLabel != shouldShowAllAppsLabel()) {
|
||||||
|
mShowAllAppsLabel = !mShowAllAppsLabel;
|
||||||
|
updateDividerType();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mShowAllAppsLabel) {
|
||||||
|
mLauncher.getStateManager().removeStateListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAllAppsVisitedCount(int count) {
|
||||||
|
mLauncher.getSharedPrefs().edit().putInt(ALL_APPS_VISITED_COUNT, count).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getAllAppsVisitedCount() {
|
||||||
|
return mLauncher.getSharedPrefs().getInt(ALL_APPS_VISITED_COUNT, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldShowAllAppsLabel() {
|
||||||
|
return getAllAppsVisitedCount() < SHOW_ALL_APPS_LABEL_ON_ALL_APPS_VISITED_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInsets(Rect insets, DeviceProfile grid) {
|
||||||
|
int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
|
||||||
|
+ grid.cellLayoutPaddingLeftRightPx;
|
||||||
|
setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
|
||||||
|
PropertySetter setter, Interpolator fadeInterpolator) {
|
||||||
|
// Don't use setViewAlpha as we want to control the visibility ourselves.
|
||||||
|
setter.setFloat(this, ALPHA, hasContent ? 1 : 0, fadeInterpolator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVerticalScroll(int scroll, boolean isScrolledOut) {
|
||||||
|
setTranslationY(scroll);
|
||||||
|
mIsScrolledOut = isScrolledOut;
|
||||||
|
updateViewVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<AppsDividerView> getTypeClass() {
|
||||||
|
return AppsDividerView.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* 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.appprediction;
|
||||||
|
|
||||||
|
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.android.launcher3.AppInfo;
|
||||||
|
import com.android.launcher3.ItemInfoWithIcon;
|
||||||
|
import com.android.launcher3.allapps.AllAppsStore;
|
||||||
|
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||||
|
import com.android.launcher3.util.ComponentKey;
|
||||||
|
|
||||||
|
public class ComponentKeyMapper {
|
||||||
|
|
||||||
|
protected final ComponentKey componentKey;
|
||||||
|
private final Context mContext;
|
||||||
|
private final DynamicItemCache mCache;
|
||||||
|
|
||||||
|
public ComponentKeyMapper(Context context, ComponentKey key, DynamicItemCache cache) {
|
||||||
|
mContext = context;
|
||||||
|
componentKey = key;
|
||||||
|
mCache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPackage() {
|
||||||
|
return componentKey.componentName.getPackageName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComponentClass() {
|
||||||
|
return componentKey.componentName.getClassName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentKey getComponentKey() {
|
||||||
|
return componentKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return componentKey.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemInfoWithIcon getApp(AllAppsStore store) {
|
||||||
|
AppInfo item = store.getApp(componentKey);
|
||||||
|
if (item != null) {
|
||||||
|
return item;
|
||||||
|
} else if (getComponentClass().equals(COMPONENT_CLASS_MARKER)) {
|
||||||
|
return mCache.getInstantApp(componentKey.componentName.getPackageName());
|
||||||
|
} else if (componentKey instanceof ShortcutKey) {
|
||||||
|
return mCache.getShortcutInfo((ShortcutKey) componentKey);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+242
@@ -0,0 +1,242 @@
|
|||||||
|
/**
|
||||||
|
* 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.appprediction;
|
||||||
|
|
||||||
|
import static android.content.pm.PackageManager.MATCH_INSTANT;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.pm.ShortcutInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.launcher3.LauncherAppState;
|
||||||
|
import com.android.launcher3.LauncherModel;
|
||||||
|
import com.android.launcher3.WorkspaceItemInfo;
|
||||||
|
import com.android.launcher3.icons.IconCache;
|
||||||
|
import com.android.launcher3.icons.LauncherIcons;
|
||||||
|
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||||
|
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||||
|
import com.android.launcher3.util.InstantAppResolver;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class which loads and caches predicted items like instant apps and shortcuts, before
|
||||||
|
* they can be displayed on the UI
|
||||||
|
*/
|
||||||
|
public class DynamicItemCache {
|
||||||
|
|
||||||
|
private static final String TAG = "DynamicItemCache";
|
||||||
|
private static final boolean DEBUG = false;
|
||||||
|
private static final String DEFAULT_URL = "default-url";
|
||||||
|
|
||||||
|
private static final int BG_MSG_LOAD_SHORTCUTS = 1;
|
||||||
|
private static final int BG_MSG_LOAD_INSTANT_APPS = 2;
|
||||||
|
|
||||||
|
private static final int UI_MSG_UPDATE_SHORTCUTS = 1;
|
||||||
|
private static final int UI_MSG_UPDATE_INSTANT_APPS = 2;
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private final Handler mWorker;
|
||||||
|
private final Handler mUiHandler;
|
||||||
|
private final InstantAppResolver mInstantAppResolver;
|
||||||
|
private final Runnable mOnUpdateCallback;
|
||||||
|
|
||||||
|
private final Map<ShortcutKey, WorkspaceItemInfo> mShortcuts;
|
||||||
|
private final Map<String, InstantAppItemInfo> mInstantApps;
|
||||||
|
|
||||||
|
public DynamicItemCache(Context context, Runnable onUpdateCallback) {
|
||||||
|
mContext = context;
|
||||||
|
mWorker = new Handler(LauncherModel.getWorkerLooper(), this::handleWorkerMessage);
|
||||||
|
mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
|
||||||
|
mInstantAppResolver = InstantAppResolver.newInstance(context);
|
||||||
|
mOnUpdateCallback = onUpdateCallback;
|
||||||
|
|
||||||
|
mShortcuts = new HashMap<>();
|
||||||
|
mInstantApps = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cacheItems(List<ShortcutKey> shortcutKeys, List<String> pkgNames) {
|
||||||
|
if (!shortcutKeys.isEmpty()) {
|
||||||
|
mWorker.removeMessages(BG_MSG_LOAD_SHORTCUTS);
|
||||||
|
Message.obtain(mWorker, BG_MSG_LOAD_SHORTCUTS, shortcutKeys).sendToTarget();
|
||||||
|
}
|
||||||
|
if (!pkgNames.isEmpty()) {
|
||||||
|
mWorker.removeMessages(BG_MSG_LOAD_INSTANT_APPS);
|
||||||
|
Message.obtain(mWorker, BG_MSG_LOAD_INSTANT_APPS, pkgNames).sendToTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleWorkerMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case BG_MSG_LOAD_SHORTCUTS: {
|
||||||
|
List<ShortcutKey> shortcutKeys = msg.obj != null ?
|
||||||
|
(List<ShortcutKey>) msg.obj : Collections.EMPTY_LIST;
|
||||||
|
Map<ShortcutKey, WorkspaceItemInfo> shortcutKeyAndInfos = new ArrayMap<>();
|
||||||
|
for (ShortcutKey shortcutKey : shortcutKeys) {
|
||||||
|
WorkspaceItemInfo workspaceItemInfo = loadShortcutWorker(shortcutKey);
|
||||||
|
if (workspaceItemInfo != null) {
|
||||||
|
shortcutKeyAndInfos.put(shortcutKey, workspaceItemInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message.obtain(mUiHandler, UI_MSG_UPDATE_SHORTCUTS, shortcutKeyAndInfos)
|
||||||
|
.sendToTarget();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case BG_MSG_LOAD_INSTANT_APPS: {
|
||||||
|
List<String> pkgNames = msg.obj != null ?
|
||||||
|
(List<String>) msg.obj : Collections.EMPTY_LIST;
|
||||||
|
List<InstantAppItemInfo> instantAppItemInfos = new ArrayList<>();
|
||||||
|
for (String pkgName : pkgNames) {
|
||||||
|
InstantAppItemInfo instantAppItemInfo = loadInstantApp(pkgName);
|
||||||
|
if (instantAppItemInfo != null) {
|
||||||
|
instantAppItemInfos.add(instantAppItemInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message.obtain(mUiHandler, UI_MSG_UPDATE_INSTANT_APPS, instantAppItemInfos)
|
||||||
|
.sendToTarget();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleUiMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case UI_MSG_UPDATE_SHORTCUTS: {
|
||||||
|
mShortcuts.clear();
|
||||||
|
mShortcuts.putAll((Map<ShortcutKey, WorkspaceItemInfo>) msg.obj);
|
||||||
|
mOnUpdateCallback.run();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case UI_MSG_UPDATE_INSTANT_APPS: {
|
||||||
|
List<InstantAppItemInfo> instantAppItemInfos = (List<InstantAppItemInfo>) msg.obj;
|
||||||
|
mInstantApps.clear();
|
||||||
|
for (InstantAppItemInfo instantAppItemInfo : instantAppItemInfos) {
|
||||||
|
mInstantApps.put(instantAppItemInfo.getTargetComponent().getPackageName(),
|
||||||
|
instantAppItemInfo);
|
||||||
|
}
|
||||||
|
mOnUpdateCallback.run();
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, String.format("Cache size: %d, Cache: %s",
|
||||||
|
mInstantApps.size(), mInstantApps.toString()));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private WorkspaceItemInfo loadShortcutWorker(ShortcutKey shortcutKey) {
|
||||||
|
DeepShortcutManager mgr = DeepShortcutManager.getInstance(mContext);
|
||||||
|
List<ShortcutInfo> details = mgr.queryForFullDetails(
|
||||||
|
shortcutKey.componentName.getPackageName(),
|
||||||
|
Collections.<String>singletonList(shortcutKey.getId()),
|
||||||
|
shortcutKey.user);
|
||||||
|
if (!details.isEmpty()) {
|
||||||
|
WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext);
|
||||||
|
try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
|
||||||
|
si.applyFrom(li.createShortcutIcon(details.get(0), true /* badged */, null));
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.e(TAG, "Error loading shortcut icon for " + shortcutKey.toString());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return si;
|
||||||
|
}
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "No shortcut found: " + shortcutKey.toString());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InstantAppItemInfo loadInstantApp(String pkgName) {
|
||||||
|
PackageManager pm = mContext.getPackageManager();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ApplicationInfo ai = pm.getApplicationInfo(pkgName, 0);
|
||||||
|
if (!mInstantAppResolver.isInstantApp(ai)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = retrieveDefaultUrl(pkgName, pm);
|
||||||
|
if (url == null) {
|
||||||
|
Log.w(TAG, "no default-url available for pkg " + pkgName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW)
|
||||||
|
.addCategory(Intent.CATEGORY_BROWSABLE)
|
||||||
|
.setData(Uri.parse(url));
|
||||||
|
InstantAppItemInfo info = new InstantAppItemInfo(intent, pkgName);
|
||||||
|
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
|
||||||
|
iconCache.getTitleAndIcon(info, false);
|
||||||
|
if (info.iconBitmap == null || iconCache.isDefaultIcon(info.iconBitmap, info.user)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String retrieveDefaultUrl(String pkgName, PackageManager pm) {
|
||||||
|
Intent mainIntent = new Intent().setAction(Intent.ACTION_MAIN)
|
||||||
|
.addCategory(Intent.CATEGORY_LAUNCHER).setPackage(pkgName);
|
||||||
|
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
|
||||||
|
mainIntent, MATCH_INSTANT | PackageManager.GET_META_DATA);
|
||||||
|
String url = null;
|
||||||
|
for (ResolveInfo resolveInfo : resolveInfos) {
|
||||||
|
if (resolveInfo.activityInfo.metaData != null
|
||||||
|
&& resolveInfo.activityInfo.metaData.containsKey(DEFAULT_URL)) {
|
||||||
|
url = resolveInfo.activityInfo.metaData.getString(DEFAULT_URL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
public InstantAppItemInfo getInstantApp(String pkgName) {
|
||||||
|
return mInstantApps.get(pkgName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
public WorkspaceItemInfo getShortcutInfo(ShortcutKey key) {
|
||||||
|
return mShortcuts.get(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 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.appprediction;
|
||||||
|
|
||||||
|
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import com.android.launcher3.AppInfo;
|
||||||
|
import com.android.launcher3.LauncherSettings;
|
||||||
|
import com.android.launcher3.WorkspaceItemInfo;
|
||||||
|
|
||||||
|
public class InstantAppItemInfo extends AppInfo {
|
||||||
|
|
||||||
|
public InstantAppItemInfo(Intent intent, String packageName) {
|
||||||
|
this.intent = intent;
|
||||||
|
this.componentName = new ComponentName(packageName, COMPONENT_CLASS_MARKER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentName getTargetComponent() {
|
||||||
|
return componentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WorkspaceItemInfo makeWorkspaceItem() {
|
||||||
|
WorkspaceItemInfo workspaceItemInfo = super.makeWorkspaceItem();
|
||||||
|
workspaceItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
|
||||||
|
workspaceItemInfo.status = WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON
|
||||||
|
| WorkspaceItemInfo.FLAG_RESTORE_STARTED
|
||||||
|
| WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI;
|
||||||
|
workspaceItemInfo.intent.setPackage(componentName.getPackageName());
|
||||||
|
return workspaceItemInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
+214
@@ -0,0 +1,214 @@
|
|||||||
|
/**
|
||||||
|
* 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.appprediction;
|
||||||
|
|
||||||
|
import static com.android.launcher3.appprediction.PredictionUiStateManager.KEY_APP_SUGGESTION;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.prediction.AppPredictionContext;
|
||||||
|
import android.app.prediction.AppPredictionManager;
|
||||||
|
import android.app.prediction.AppPredictor;
|
||||||
|
import android.app.prediction.AppTarget;
|
||||||
|
import android.app.prediction.AppTargetEvent;
|
||||||
|
import android.app.prediction.AppTargetId;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.launcher3.InvariantDeviceProfile;
|
||||||
|
import com.android.launcher3.Utilities;
|
||||||
|
import com.android.launcher3.model.AppLaunchTracker;
|
||||||
|
import com.android.launcher3.util.UiThreadHelper;
|
||||||
|
|
||||||
|
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
|
||||||
|
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclass of app tracker which publishes the data to the prediction engine and gets back results.
|
||||||
|
*/
|
||||||
|
@TargetApi(Build.VERSION_CODES.Q)
|
||||||
|
public class PredictionAppTracker extends AppLaunchTracker
|
||||||
|
implements OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
private static final String TAG = "PredictionAppTracker";
|
||||||
|
private static final boolean DBG = false;
|
||||||
|
|
||||||
|
private static final int MSG_INIT = 0;
|
||||||
|
private static final int MSG_DESTROY = 1;
|
||||||
|
private static final int MSG_LAUNCH = 2;
|
||||||
|
private static final int MSG_PREDICT = 3;
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private final Handler mMessageHandler;
|
||||||
|
|
||||||
|
private boolean mEnabled;
|
||||||
|
|
||||||
|
// Accessed only on worker thread
|
||||||
|
private AppPredictor mHomeAppPredictor;
|
||||||
|
private AppPredictor mRecentsOverviewPredictor;
|
||||||
|
|
||||||
|
public PredictionAppTracker(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
mMessageHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleMessage);
|
||||||
|
|
||||||
|
SharedPreferences prefs = Utilities.getPrefs(context);
|
||||||
|
setEnabled(prefs.getBoolean(KEY_APP_SUGGESTION, true));
|
||||||
|
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
|
||||||
|
// Reinitialize everything
|
||||||
|
setEnabled(mEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||||
|
if (KEY_APP_SUGGESTION.equals(key)) {
|
||||||
|
setEnabled(prefs.getBoolean(KEY_APP_SUGGESTION, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private void destroy() {
|
||||||
|
if (mHomeAppPredictor != null) {
|
||||||
|
mHomeAppPredictor.destroy();
|
||||||
|
mHomeAppPredictor = null;
|
||||||
|
}
|
||||||
|
if (mRecentsOverviewPredictor != null) {
|
||||||
|
mRecentsOverviewPredictor.destroy();
|
||||||
|
mRecentsOverviewPredictor = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private AppPredictor createPredictor(Client client, int count) {
|
||||||
|
AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
|
||||||
|
|
||||||
|
AppPredictor predictor = apm.createAppPredictionSession(
|
||||||
|
new AppPredictionContext.Builder(mContext)
|
||||||
|
.setUiSurface(client.id)
|
||||||
|
.setPredictedTargetCount(count)
|
||||||
|
.build());
|
||||||
|
predictor.registerPredictionUpdates(mContext.getMainExecutor(),
|
||||||
|
PredictionUiStateManager.INSTANCE.get(mContext).appPredictorCallback(client));
|
||||||
|
predictor.requestPredictionUpdate();
|
||||||
|
return predictor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private boolean handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_INIT: {
|
||||||
|
// Destroy any existing clients
|
||||||
|
destroy();
|
||||||
|
|
||||||
|
// Initialize the clients
|
||||||
|
int count = InvariantDeviceProfile.INSTANCE.get(mContext).numColumns;
|
||||||
|
mHomeAppPredictor = createPredictor(Client.HOME, count);
|
||||||
|
mRecentsOverviewPredictor = createPredictor(Client.OVERVIEW, count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case MSG_DESTROY: {
|
||||||
|
destroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case MSG_LAUNCH: {
|
||||||
|
if (mEnabled && mHomeAppPredictor != null) {
|
||||||
|
mHomeAppPredictor.notifyAppTargetEvent((AppTargetEvent) msg.obj);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case MSG_PREDICT: {
|
||||||
|
if (mEnabled && mHomeAppPredictor != null) {
|
||||||
|
String client = (String) msg.obj;
|
||||||
|
if (Client.HOME.id.equals(client)) {
|
||||||
|
mHomeAppPredictor.requestPredictionUpdate();
|
||||||
|
} else {
|
||||||
|
mRecentsOverviewPredictor.requestPredictionUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void onReturnedToHome() {
|
||||||
|
String client = Client.HOME.id;
|
||||||
|
mMessageHandler.removeMessages(MSG_PREDICT, client);
|
||||||
|
Message.obtain(mMessageHandler, MSG_PREDICT, client).sendToTarget();
|
||||||
|
if (DBG) {
|
||||||
|
Log.d(TAG, String.format("Sent immediate message to update %s", client));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
public void setEnabled(boolean isEnabled) {
|
||||||
|
mEnabled = isEnabled;
|
||||||
|
if (isEnabled) {
|
||||||
|
mMessageHandler.removeMessages(MSG_DESTROY);
|
||||||
|
mMessageHandler.sendEmptyMessage(MSG_INIT);
|
||||||
|
} else {
|
||||||
|
mMessageHandler.removeMessages(MSG_INIT);
|
||||||
|
mMessageHandler.sendEmptyMessage(MSG_DESTROY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
|
||||||
|
String container) {
|
||||||
|
// TODO: Use the full shortcut info
|
||||||
|
AppTarget target = new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutId))
|
||||||
|
.setTarget(packageName, user)
|
||||||
|
.setClassName(shortcutId)
|
||||||
|
.build();
|
||||||
|
sendLaunch(target, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void onStartApp(ComponentName cn, UserHandle user, String container) {
|
||||||
|
if (cn != null) {
|
||||||
|
AppTarget target = new AppTarget.Builder(new AppTargetId("app:" + cn))
|
||||||
|
.setTarget(cn.getPackageName(), user)
|
||||||
|
.setClassName(cn.getClassName())
|
||||||
|
.build();
|
||||||
|
sendLaunch(target, container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void sendLaunch(AppTarget target, String container) {
|
||||||
|
AppTargetEvent event = new AppTargetEvent.Builder(target, AppTargetEvent.ACTION_LAUNCH)
|
||||||
|
.setLaunchLocation(container == null ? CONTAINER_DEFAULT : container)
|
||||||
|
.build();
|
||||||
|
Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
+411
@@ -0,0 +1,411 @@
|
|||||||
|
/**
|
||||||
|
* 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.appprediction;
|
||||||
|
|
||||||
|
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||||
|
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.IntProperty;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import com.android.launcher3.AppInfo;
|
||||||
|
import com.android.launcher3.BubbleTextView;
|
||||||
|
import com.android.launcher3.DeviceProfile;
|
||||||
|
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
||||||
|
import com.android.launcher3.ItemInfo;
|
||||||
|
import com.android.launcher3.ItemInfoWithIcon;
|
||||||
|
import com.android.launcher3.Launcher;
|
||||||
|
import com.android.launcher3.LauncherAppState;
|
||||||
|
import com.android.launcher3.R;
|
||||||
|
import com.android.launcher3.WorkspaceItemInfo;
|
||||||
|
import com.android.launcher3.allapps.AllAppsStore;
|
||||||
|
import com.android.launcher3.allapps.FloatingHeaderRow;
|
||||||
|
import com.android.launcher3.allapps.FloatingHeaderView;
|
||||||
|
import com.android.launcher3.anim.AlphaUpdateListener;
|
||||||
|
import com.android.launcher3.anim.PropertySetter;
|
||||||
|
import com.android.launcher3.config.FeatureFlags;
|
||||||
|
import com.android.launcher3.keyboard.FocusIndicatorHelper;
|
||||||
|
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
|
||||||
|
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
|
||||||
|
import com.android.launcher3.model.AppLaunchTracker;
|
||||||
|
import com.android.launcher3.touch.ItemClickHandler;
|
||||||
|
import com.android.launcher3.touch.ItemLongClickListener;
|
||||||
|
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||||
|
import com.android.launcher3.util.Themes;
|
||||||
|
import com.android.quickstep.AnimatedFloat;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.P)
|
||||||
|
public class PredictionRowView extends LinearLayout implements
|
||||||
|
LogContainerProvider, OnDeviceProfileChangeListener, FloatingHeaderRow {
|
||||||
|
|
||||||
|
private static final String TAG = "PredictionRowView";
|
||||||
|
|
||||||
|
private static final IntProperty<PredictionRowView> TEXT_ALPHA =
|
||||||
|
new IntProperty<PredictionRowView>("textAlpha") {
|
||||||
|
@Override
|
||||||
|
public void setValue(PredictionRowView view, int alpha) {
|
||||||
|
view.setTextAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer get(PredictionRowView view) {
|
||||||
|
return view.mIconCurrentTextAlpha;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final Interpolator ALPHA_FACTOR_INTERPOLATOR =
|
||||||
|
(t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f;
|
||||||
|
|
||||||
|
private static final OnClickListener PREDICTION_CLICK_LISTENER =
|
||||||
|
ItemClickHandler.getInstance(AppLaunchTracker.CONTAINER_PREDICTIONS);
|
||||||
|
|
||||||
|
private final Launcher mLauncher;
|
||||||
|
private final PredictionUiStateManager mPredictionUiStateManager;
|
||||||
|
private final int mNumPredictedAppsPerRow;
|
||||||
|
|
||||||
|
// The set of predicted app component names
|
||||||
|
private final List<ComponentKeyMapper> mPredictedAppComponents = new ArrayList<>();
|
||||||
|
// The set of predicted apps resolved from the component names and the current set of apps
|
||||||
|
private final ArrayList<ItemInfoWithIcon> mPredictedApps = new ArrayList<>();
|
||||||
|
// Helper to drawing the focus indicator.
|
||||||
|
private final FocusIndicatorHelper mFocusHelper;
|
||||||
|
|
||||||
|
private final int mIconTextColor;
|
||||||
|
private final int mIconFullTextAlpha;
|
||||||
|
private int mIconCurrentTextAlpha;
|
||||||
|
|
||||||
|
private FloatingHeaderView mParent;
|
||||||
|
private boolean mScrolledOut;
|
||||||
|
|
||||||
|
private float mScrollTranslation = 0;
|
||||||
|
private final AnimatedFloat mContentAlphaFactor =
|
||||||
|
new AnimatedFloat(this::updateTranslationAndAlpha);
|
||||||
|
private final AnimatedFloat mOverviewScrollFactor =
|
||||||
|
new AnimatedFloat(this::updateTranslationAndAlpha);
|
||||||
|
|
||||||
|
private View mLoadingProgress;
|
||||||
|
|
||||||
|
private boolean mPredictionsEnabled = false;
|
||||||
|
|
||||||
|
public PredictionRowView(@NonNull Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PredictionRowView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
|
||||||
|
mFocusHelper = new SimpleFocusIndicatorHelper(this);
|
||||||
|
|
||||||
|
mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numColumns;
|
||||||
|
mLauncher = Launcher.getLauncher(context);
|
||||||
|
mLauncher.addOnDeviceProfileChangeListener(this);
|
||||||
|
|
||||||
|
mPredictionUiStateManager = PredictionUiStateManager.INSTANCE.get(context);
|
||||||
|
|
||||||
|
mIconTextColor = Themes.getAttrColor(context, android.R.attr.textColorSecondary);
|
||||||
|
mIconFullTextAlpha = Color.alpha(mIconTextColor);
|
||||||
|
mIconCurrentTextAlpha = mIconFullTextAlpha;
|
||||||
|
|
||||||
|
updateVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
|
||||||
|
mPredictionUiStateManager.setTargetAppsView(mLauncher.getAppsView());
|
||||||
|
getAppsStore().registerIconContainer(this);
|
||||||
|
AllAppsTipView.scheduleShowIfNeeded(mLauncher);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AllAppsStore getAppsStore() {
|
||||||
|
return mLauncher.getAppsView().getAppsStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
|
||||||
|
mPredictionUiStateManager.setTargetAppsView(null);
|
||||||
|
getAppsStore().unregisterIconContainer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
|
||||||
|
mParent = parent;
|
||||||
|
setPredictionsEnabled(mPredictionUiStateManager.arePredictionsEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPredictionsEnabled(boolean predictionsEnabled) {
|
||||||
|
if (predictionsEnabled != mPredictionsEnabled) {
|
||||||
|
mPredictionsEnabled = predictionsEnabled;
|
||||||
|
updateVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateVisibility() {
|
||||||
|
setVisibility(mPredictionsEnabled ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getExpectedHeight(),
|
||||||
|
MeasureSpec.EXACTLY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void dispatchDraw(Canvas canvas) {
|
||||||
|
mFocusHelper.draw(canvas);
|
||||||
|
super.dispatchDraw(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getExpectedHeight() {
|
||||||
|
return getVisibility() == GONE ? 0 :
|
||||||
|
Launcher.getLauncher(getContext()).getDeviceProfile().allAppsCellHeightPx
|
||||||
|
+ getPaddingTop() + getPaddingBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldDraw() {
|
||||||
|
return getVisibility() != GONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasVisibleContent() {
|
||||||
|
return mPredictionUiStateManager.arePredictionsEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the predicted apps.
|
||||||
|
*/
|
||||||
|
public List<ItemInfoWithIcon> getPredictedApps() {
|
||||||
|
return mPredictedApps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current set of predicted apps.
|
||||||
|
*
|
||||||
|
* This can be called before we get the full set of applications, we should merge the results
|
||||||
|
* only in onPredictionsUpdated() which is idempotent.
|
||||||
|
*
|
||||||
|
* If the number of predicted apps is the same as the previous list of predicted apps,
|
||||||
|
* we can optimize by swapping them in place.
|
||||||
|
*/
|
||||||
|
public void setPredictedApps(boolean predictionsEnabled, List<ComponentKeyMapper> apps) {
|
||||||
|
setPredictionsEnabled(predictionsEnabled);
|
||||||
|
mPredictedAppComponents.clear();
|
||||||
|
mPredictedAppComponents.addAll(apps);
|
||||||
|
|
||||||
|
mPredictedApps.clear();
|
||||||
|
mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
|
||||||
|
applyPredictionApps();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeviceProfileChanged(DeviceProfile dp) {
|
||||||
|
removeAllViews();
|
||||||
|
applyPredictionApps();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyPredictionApps() {
|
||||||
|
if (mLoadingProgress != null) {
|
||||||
|
removeView(mLoadingProgress);
|
||||||
|
}
|
||||||
|
if (!mPredictionsEnabled) {
|
||||||
|
mParent.onHeightUpdated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getChildCount() != mNumPredictedAppsPerRow) {
|
||||||
|
while (getChildCount() > mNumPredictedAppsPerRow) {
|
||||||
|
removeViewAt(0);
|
||||||
|
}
|
||||||
|
while (getChildCount() < mNumPredictedAppsPerRow) {
|
||||||
|
BubbleTextView icon = (BubbleTextView) mLauncher.getLayoutInflater().inflate(
|
||||||
|
R.layout.all_apps_icon, this, false);
|
||||||
|
icon.setOnClickListener(PREDICTION_CLICK_LISTENER);
|
||||||
|
icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
|
||||||
|
icon.setLongPressTimeoutFactor(1f);
|
||||||
|
icon.setOnFocusChangeListener(mFocusHelper);
|
||||||
|
|
||||||
|
LayoutParams lp = (LayoutParams) icon.getLayoutParams();
|
||||||
|
// Ensure the all apps icon height matches the workspace icons in portrait mode.
|
||||||
|
lp.height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
|
||||||
|
lp.width = 0;
|
||||||
|
lp.weight = 1;
|
||||||
|
addView(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int predictionCount = mPredictedApps.size();
|
||||||
|
int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha);
|
||||||
|
|
||||||
|
for (int i = 0; i < getChildCount(); i++) {
|
||||||
|
BubbleTextView icon = (BubbleTextView) getChildAt(i);
|
||||||
|
icon.reset();
|
||||||
|
if (predictionCount > i) {
|
||||||
|
icon.setVisibility(View.VISIBLE);
|
||||||
|
if (mPredictedApps.get(i) instanceof AppInfo) {
|
||||||
|
icon.applyFromApplicationInfo((AppInfo) mPredictedApps.get(i));
|
||||||
|
} else if (mPredictedApps.get(i) instanceof WorkspaceItemInfo) {
|
||||||
|
icon.applyFromWorkspaceItem((WorkspaceItemInfo) mPredictedApps.get(i));
|
||||||
|
}
|
||||||
|
icon.setTextColor(iconColor);
|
||||||
|
} else {
|
||||||
|
icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (predictionCount == 0) {
|
||||||
|
if (mLoadingProgress == null) {
|
||||||
|
mLoadingProgress = LayoutInflater.from(getContext())
|
||||||
|
.inflate(R.layout.prediction_load_progress, this, false);
|
||||||
|
}
|
||||||
|
addView(mLoadingProgress);
|
||||||
|
} else {
|
||||||
|
mLoadingProgress = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mParent.onHeightUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemInfoWithIcon> processPredictedAppComponents(List<ComponentKeyMapper> components) {
|
||||||
|
if (getAppsStore().getApps().isEmpty()) {
|
||||||
|
// Apps have not been bound yet.
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ItemInfoWithIcon> predictedApps = new ArrayList<>();
|
||||||
|
for (ComponentKeyMapper mapper : components) {
|
||||||
|
ItemInfoWithIcon info = mapper.getApp(getAppsStore());
|
||||||
|
if (info != null) {
|
||||||
|
predictedApps.add(info);
|
||||||
|
} else {
|
||||||
|
if (FeatureFlags.IS_DOGFOOD_BUILD) {
|
||||||
|
Log.e(TAG, "Predicted app not found: " + mapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Stop at the number of predicted apps
|
||||||
|
if (predictedApps.size() == mNumPredictedAppsPerRow) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return predictedApps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
|
||||||
|
LauncherLogProto.Target targetParent) {
|
||||||
|
for (int i = 0; i < mPredictedApps.size(); i++) {
|
||||||
|
ItemInfoWithIcon appInfo = mPredictedApps.get(i);
|
||||||
|
if (appInfo == info) {
|
||||||
|
targetParent.containerType = LauncherLogProto.ContainerType.PREDICTION;
|
||||||
|
target.predictedRank = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextAlpha(int alpha) {
|
||||||
|
mIconCurrentTextAlpha = alpha;
|
||||||
|
int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha);
|
||||||
|
|
||||||
|
if (mLoadingProgress == null) {
|
||||||
|
for (int i = 0; i < getChildCount(); i++) {
|
||||||
|
((BubbleTextView) getChildAt(i)).setTextColor(iconColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasOverlappingRendering() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVerticalScroll(int scroll, boolean isScrolledOut) {
|
||||||
|
mScrolledOut = isScrolledOut;
|
||||||
|
updateTranslationAndAlpha();
|
||||||
|
if (!isScrolledOut) {
|
||||||
|
mScrollTranslation = scroll;
|
||||||
|
updateTranslationAndAlpha();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTranslationAndAlpha() {
|
||||||
|
if (mPredictionsEnabled) {
|
||||||
|
setTranslationY((1 - mOverviewScrollFactor.value) * mScrollTranslation);
|
||||||
|
|
||||||
|
float factor = ALPHA_FACTOR_INTERPOLATOR.getInterpolation(mOverviewScrollFactor.value);
|
||||||
|
float endAlpha = factor + (1 - factor) * (mScrolledOut ? 0 : 1);
|
||||||
|
setAlpha(mContentAlphaFactor.value * endAlpha);
|
||||||
|
AlphaUpdateListener.updateVisibility(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
|
||||||
|
PropertySetter setter, Interpolator fadeInterpolator) {
|
||||||
|
boolean isDrawn = getAlpha() > 0;
|
||||||
|
int textAlpha = hasHeaderExtra
|
||||||
|
? (hasContent ? mIconFullTextAlpha : 0) // Text follows the content visibility
|
||||||
|
: mIconCurrentTextAlpha; // Leave as before
|
||||||
|
if (!isDrawn) {
|
||||||
|
// If the header is not drawn, no need to animate the text alpha
|
||||||
|
setTextAlpha(textAlpha);
|
||||||
|
} else {
|
||||||
|
setter.setInt(this, TEXT_ALPHA, textAlpha, fadeInterpolator);
|
||||||
|
}
|
||||||
|
|
||||||
|
setter.setFloat(mOverviewScrollFactor, AnimatedFloat.VALUE,
|
||||||
|
(hasHeaderExtra && !hasContent) ? 1 : 0, LINEAR);
|
||||||
|
setter.setFloat(mContentAlphaFactor, AnimatedFloat.VALUE, hasHeaderExtra ? 1 : 0,
|
||||||
|
fadeInterpolator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInsets(Rect insets, DeviceProfile grid) {
|
||||||
|
int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
|
||||||
|
+ grid.cellLayoutPaddingLeftRightPx;
|
||||||
|
setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<PredictionRowView> getTypeClass() {
|
||||||
|
return PredictionRowView.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
+330
@@ -0,0 +1,330 @@
|
|||||||
|
/**
|
||||||
|
* 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.appprediction;
|
||||||
|
|
||||||
|
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
|
||||||
|
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||||
|
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
|
||||||
|
|
||||||
|
import android.app.prediction.AppPredictor;
|
||||||
|
import android.app.prediction.AppTarget;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
|
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||||
|
|
||||||
|
import com.android.launcher3.AppInfo;
|
||||||
|
import com.android.launcher3.InvariantDeviceProfile;
|
||||||
|
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
|
||||||
|
import com.android.launcher3.ItemInfoWithIcon;
|
||||||
|
import com.android.launcher3.Launcher;
|
||||||
|
import com.android.launcher3.LauncherAppState;
|
||||||
|
import com.android.launcher3.Utilities;
|
||||||
|
import com.android.launcher3.allapps.AllAppsContainerView;
|
||||||
|
import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
|
||||||
|
import com.android.launcher3.icons.IconCache;
|
||||||
|
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
|
||||||
|
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||||
|
import com.android.launcher3.util.ComponentKey;
|
||||||
|
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler responsible to updating the UI due to predicted apps changes. Operations:
|
||||||
|
* 1) Pushes the predicted apps to all-apps. If all-apps is visible, waits until it becomes
|
||||||
|
* invisible again before applying the changes. This ensures that the UI does not change abruptly
|
||||||
|
* in front of the user, even if an app launched and user pressed back button to return to the
|
||||||
|
* all-apps UI again.
|
||||||
|
* 2) Prefetch high-res icons for predicted apps. This ensures that we have the icons in memory
|
||||||
|
* even if all-apps is not opened as they are shown in search UI as well
|
||||||
|
* 3) Load instant app if it is not already in memory. As predictions are persisted on disk,
|
||||||
|
* instant app will not be in memory when launcher starts.
|
||||||
|
* 4) Maintains the current active client id (for the predictions) and all updates are performed on
|
||||||
|
* that client id.
|
||||||
|
*/
|
||||||
|
public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInfoUpdateReceiver,
|
||||||
|
OnSharedPreferenceChangeListener, OnIDPChangeListener, OnUpdateListener {
|
||||||
|
|
||||||
|
public static final String KEY_APP_SUGGESTION = "pref_show_predictions";
|
||||||
|
|
||||||
|
// TODO (b/129421797): Update the client constants
|
||||||
|
public enum Client {
|
||||||
|
HOME("GEL"),
|
||||||
|
OVERVIEW("OVERVIEW_GEL");
|
||||||
|
|
||||||
|
public final String id;
|
||||||
|
|
||||||
|
Client(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final MainThreadInitializedObject<PredictionUiStateManager> INSTANCE =
|
||||||
|
new MainThreadInitializedObject<>(PredictionUiStateManager::new);
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private final SharedPreferences mMainPrefs;
|
||||||
|
|
||||||
|
private final DynamicItemCache mDynamicItemCache;
|
||||||
|
private final List[] mPredictionServicePredictions;
|
||||||
|
|
||||||
|
private int mMaxIconsPerRow;
|
||||||
|
private Client mActiveClient;
|
||||||
|
|
||||||
|
private AllAppsContainerView mAppsView;
|
||||||
|
|
||||||
|
private PredictionState mPendingState;
|
||||||
|
private PredictionState mCurrentState;
|
||||||
|
|
||||||
|
private PredictionUiStateManager(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
mMainPrefs = Utilities.getPrefs(context);
|
||||||
|
|
||||||
|
mDynamicItemCache = new DynamicItemCache(context, this::onAppsUpdated);
|
||||||
|
|
||||||
|
mActiveClient = Client.HOME;
|
||||||
|
|
||||||
|
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
|
||||||
|
mMaxIconsPerRow = idp.numColumns;
|
||||||
|
|
||||||
|
idp.addOnChangeListener(this);
|
||||||
|
mPredictionServicePredictions = new List[Client.values().length];
|
||||||
|
for (int i = 0; i < mPredictionServicePredictions.length; i++) {
|
||||||
|
mPredictionServicePredictions[i] = Collections.emptyList();
|
||||||
|
}
|
||||||
|
// Listens for enable/disable signal, and predictions if using AiAi is disabled.
|
||||||
|
mMainPrefs.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
// Call this last
|
||||||
|
mCurrentState = parseLastState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
|
||||||
|
mMaxIconsPerRow = profile.numColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client getClient() {
|
||||||
|
return mActiveClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchClient(Client client) {
|
||||||
|
if (client == mActiveClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mActiveClient = client;
|
||||||
|
dispatchOnChange(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetAppsView(AllAppsContainerView appsView) {
|
||||||
|
if (mAppsView != null) {
|
||||||
|
mAppsView.getAppsStore().removeUpdateListener(this);
|
||||||
|
}
|
||||||
|
mAppsView = appsView;
|
||||||
|
if (mAppsView != null) {
|
||||||
|
mAppsView.getAppsStore().addUpdateListener(this);
|
||||||
|
}
|
||||||
|
if (mPendingState != null) {
|
||||||
|
applyState(mPendingState);
|
||||||
|
mPendingState = null;
|
||||||
|
} else {
|
||||||
|
applyState(mCurrentState);
|
||||||
|
}
|
||||||
|
updateDependencies(mCurrentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reapplyItemInfo(ItemInfoWithIcon info) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGlobalLayout() {
|
||||||
|
if (mAppsView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mPendingState != null && canApplyPredictions(mPendingState)) {
|
||||||
|
applyState(mPendingState);
|
||||||
|
mPendingState = null;
|
||||||
|
}
|
||||||
|
if (mPendingState == null) {
|
||||||
|
mAppsView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleApplyPredictedApps(PredictionState state) {
|
||||||
|
boolean registerListener = mPendingState == null;
|
||||||
|
mPendingState = state;
|
||||||
|
if (registerListener) {
|
||||||
|
// OnGlobalLayoutListener is called whenever a view in the view tree changes
|
||||||
|
// visibility. Add a listener and wait until appsView is invisible again.
|
||||||
|
mAppsView.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||||
|
if (KEY_APP_SUGGESTION.equals(key)) {
|
||||||
|
dispatchOnChange(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyState(PredictionState state) {
|
||||||
|
boolean wasEnabled = mCurrentState.isEnabled;
|
||||||
|
mCurrentState = state;
|
||||||
|
if (mAppsView != null) {
|
||||||
|
mAppsView.getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
|
||||||
|
.setPredictedApps(mCurrentState.isEnabled, mCurrentState.apps);
|
||||||
|
|
||||||
|
if (wasEnabled != mCurrentState.isEnabled) {
|
||||||
|
// Reapply state as the State UI might have changed.
|
||||||
|
Launcher.getLauncher(mAppsView.getContext()).getStateManager().reapplyState(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppPredictor.Callback appPredictorCallback(Client client) {
|
||||||
|
return targets -> {
|
||||||
|
mPredictionServicePredictions[client.ordinal()] = targets;
|
||||||
|
dispatchOnChange(true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dispatchOnChange(boolean changed) {
|
||||||
|
PredictionState newState = changed ? parseLastState() :
|
||||||
|
(mPendingState == null ? mCurrentState : mPendingState);
|
||||||
|
if (changed && mAppsView != null && !canApplyPredictions(newState)) {
|
||||||
|
scheduleApplyPredictedApps(newState);
|
||||||
|
} else {
|
||||||
|
applyState(newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PredictionState parseLastState() {
|
||||||
|
PredictionState state = new PredictionState();
|
||||||
|
state.isEnabled = mMainPrefs.getBoolean(KEY_APP_SUGGESTION, true);
|
||||||
|
if (!state.isEnabled) {
|
||||||
|
state.apps = Collections.EMPTY_LIST;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.apps = new ArrayList<>();
|
||||||
|
|
||||||
|
List<AppTarget> appTargets = mPredictionServicePredictions[mActiveClient.ordinal()];
|
||||||
|
if (!appTargets.isEmpty()) {
|
||||||
|
for (AppTarget appTarget : appTargets) {
|
||||||
|
ComponentKey key;
|
||||||
|
if (appTarget.getShortcutInfo() != null) {
|
||||||
|
key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
|
||||||
|
} else {
|
||||||
|
key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
|
||||||
|
appTarget.getClassName()), appTarget.getUser());
|
||||||
|
}
|
||||||
|
state.apps.add(new ComponentKeyMapper(mContext, key, mDynamicItemCache));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateDependencies(state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDependencies(PredictionState state) {
|
||||||
|
if (!state.isEnabled || mAppsView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
|
||||||
|
List<String> instantAppsToLoad = new ArrayList<>();
|
||||||
|
List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
|
||||||
|
int total = state.apps.size();
|
||||||
|
for (int i = 0, count = 0; i < total && count < mMaxIconsPerRow; i++) {
|
||||||
|
ComponentKeyMapper mapper = state.apps.get(i);
|
||||||
|
// Update instant apps
|
||||||
|
if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
|
||||||
|
instantAppsToLoad.add(mapper.getPackage());
|
||||||
|
count++;
|
||||||
|
} else if (mapper.getComponentKey() instanceof ShortcutKey) {
|
||||||
|
shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
|
||||||
|
count++;
|
||||||
|
} else {
|
||||||
|
// Reload high res icon
|
||||||
|
AppInfo info = (AppInfo) mapper.getApp(mAppsView.getAppsStore());
|
||||||
|
if (info != null) {
|
||||||
|
if (info.usingLowResIcon()) {
|
||||||
|
// TODO: Update icon cache to support null callbacks.
|
||||||
|
iconCache.updateIconInBackground(this, info);
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mDynamicItemCache.cacheItems(shortcutsToLoad, instantAppsToLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppsUpdated() {
|
||||||
|
dispatchOnChange(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean arePredictionsEnabled() {
|
||||||
|
return mCurrentState.isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canApplyPredictions(PredictionState newState) {
|
||||||
|
if (mAppsView == null) {
|
||||||
|
// If there is no apps view, no need to schedule.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Launcher launcher = Launcher.getLauncher(mAppsView.getContext());
|
||||||
|
PredictionRowView predictionRow = mAppsView.getFloatingHeaderView().
|
||||||
|
findFixedRowByType(PredictionRowView.class);
|
||||||
|
if (!predictionRow.isShown() || predictionRow.getAlpha() == 0 ||
|
||||||
|
launcher.isForceInvisible()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mCurrentState.isEnabled != newState.isEnabled
|
||||||
|
|| mCurrentState.apps.isEmpty() != newState.apps.isEmpty()) {
|
||||||
|
// If the visibility of the prediction row is changing, apply immediately.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
|
||||||
|
// If we are here & mAppsView.isShown() = true, we are probably in all-apps or mid way
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!launcher.isInState(OVERVIEW) && !launcher.isInState(BACKGROUND_APP)) {
|
||||||
|
// Just a fallback as we dont need to apply instantly, if we are not in the swipe-up UI
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instead of checking against 1, we should check against (1 + delta), where delta accounts
|
||||||
|
// for the nav-bar height (as app icon can still be visible under the nav-bar). Checking
|
||||||
|
// against 1, keeps the logic simple :)
|
||||||
|
return launcher.getAllAppsController().getProgress() > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PredictionState getCurrentState() {
|
||||||
|
return mCurrentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PredictionState {
|
||||||
|
|
||||||
|
public boolean isEnabled;
|
||||||
|
public List<ComponentKeyMapper> apps;
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
-7
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.launcher3.uioverrides;
|
package com.android.launcher3.uioverrides;
|
||||||
|
|
||||||
|
import static com.android.launcher3.LauncherState.RECENTS_CLEAR_ALL_BUTTON;
|
||||||
|
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||||
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
|
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
|
||||||
|
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
@@ -22,15 +24,17 @@ import android.annotation.TargetApi;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.FloatProperty;
|
import android.util.FloatProperty;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.LauncherState;
|
import com.android.launcher3.LauncherState;
|
||||||
import com.android.launcher3.LauncherStateManager.AnimationConfig;
|
import com.android.launcher3.LauncherStateManager.AnimationConfig;
|
||||||
import com.android.launcher3.anim.AnimatorSetBuilder;
|
import com.android.launcher3.anim.AnimatorSetBuilder;
|
||||||
|
import com.android.launcher3.anim.PropertySetter;
|
||||||
|
import com.android.quickstep.views.ClearAllButton;
|
||||||
import com.android.quickstep.views.LauncherRecentsView;
|
import com.android.quickstep.views.LauncherRecentsView;
|
||||||
import com.android.quickstep.views.RecentsView;
|
import com.android.quickstep.views.RecentsView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing
|
* State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing
|
||||||
* the basic view properties, this class also manages changes in the task visuals.
|
* the basic view properties, this class also manages changes in the task visuals.
|
||||||
@@ -49,10 +53,8 @@ public final class RecentsViewStateController extends
|
|||||||
if (state.overviewUi) {
|
if (state.overviewUi) {
|
||||||
mRecentsView.updateEmptyMessage();
|
mRecentsView.updateEmptyMessage();
|
||||||
mRecentsView.resetTaskVisuals();
|
mRecentsView.resetTaskVisuals();
|
||||||
mRecentsView.setHintVisibility(1);
|
|
||||||
} else {
|
|
||||||
mRecentsView.setHintVisibility(0);
|
|
||||||
}
|
}
|
||||||
|
setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state.getVisibleElements(mLauncher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -62,7 +64,6 @@ public final class RecentsViewStateController extends
|
|||||||
|
|
||||||
if (!toState.overviewUi) {
|
if (!toState.overviewUi) {
|
||||||
builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
|
builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
|
||||||
mRecentsView.setHintVisibility(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toState.overviewUi) {
|
if (toState.overviewUi) {
|
||||||
@@ -74,8 +75,15 @@ public final class RecentsViewStateController extends
|
|||||||
updateAnim.setDuration(config.duration);
|
updateAnim.setDuration(config.duration);
|
||||||
builder.play(updateAnim);
|
builder.play(updateAnim);
|
||||||
mRecentsView.updateEmptyMessage();
|
mRecentsView.updateEmptyMessage();
|
||||||
builder.addOnFinishRunnable(() -> mRecentsView.setHintVisibility(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAlphas(config.getPropertySetter(builder), toState.getVisibleElements(mLauncher));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAlphas(PropertySetter propertySetter, int visibleElements) {
|
||||||
|
boolean hasClearAllButton = (visibleElements & RECENTS_CLEAR_ALL_BUTTON) != 0;
|
||||||
|
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
|
||||||
|
hasClearAllButton ? 1f : 0f, LINEAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+5
-2
@@ -18,11 +18,10 @@ package com.android.launcher3.uioverrides.states;
|
|||||||
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
|
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
|
||||||
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||||
import com.android.quickstep.RecentsModel;
|
import com.android.quickstep.RecentsModel;
|
||||||
import com.android.quickstep.SysUINavigationMode;
|
|
||||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
|
||||||
import com.android.quickstep.util.LayoutUtils;
|
import com.android.quickstep.util.LayoutUtils;
|
||||||
import com.android.quickstep.views.RecentsView;
|
import com.android.quickstep.views.RecentsView;
|
||||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||||
@@ -75,4 +74,8 @@ public class BackgroundAppState extends OverviewState {
|
|||||||
return new ScaleAndTranslation(scale, 0f, 0f);
|
return new ScaleAndTranslation(scale, 0f, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVisibleElements(Launcher launcher) {
|
||||||
|
return super.getVisibleElements(launcher) & ~RECENTS_CLEAR_ALL_BUTTON;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-5
@@ -17,8 +17,6 @@ package com.android.launcher3.uioverrides.states;
|
|||||||
|
|
||||||
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
|
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
|
||||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
|
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
|
||||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
|
|
||||||
import static com.android.launcher3.logging.LoggerUtils.getTargetStr;
|
|
||||||
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
|
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
|
||||||
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
|
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
|
||||||
|
|
||||||
@@ -27,7 +25,6 @@ import android.view.View;
|
|||||||
|
|
||||||
import com.android.launcher3.AbstractFloatingView;
|
import com.android.launcher3.AbstractFloatingView;
|
||||||
import com.android.launcher3.DeviceProfile;
|
import com.android.launcher3.DeviceProfile;
|
||||||
import com.android.launcher3.InvariantDeviceProfile;
|
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.LauncherState;
|
import com.android.launcher3.LauncherState;
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
@@ -73,6 +70,15 @@ public class OverviewState extends LauncherState {
|
|||||||
return new ScaleAndTranslation(scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor);
|
return new ScaleAndTranslation(scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
|
||||||
|
if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
|
||||||
|
// If the hotseat icons are visible in overview, keep them in their normal position.
|
||||||
|
return super.getWorkspaceScaleAndTranslation(launcher);
|
||||||
|
}
|
||||||
|
return getWorkspaceScaleAndTranslation(launcher);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
|
public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
|
||||||
return new ScaleAndTranslation(1f, 0f, 0f);
|
return new ScaleAndTranslation(1f, 0f, 0f);
|
||||||
@@ -110,9 +116,9 @@ public class OverviewState extends LauncherState {
|
|||||||
@Override
|
@Override
|
||||||
public int getVisibleElements(Launcher launcher) {
|
public int getVisibleElements(Launcher launcher) {
|
||||||
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
|
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
|
||||||
return VERTICAL_SWIPE_INDICATOR;
|
return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
|
||||||
} else {
|
} else {
|
||||||
return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR |
|
return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON |
|
||||||
(launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
|
(launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
|
||||||
? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
|
? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -28,6 +28,7 @@ import android.animation.AnimatorListenerAdapter;
|
|||||||
import android.animation.AnimatorSet;
|
import android.animation.AnimatorSet;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.LauncherState;
|
import com.android.launcher3.LauncherState;
|
||||||
@@ -44,12 +45,14 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
|
|||||||
private static final long PEEK_ANIM_DURATION = 100;
|
private static final long PEEK_ANIM_DURATION = 100;
|
||||||
|
|
||||||
private final MotionPauseDetector mMotionPauseDetector;
|
private final MotionPauseDetector mMotionPauseDetector;
|
||||||
|
private final float mMotionPauseMinDisplacement;
|
||||||
|
|
||||||
private AnimatorSet mPeekAnim;
|
private AnimatorSet mPeekAnim;
|
||||||
|
|
||||||
public FlingAndHoldTouchController(Launcher l) {
|
public FlingAndHoldTouchController(Launcher l) {
|
||||||
super(l, false /* allowDragToOverview */);
|
super(l, false /* allowDragToOverview */);
|
||||||
mMotionPauseDetector = new MotionPauseDetector(l);
|
mMotionPauseDetector = new MotionPauseDetector(l);
|
||||||
|
mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -98,7 +101,8 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onDrag(float displacement, MotionEvent event) {
|
public boolean onDrag(float displacement, MotionEvent event) {
|
||||||
mMotionPauseDetector.addPosition(displacement, 0, event.getEventTime());
|
mMotionPauseDetector.setDisallowPause(-displacement < mMotionPauseMinDisplacement);
|
||||||
|
mMotionPauseDetector.addPosition(displacement, event.getEventTime());
|
||||||
return super.onDrag(displacement, event);
|
return super.onDrag(displacement, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +115,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
|
|||||||
|
|
||||||
AnimatorSetBuilder builder = new AnimatorSetBuilder();
|
AnimatorSetBuilder builder = new AnimatorSetBuilder();
|
||||||
builder.setInterpolator(AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
|
builder.setInterpolator(AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
|
||||||
|
builder.setInterpolator(AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE, OVERSHOOT_1_2);
|
||||||
AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation(
|
AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation(
|
||||||
NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION);
|
NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION);
|
||||||
overviewAnim.addListener(new AnimatorListenerAdapter() {
|
overviewAnim.addListener(new AnimatorListenerAdapter() {
|
||||||
|
|||||||
+3
-3
@@ -17,8 +17,6 @@ package com.android.launcher3.uioverrides.touchcontrollers;
|
|||||||
|
|
||||||
import static android.view.View.TRANSLATION_X;
|
import static android.view.View.TRANSLATION_X;
|
||||||
|
|
||||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
|
|
||||||
import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
|
|
||||||
import static com.android.launcher3.LauncherState.ALL_APPS;
|
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||||
import static com.android.launcher3.LauncherState.NORMAL;
|
import static com.android.launcher3.LauncherState.NORMAL;
|
||||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||||
@@ -44,9 +42,9 @@ import com.android.launcher3.anim.AnimationSuccessListener;
|
|||||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||||
import com.android.launcher3.anim.AnimatorSetBuilder;
|
import com.android.launcher3.anim.AnimatorSetBuilder;
|
||||||
import com.android.launcher3.anim.Interpolators;
|
import com.android.launcher3.anim.Interpolators;
|
||||||
|
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||||
import com.android.launcher3.touch.SwipeDetector;
|
import com.android.launcher3.touch.SwipeDetector;
|
||||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Command;
|
|
||||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
|
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
|
||||||
import com.android.launcher3.util.TouchController;
|
import com.android.launcher3.util.TouchController;
|
||||||
import com.android.quickstep.views.RecentsView;
|
import com.android.quickstep.views.RecentsView;
|
||||||
@@ -212,11 +210,13 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect
|
|||||||
private void onSwipeInteractionCompleted(LauncherState targetState) {
|
private void onSwipeInteractionCompleted(LauncherState targetState) {
|
||||||
clearState();
|
clearState();
|
||||||
mLauncher.getStateManager().goToState(targetState, false /* animated */);
|
mLauncher.getStateManager().goToState(targetState, false /* animated */);
|
||||||
|
AccessibilityManagerCompat.sendStateEventToTest(mLauncher, targetState.ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logStateChange(int startContainerType, int logAction) {
|
private void logStateChange(int startContainerType, int logAction) {
|
||||||
mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
|
mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
|
||||||
LauncherLogProto.Action.Direction.UP,
|
LauncherLogProto.Action.Direction.UP,
|
||||||
|
mSwipeDetector.getDownX(), mSwipeDetector.getDownY(),
|
||||||
LauncherLogProto.ContainerType.NAVBAR,
|
LauncherLogProto.ContainerType.NAVBAR,
|
||||||
startContainerType,
|
startContainerType,
|
||||||
mEndState.containerType,
|
mEndState.containerType,
|
||||||
|
|||||||
+2
-1
@@ -75,7 +75,8 @@ public final class PortraitOverviewStateTouchHelper {
|
|||||||
* @return the animation
|
* @return the animation
|
||||||
*/
|
*/
|
||||||
PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
|
PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
|
||||||
TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getNextPage());
|
mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
|
||||||
|
TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getCurrentPage());
|
||||||
if (taskView == null) {
|
if (taskView == null) {
|
||||||
throw new IllegalStateException("There is no task view to animate to.");
|
throw new IllegalStateException("There is no task view to animate to.");
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-21
@@ -21,10 +21,11 @@ import static android.view.MotionEvent.ACTION_DOWN;
|
|||||||
import static android.view.MotionEvent.ACTION_MOVE;
|
import static android.view.MotionEvent.ACTION_MOVE;
|
||||||
import static android.view.MotionEvent.ACTION_POINTER_UP;
|
import static android.view.MotionEvent.ACTION_POINTER_UP;
|
||||||
import static android.view.MotionEvent.ACTION_UP;
|
import static android.view.MotionEvent.ACTION_UP;
|
||||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE;
|
|
||||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE_NOOP;
|
|
||||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPLEFT;
|
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPLEFT;
|
||||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPRIGHT;
|
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPRIGHT;
|
||||||
|
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE;
|
||||||
|
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE_NOOP;
|
||||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR;
|
import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR;
|
||||||
|
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
@@ -35,12 +36,15 @@ import android.os.Bundle;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import com.android.launcher3.BaseDraggingActivity;
|
||||||
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.anim.Interpolators;
|
import com.android.launcher3.anim.Interpolators;
|
||||||
import com.android.launcher3.logging.UserEventDispatcher;
|
import com.android.launcher3.logging.UserEventDispatcher;
|
||||||
import com.android.quickstep.util.MotionPauseDetector;
|
|
||||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||||
import com.android.launcher3.R;
|
import com.android.systemui.shared.system.InputMonitorCompat;
|
||||||
import com.android.systemui.shared.system.NavigationBarCompat;
|
import com.android.systemui.shared.system.NavigationBarCompat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,27 +80,32 @@ public class AssistantTouchConsumer implements InputConsumer {
|
|||||||
private float mLastProgress;
|
private float mLastProgress;
|
||||||
private int mState;
|
private int mState;
|
||||||
private int mDirection;
|
private int mDirection;
|
||||||
|
private ActivityControlHelper mActivityControlHelper;
|
||||||
|
|
||||||
private final float mDistThreshold;
|
private final float mDistThreshold;
|
||||||
private final long mTimeThreshold;
|
private final long mTimeThreshold;
|
||||||
private final int mAngleThreshold;
|
private final int mAngleThreshold;
|
||||||
private final float mSlop;
|
private final float mSlop;
|
||||||
private final MotionPauseDetector mMotionPauseDetector;
|
|
||||||
private final ISystemUiProxy mSysUiProxy;
|
private final ISystemUiProxy mSysUiProxy;
|
||||||
private final InputConsumer mConsumerDelegate;
|
private final InputConsumer mConsumerDelegate;
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
|
||||||
|
private final InputMonitorCompat mInputMonitorCompat;
|
||||||
|
|
||||||
|
|
||||||
public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
|
public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
|
||||||
InputConsumer delegate) {
|
InputConsumer delegate, InputMonitorCompat inputMonitorCompat,
|
||||||
|
ActivityControlHelper activityControlHelper) {
|
||||||
final Resources res = context.getResources();
|
final Resources res = context.getResources();
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mSysUiProxy = systemUiProxy;
|
mSysUiProxy = systemUiProxy;
|
||||||
mConsumerDelegate = delegate;
|
mConsumerDelegate = delegate;
|
||||||
mMotionPauseDetector = new MotionPauseDetector(context);
|
|
||||||
mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
|
mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
|
||||||
mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
|
mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
|
||||||
mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
|
mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
|
||||||
mSlop = NavigationBarCompat.getQuickScrubTouchSlopPx();
|
mSlop = NavigationBarCompat.getQuickStepDragSlopPx();
|
||||||
|
mInputMonitorCompat = inputMonitorCompat;
|
||||||
|
mActivityControlHelper = activityControlHelper;
|
||||||
mState = STATE_INACTIVE;
|
mState = STATE_INACTIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,8 +115,18 @@ public class AssistantTouchConsumer implements InputConsumer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean useSharedSwipeState() {
|
||||||
return mState != STATE_INACTIVE;
|
if (mConsumerDelegate != null) {
|
||||||
|
return mConsumerDelegate.useSharedSwipeState();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConsumerAboutToBeSwitched() {
|
||||||
|
if (mConsumerDelegate != null) {
|
||||||
|
mConsumerDelegate.onConsumerAboutToBeSwitched();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -120,14 +139,6 @@ public class AssistantTouchConsumer implements InputConsumer {
|
|||||||
mDownPos.set(ev.getX(), ev.getY());
|
mDownPos.set(ev.getX(), ev.getY());
|
||||||
mLastPos.set(mDownPos);
|
mLastPos.set(mDownPos);
|
||||||
mTimeFraction = 0;
|
mTimeFraction = 0;
|
||||||
|
|
||||||
// Detect when the gesture decelerates to start the assistant
|
|
||||||
mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
|
|
||||||
if (isPaused && mState == STATE_ASSISTANT_ACTIVE) {
|
|
||||||
mTimeFraction = 1;
|
|
||||||
updateAssistantProgress();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ACTION_POINTER_UP: {
|
case ACTION_POINTER_UP: {
|
||||||
@@ -156,6 +167,10 @@ public class AssistantTouchConsumer implements InputConsumer {
|
|||||||
if (!mPassedSlop) {
|
if (!mPassedSlop) {
|
||||||
// Normal gesture, ensure we pass the slop before we start tracking the gesture
|
// Normal gesture, ensure we pass the slop before we start tracking the gesture
|
||||||
if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) > mSlop) {
|
if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) > mSlop) {
|
||||||
|
|
||||||
|
// Cancel touches to other windows (intercept)
|
||||||
|
mInputMonitorCompat.pilferPointers();
|
||||||
|
|
||||||
mPassedSlop = true;
|
mPassedSlop = true;
|
||||||
mStartDragPos.set(mLastPos.x, mLastPos.y);
|
mStartDragPos.set(mLastPos.x, mLastPos.y);
|
||||||
mDragTime = SystemClock.uptimeMillis();
|
mDragTime = SystemClock.uptimeMillis();
|
||||||
@@ -165,7 +180,8 @@ public class AssistantTouchConsumer implements InputConsumer {
|
|||||||
Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x));
|
Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x));
|
||||||
mDirection = angle > 90 ? UPLEFT : UPRIGHT;
|
mDirection = angle > 90 ? UPLEFT : UPRIGHT;
|
||||||
angle = angle > 90 ? 180 - angle : angle;
|
angle = angle > 90 ? 180 - angle : angle;
|
||||||
if (angle > mAngleThreshold) {
|
|
||||||
|
if (angle > mAngleThreshold && angle < 90) {
|
||||||
mState = STATE_ASSISTANT_ACTIVE;
|
mState = STATE_ASSISTANT_ACTIVE;
|
||||||
|
|
||||||
if (mConsumerDelegate != null) {
|
if (mConsumerDelegate != null) {
|
||||||
@@ -183,7 +199,6 @@ public class AssistantTouchConsumer implements InputConsumer {
|
|||||||
// Movement
|
// Movement
|
||||||
mDistance = (float) Math.hypot(mLastPos.x - mStartDragPos.x,
|
mDistance = (float) Math.hypot(mLastPos.x - mStartDragPos.x,
|
||||||
mLastPos.y - mStartDragPos.y);
|
mLastPos.y - mStartDragPos.y);
|
||||||
mMotionPauseDetector.addPosition(mDistance, 0, ev.getEventTime());
|
|
||||||
if (mDistance >= 0) {
|
if (mDistance >= 0) {
|
||||||
final long diff = SystemClock.uptimeMillis() - mDragTime;
|
final long diff = SystemClock.uptimeMillis() - mDragTime;
|
||||||
mTimeFraction = Math.min(diff * 1f / mTimeThreshold, 1);
|
mTimeFraction = Math.min(diff * 1f / mTimeThreshold, 1);
|
||||||
@@ -212,7 +227,8 @@ public class AssistantTouchConsumer implements InputConsumer {
|
|||||||
animator.setInterpolator(Interpolators.DEACCEL_2);
|
animator.setInterpolator(Interpolators.DEACCEL_2);
|
||||||
animator.start();
|
animator.start();
|
||||||
}
|
}
|
||||||
mMotionPauseDetector.clear();
|
mPassedSlop = false;
|
||||||
|
mState = STATE_INACTIVE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +248,14 @@ public class AssistantTouchConsumer implements InputConsumer {
|
|||||||
SWIPE, mDirection, NAVBAR);
|
SWIPE, mDirection, NAVBAR);
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
|
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
|
||||||
|
|
||||||
|
BaseDraggingActivity launcherActivity = mActivityControlHelper.getCreatedActivity();
|
||||||
|
if (launcherActivity != null) {
|
||||||
|
launcherActivity.getRootView().
|
||||||
|
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
|
||||||
|
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
|
||||||
|
}
|
||||||
|
|
||||||
mSysUiProxy.startAssistant(args);
|
mSysUiProxy.startAssistant(args);
|
||||||
mLaunchedAssistant = true;
|
mLaunchedAssistant = true;
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-4
@@ -17,7 +17,9 @@ package com.android.quickstep;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.graphics.PointF;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A dummy input consumer used when the device is still locked, e.g. from secure camera.
|
* A dummy input consumer used when the device is still locked, e.g. from secure camera.
|
||||||
@@ -25,9 +27,13 @@ import android.view.MotionEvent;
|
|||||||
public class DeviceLockedInputConsumer implements InputConsumer {
|
public class DeviceLockedInputConsumer implements InputConsumer {
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
private final float mTouchSlopSquared;
|
||||||
|
private final PointF mTouchDown = new PointF();
|
||||||
|
|
||||||
public DeviceLockedInputConsumer(Context context) {
|
public DeviceLockedInputConsumer(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
float touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||||
|
mTouchSlopSquared = touchSlop * touchSlop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -37,11 +43,19 @@ public class DeviceLockedInputConsumer implements InputConsumer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMotionEvent(MotionEvent ev) {
|
public void onMotionEvent(MotionEvent ev) {
|
||||||
// For now, just start the home intent so user is prompted to unlock the device.
|
float x = ev.getX();
|
||||||
|
float y = ev.getY();
|
||||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
mContext.startActivity(new Intent(Intent.ACTION_MAIN)
|
mTouchDown.set(x, y);
|
||||||
.addCategory(Intent.CATEGORY_HOME)
|
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
float xSquared = (x - mTouchDown.x) * (x - mTouchDown.x);
|
||||||
|
float ySquared = (y - mTouchDown.y) * (y - mTouchDown.y);
|
||||||
|
if (xSquared + ySquared > mTouchSlopSquared) {
|
||||||
|
// For now, just start the home intent so user is prompted to unlock the device.
|
||||||
|
mContext.startActivity(new Intent(Intent.ACTION_MAIN)
|
||||||
|
.addCategory(Intent.CATEGORY_HOME)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-5
@@ -26,11 +26,13 @@ import android.content.Context;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.launcher3.DeviceProfile;
|
import com.android.launcher3.DeviceProfile;
|
||||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||||
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
|
|
||||||
import com.android.quickstep.util.LayoutUtils;
|
import com.android.quickstep.util.LayoutUtils;
|
||||||
import com.android.quickstep.util.RemoteAnimationTargetSet;
|
import com.android.quickstep.util.RemoteAnimationTargetSet;
|
||||||
import com.android.quickstep.views.RecentsView;
|
import com.android.quickstep.views.RecentsView;
|
||||||
@@ -39,9 +41,6 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
|||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ActivityControlHelper} for recents when the default launcher is different than the
|
* {@link ActivityControlHelper} for recents when the default launcher is different than the
|
||||||
* currently running one and apps should interact with the {@link RecentsActivity} as opposed
|
* currently running one and apps should interact with the {@link RecentsActivity} as opposed
|
||||||
@@ -72,7 +71,9 @@ public final class FallbackActivityControllerHelper implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwipeUpComplete(RecentsActivity activity) {
|
public void onSwipeUpComplete(RecentsActivity activity) {
|
||||||
// TODO:
|
RecentsView recentsView = activity.getOverviewPanel();
|
||||||
|
recentsView.getClearAllButton().setVisibilityAlpha(1);
|
||||||
|
recentsView.setDisallowScrollToClearAll(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -121,6 +122,8 @@ public final class FallbackActivityControllerHelper implements
|
|||||||
|
|
||||||
RecentsView rv = activity.getOverviewPanel();
|
RecentsView rv = activity.getOverviewPanel();
|
||||||
rv.setContentAlpha(0);
|
rv.setContentAlpha(0);
|
||||||
|
rv.getClearAllButton().setVisibilityAlpha(0);
|
||||||
|
rv.setDisallowScrollToClearAll(true);
|
||||||
|
|
||||||
return new AnimationFactory() {
|
return new AnimationFactory() {
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public interface InputConsumer {
|
|||||||
|
|
||||||
int getType();
|
int getType();
|
||||||
|
|
||||||
default boolean isActive() {
|
default boolean useSharedSwipeState() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-32
@@ -38,10 +38,14 @@ import android.view.MotionEvent;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
|
||||||
import com.android.launcher3.DeviceProfile;
|
import com.android.launcher3.DeviceProfile;
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.LauncherAppState;
|
import com.android.launcher3.LauncherAppState;
|
||||||
import com.android.launcher3.LauncherInitListener;
|
import com.android.launcher3.LauncherInitListenerEx;
|
||||||
import com.android.launcher3.LauncherState;
|
import com.android.launcher3.LauncherState;
|
||||||
import com.android.launcher3.allapps.DiscoveryBounce;
|
import com.android.launcher3.allapps.DiscoveryBounce;
|
||||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||||
@@ -59,10 +63,6 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
|||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ActivityControlHelper} for the in-launcher recents.
|
* {@link ActivityControlHelper} for the in-launcher recents.
|
||||||
*/
|
*/
|
||||||
@@ -163,21 +163,16 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
|
|||||||
}
|
}
|
||||||
activity.getStateManager().setRestState(resetState);
|
activity.getStateManager().setRestState(resetState);
|
||||||
|
|
||||||
final LauncherState fromState;
|
final LauncherState fromState = animateActivity ? BACKGROUND_APP : OVERVIEW;
|
||||||
if (!activityVisible) {
|
activity.getStateManager().goToState(fromState, false);
|
||||||
// Since the launcher is not visible, we can safely reset the scroll position.
|
// Since all apps is not visible, we can safely reset the scroll position.
|
||||||
// This ensures then the next swipe up to all-apps starts from scroll 0.
|
// This ensures then the next swipe up to all-apps starts from scroll 0.
|
||||||
activity.getAppsView().reset(false /* animate */);
|
activity.getAppsView().reset(false /* animate */);
|
||||||
fromState = animateActivity ? BACKGROUND_APP : OVERVIEW;
|
|
||||||
activity.getStateManager().goToState(fromState, false);
|
|
||||||
|
|
||||||
// Optimization, hide the all apps view to prevent layout while initializing
|
// Optimization, hide the all apps view to prevent layout while initializing
|
||||||
activity.getAppsView().getContentView().setVisibility(View.GONE);
|
activity.getAppsView().getContentView().setVisibility(View.GONE);
|
||||||
|
|
||||||
AccessibilityManagerCompat.sendStateEventToTest(activity, fromState.ordinal);
|
AccessibilityManagerCompat.sendStateEventToTest(activity, fromState.ordinal);
|
||||||
} else {
|
|
||||||
fromState = startState;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AnimationFactory() {
|
return new AnimationFactory() {
|
||||||
private Animator mShelfAnim;
|
private Animator mShelfAnim;
|
||||||
@@ -185,8 +180,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createActivityController(long transitionLength) {
|
public void createActivityController(long transitionLength) {
|
||||||
createActivityControllerInternal(activity, activityVisible, fromState,
|
createActivityControllerInternal(activity, fromState, transitionLength, callback);
|
||||||
transitionLength, callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -230,18 +224,9 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createActivityControllerInternal(Launcher activity, boolean wasVisible,
|
private void createActivityControllerInternal(Launcher activity, LauncherState fromState,
|
||||||
LauncherState fromState, long transitionLength,
|
long transitionLength, Consumer<AnimatorPlaybackController> callback) {
|
||||||
Consumer<AnimatorPlaybackController> callback) {
|
|
||||||
LauncherState endState = OVERVIEW;
|
LauncherState endState = OVERVIEW;
|
||||||
if (wasVisible && fromState != BACKGROUND_APP) {
|
|
||||||
// If a translucent app was launched fom launcher, animate launcher states.
|
|
||||||
DeviceProfile dp = activity.getDeviceProfile();
|
|
||||||
long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
|
|
||||||
callback.accept(activity.getStateManager()
|
|
||||||
.createAnimationToNewWorkspace(fromState, endState, accuracy));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fromState == endState) {
|
if (fromState == endState) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -319,7 +304,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
|
|||||||
@Override
|
@Override
|
||||||
public ActivityInitListener createActivityInitListener(
|
public ActivityInitListener createActivityInitListener(
|
||||||
BiPredicate<Launcher, Boolean> onInitListener) {
|
BiPredicate<Launcher, Boolean> onInitListener) {
|
||||||
return new LauncherInitListener(onInitListener);
|
return new LauncherInitListenerEx(onInitListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
+23
-14
@@ -21,9 +21,9 @@ import static android.view.MotionEvent.ACTION_MOVE;
|
|||||||
import static android.view.MotionEvent.ACTION_POINTER_UP;
|
import static android.view.MotionEvent.ACTION_POINTER_UP;
|
||||||
import static android.view.MotionEvent.ACTION_UP;
|
import static android.view.MotionEvent.ACTION_UP;
|
||||||
import static android.view.MotionEvent.INVALID_POINTER_ID;
|
import static android.view.MotionEvent.INVALID_POINTER_ID;
|
||||||
|
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
|
||||||
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
|
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
|
||||||
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
|
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
|
||||||
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
|
|
||||||
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
|
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
|
||||||
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
|
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
|
||||||
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
|
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
|
||||||
@@ -45,6 +45,9 @@ import android.view.VelocityTracker;
|
|||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
|
||||||
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.util.Preconditions;
|
import com.android.launcher3.util.Preconditions;
|
||||||
import com.android.launcher3.util.RaceConditionTracker;
|
import com.android.launcher3.util.RaceConditionTracker;
|
||||||
import com.android.launcher3.util.TraceHelper;
|
import com.android.launcher3.util.TraceHelper;
|
||||||
@@ -62,8 +65,6 @@ import com.android.systemui.shared.system.WindowManagerWrapper;
|
|||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Input consumer for handling events originating from an activity other than Launcher
|
* Input consumer for handling events originating from an activity other than Launcher
|
||||||
*/
|
*/
|
||||||
@@ -90,6 +91,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
|||||||
|
|
||||||
private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
|
private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
|
||||||
private final MotionPauseDetector mMotionPauseDetector;
|
private final MotionPauseDetector mMotionPauseDetector;
|
||||||
|
private final float mMotionPauseMinDisplacement;
|
||||||
private VelocityTracker mVelocityTracker;
|
private VelocityTracker mVelocityTracker;
|
||||||
|
|
||||||
private WindowTransformSwipeHandler mInteractionHandler;
|
private WindowTransformSwipeHandler mInteractionHandler;
|
||||||
@@ -107,8 +109,9 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
|||||||
// Slop used to determine when we say that the gesture has started.
|
// Slop used to determine when we say that the gesture has started.
|
||||||
private boolean mPassedTouchSlop;
|
private boolean mPassedTouchSlop;
|
||||||
|
|
||||||
// TODO: Start displacement should have both x and y
|
// Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
|
||||||
private float mStartDisplacement;
|
private float mStartDisplacement;
|
||||||
|
private float mStartDisplacementX;
|
||||||
|
|
||||||
private Handler mMainThreadHandler;
|
private Handler mMainThreadHandler;
|
||||||
private Runnable mCancelRecentsAnimationRunnable = () -> {
|
private Runnable mCancelRecentsAnimationRunnable = () -> {
|
||||||
@@ -131,6 +134,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
|||||||
mMode = SysUINavigationMode.getMode(base);
|
mMode = SysUINavigationMode.getMode(base);
|
||||||
|
|
||||||
mMotionPauseDetector = new MotionPauseDetector(base);
|
mMotionPauseDetector = new MotionPauseDetector(base);
|
||||||
|
mMotionPauseMinDisplacement = base.getResources().getDimension(
|
||||||
|
R.dimen.motion_pause_detector_min_displacement_from_app);
|
||||||
mOnCompleteCallback = onCompleteCallback;
|
mOnCompleteCallback = onCompleteCallback;
|
||||||
mVelocityTracker = VelocityTracker.obtain();
|
mVelocityTracker = VelocityTracker.obtain();
|
||||||
mInputMonitorCompat = inputMonitorCompat;
|
mInputMonitorCompat = inputMonitorCompat;
|
||||||
@@ -149,6 +154,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
|||||||
|
|
||||||
mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
|
mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
|
||||||
mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
|
mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
|
||||||
|
|
||||||
mPassedTouchSlop = mPassedDragSlop = continuingPreviousGesture;
|
mPassedTouchSlop = mPassedDragSlop = continuingPreviousGesture;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +224,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
|||||||
}
|
}
|
||||||
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
|
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
|
||||||
float displacement = getDisplacement(ev);
|
float displacement = getDisplacement(ev);
|
||||||
|
float displacementX = mLastPos.x - mDownPos.x;
|
||||||
|
|
||||||
if (!mPassedDragSlop) {
|
if (!mPassedDragSlop) {
|
||||||
if (!mIsDeferredDownTarget) {
|
if (!mIsDeferredDownTarget) {
|
||||||
@@ -226,13 +233,13 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
|||||||
if (Math.abs(displacement) > mDragSlop) {
|
if (Math.abs(displacement) > mDragSlop) {
|
||||||
mPassedDragSlop = true;
|
mPassedDragSlop = true;
|
||||||
mStartDisplacement = displacement;
|
mStartDisplacement = displacement;
|
||||||
|
mStartDisplacementX = displacementX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mPassedTouchSlop) {
|
if (!mPassedTouchSlop) {
|
||||||
if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) >=
|
if (Math.hypot(displacementX, mLastPos.y - mDownPos.y) >= mTouchSlop) {
|
||||||
mTouchSlop) {
|
|
||||||
mPassedTouchSlop = true;
|
mPassedTouchSlop = true;
|
||||||
|
|
||||||
if (mIsDeferredDownTarget) {
|
if (mIsDeferredDownTarget) {
|
||||||
@@ -243,6 +250,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
|||||||
if (!mPassedDragSlop) {
|
if (!mPassedDragSlop) {
|
||||||
mPassedDragSlop = true;
|
mPassedDragSlop = true;
|
||||||
mStartDisplacement = displacement;
|
mStartDisplacement = displacement;
|
||||||
|
mStartDisplacementX = displacementX;
|
||||||
}
|
}
|
||||||
notifyGestureStarted();
|
notifyGestureStarted();
|
||||||
}
|
}
|
||||||
@@ -253,12 +261,12 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
|||||||
mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
|
mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
|
||||||
|
|
||||||
if (mMode == Mode.NO_BUTTON) {
|
if (mMode == Mode.NO_BUTTON) {
|
||||||
boolean isLandscape = isNavBarOnLeft() || isNavBarOnRight();
|
float horizontalDist = Math.abs(displacementX - mStartDisplacementX);
|
||||||
float orthogonalDisplacement = !isLandscape
|
float upDist = -(displacement - mStartDisplacement);
|
||||||
? ev.getX() - mDownPos.x
|
boolean isLikelyToStartNewTask = horizontalDist > upDist;
|
||||||
: ev.getY() - mDownPos.y;
|
mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
|
||||||
mMotionPauseDetector.addPosition(displacement, orthogonalDisplacement,
|
|| isLikelyToStartNewTask);
|
||||||
ev.getEventTime());
|
mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -346,7 +354,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
|||||||
: velocityY;
|
: velocityY;
|
||||||
|
|
||||||
mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
|
mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
|
||||||
mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY));
|
mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY),
|
||||||
|
mDownPos);
|
||||||
} else {
|
} else {
|
||||||
// Since we start touch tracking on DOWN, we may reach this state without actually
|
// Since we start touch tracking on DOWN, we may reach this state without actually
|
||||||
// starting the gesture. In that case, just cleanup immediately.
|
// starting the gesture. In that case, just cleanup immediately.
|
||||||
@@ -412,7 +421,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean useSharedSwipeState() {
|
||||||
return mInteractionHandler != null;
|
return mInteractionHandler != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class TaskOverlayFactory implements ResourceBasedOverride {
|
public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||||
|
|
||||||
|
public static final String AIAI_PACKAGE = "com.google.android.as";
|
||||||
|
|
||||||
/** Note that these will be shown in order from top to bottom, if available for the task. */
|
/** Note that these will be shown in order from top to bottom, if available for the task. */
|
||||||
private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{
|
private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{
|
||||||
new TaskSystemShortcut.AppInfo(),
|
new TaskSystemShortcut.AppInfo(),
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import com.android.launcher3.ItemInfo;
|
|||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.LauncherState;
|
import com.android.launcher3.LauncherState;
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.ShortcutInfo;
|
import com.android.launcher3.WorkspaceItemInfo;
|
||||||
import com.android.launcher3.popup.SystemShortcut;
|
import com.android.launcher3.popup.SystemShortcut;
|
||||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||||
import com.android.launcher3.util.InstantAppResolver;
|
import com.android.launcher3.util.InstantAppResolver;
|
||||||
@@ -86,7 +86,7 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
|
|||||||
public View.OnClickListener getOnClickListener(BaseDraggingActivity activity, TaskView view) {
|
public View.OnClickListener getOnClickListener(BaseDraggingActivity activity, TaskView view) {
|
||||||
Task task = view.getTask();
|
Task task = view.getTask();
|
||||||
|
|
||||||
ShortcutInfo dummyInfo = new ShortcutInfo();
|
WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
|
||||||
dummyInfo.intent = new Intent();
|
dummyInfo.intent = new Intent();
|
||||||
ComponentName component = task.getTopComponent();
|
ComponentName component = task.getTopComponent();
|
||||||
dummyInfo.intent.setComponent(component);
|
dummyInfo.intent.setComponent(component);
|
||||||
|
|||||||
+9
-4
@@ -87,7 +87,7 @@ public class TouchInteractionService extends Service implements
|
|||||||
new LooperExecutor(UiThreadHelper.getBackgroundLooper());
|
new LooperExecutor(UiThreadHelper.getBackgroundLooper());
|
||||||
|
|
||||||
private static final String NAVBAR_VERTICAL_SIZE = "navigation_bar_frame_height";
|
private static final String NAVBAR_VERTICAL_SIZE = "navigation_bar_frame_height";
|
||||||
private static final String NAVBAR_HORIZONTAL_SIZE = "navigation_bar_frame_width";
|
private static final String NAVBAR_HORIZONTAL_SIZE = "navigation_bar_width";
|
||||||
|
|
||||||
public static final EventLogArray TOUCH_INTERACTION_LOG =
|
public static final EventLogArray TOUCH_INTERACTION_LOG =
|
||||||
new EventLogArray("touch_interaction_log", 40);
|
new EventLogArray("touch_interaction_log", 40);
|
||||||
@@ -422,7 +422,7 @@ public class TouchInteractionService extends Service implements
|
|||||||
if (event.getAction() == ACTION_DOWN) {
|
if (event.getAction() == ACTION_DOWN) {
|
||||||
if (isInValidSystemUiState()
|
if (isInValidSystemUiState()
|
||||||
&& mSwipeTouchRegion.contains(event.getX(), event.getY())) {
|
&& mSwipeTouchRegion.contains(event.getX(), event.getY())) {
|
||||||
boolean useSharedState = mConsumer.isActive();
|
boolean useSharedState = mConsumer.useSharedSwipeState();
|
||||||
mConsumer.onConsumerAboutToBeSwitched();
|
mConsumer.onConsumerAboutToBeSwitched();
|
||||||
mConsumer = newConsumer(useSharedState, event);
|
mConsumer = newConsumer(useSharedState, event);
|
||||||
TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
|
TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
|
||||||
@@ -455,13 +455,18 @@ public class TouchInteractionService extends Service implements
|
|||||||
|
|
||||||
final ActivityControlHelper activityControl =
|
final ActivityControlHelper activityControl =
|
||||||
mOverviewComponentObserver.getActivityControlHelper();
|
mOverviewComponentObserver.getActivityControlHelper();
|
||||||
|
|
||||||
if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
|
if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
|
||||||
return InputConsumer.NO_OP;
|
return InputConsumer.NO_OP;
|
||||||
} else if (mAssistantAvailable
|
} else if (mAssistantAvailable
|
||||||
&& SysUINavigationMode.INSTANCE.get(this).getMode() == Mode.NO_BUTTON
|
&& SysUINavigationMode.INSTANCE.get(this).getMode() == Mode.NO_BUTTON
|
||||||
&& AssistantTouchConsumer.withinTouchRegion(this, event)) {
|
&& AssistantTouchConsumer.withinTouchRegion(this, event)) {
|
||||||
return new AssistantTouchConsumer(this, mISystemUiProxy, !activityControl.isResumed()
|
|
||||||
? createOtherActivityInputConsumer(event, runningTaskInfo) : null);
|
boolean addDelegate = !activityControl.isResumed();
|
||||||
|
return new AssistantTouchConsumer(this, mISystemUiProxy, addDelegate ?
|
||||||
|
createOtherActivityInputConsumer(event, runningTaskInfo) : null,
|
||||||
|
mInputMonitorCompat, activityControl);
|
||||||
|
|
||||||
} else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
|
} else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
|
||||||
return OverviewInputConsumer.newInstance(activityControl, false);
|
return OverviewInputConsumer.newInstance(activityControl, false);
|
||||||
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
|
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
|
||||||
|
|||||||
+28
-17
@@ -64,6 +64,9 @@ import android.view.WindowInsets;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
|
||||||
import com.android.launcher3.AbstractFloatingView;
|
import com.android.launcher3.AbstractFloatingView;
|
||||||
import com.android.launcher3.BaseDraggingActivity;
|
import com.android.launcher3.BaseDraggingActivity;
|
||||||
import com.android.launcher3.DeviceProfile;
|
import com.android.launcher3.DeviceProfile;
|
||||||
@@ -103,9 +106,6 @@ import com.android.systemui.shared.system.WindowCallbacksCompat;
|
|||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.O)
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||||
implements SwipeAnimationListener, OnApplyWindowInsetsListener {
|
implements SwipeAnimationListener, OnApplyWindowInsetsListener {
|
||||||
@@ -253,6 +253,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
|||||||
private boolean mGestureStarted;
|
private boolean mGestureStarted;
|
||||||
private int mLogAction = Touch.SWIPE;
|
private int mLogAction = Touch.SWIPE;
|
||||||
private int mLogDirection = Direction.UP;
|
private int mLogDirection = Direction.UP;
|
||||||
|
private PointF mDownPos;
|
||||||
|
|
||||||
private final RecentsAnimationWrapper mRecentsAnimationWrapper;
|
private final RecentsAnimationWrapper mRecentsAnimationWrapper;
|
||||||
|
|
||||||
@@ -429,8 +430,19 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
|||||||
// If we've already ended the gesture and are going home, don't prepare recents UI,
|
// If we've already ended the gesture and are going home, don't prepare recents UI,
|
||||||
// as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
|
// as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
|
||||||
if (mGestureEndTarget != HOME) {
|
if (mGestureEndTarget != HOME) {
|
||||||
mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
|
Runnable initAnimFactory = () -> {
|
||||||
mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated);
|
mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
|
||||||
|
mWasLauncherAlreadyVisible, true,
|
||||||
|
this::onAnimatorPlaybackControllerCreated);
|
||||||
|
};
|
||||||
|
if (mWasLauncherAlreadyVisible) {
|
||||||
|
// Launcher is visible, but might be about to stop. Thus, if we prepare recents
|
||||||
|
// now, it might get overridden by moveToRestState() in onStop(). To avoid this,
|
||||||
|
// wait until the next gesture (and possibly launcher) starts.
|
||||||
|
mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory);
|
||||||
|
} else {
|
||||||
|
initAnimFactory.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
|
AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
|
||||||
|
|
||||||
@@ -692,9 +704,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
|||||||
/**
|
/**
|
||||||
* @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
|
* @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
|
||||||
* @param velocity The x and y components of the velocity when the gesture ends.
|
* @param velocity The x and y components of the velocity when the gesture ends.
|
||||||
|
* @param downPos The x and y value of where the gesture started.
|
||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
public void onGestureEnded(float endVelocity, PointF velocity) {
|
public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
|
||||||
float flingThreshold = mContext.getResources()
|
float flingThreshold = mContext.getResources()
|
||||||
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
|
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
|
||||||
boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
|
boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
|
||||||
@@ -707,6 +720,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
|||||||
} else {
|
} else {
|
||||||
mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
|
mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
|
||||||
}
|
}
|
||||||
|
mDownPos = downPos;
|
||||||
handleNormalGestureEnd(endVelocity, isFling, velocity);
|
handleNormalGestureEnd(endVelocity, isFling, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,14 +747,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
|||||||
float endShift;
|
float endShift;
|
||||||
final float startShift;
|
final float startShift;
|
||||||
Interpolator interpolator = DEACCEL;
|
Interpolator interpolator = DEACCEL;
|
||||||
int nextPage = 0;
|
|
||||||
int taskToLaunch = 0;
|
|
||||||
final boolean goingToNewTask;
|
final boolean goingToNewTask;
|
||||||
if (mRecentsView != null) {
|
if (mRecentsView != null) {
|
||||||
nextPage = mRecentsView.getNextPage();
|
|
||||||
final int lastTaskIndex = mRecentsView.getTaskViewCount() - 1;
|
|
||||||
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
|
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
|
||||||
taskToLaunch = nextPage <= lastTaskIndex ? nextPage : lastTaskIndex;
|
final int taskToLaunch = mRecentsView.getNextPage();
|
||||||
goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
|
goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
|
||||||
} else {
|
} else {
|
||||||
goingToNewTask = false;
|
goingToNewTask = false;
|
||||||
@@ -806,11 +816,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mRecentsView != null && !endTarget.isLauncher && taskToLaunch != nextPage) {
|
|
||||||
// Scrolled to Clear all button, snap back to last task and launch it.
|
|
||||||
mRecentsView.snapToPage(taskToLaunch, Math.toIntExact(duration), interpolator);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endTarget == HOME) {
|
if (endTarget == HOME) {
|
||||||
setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
|
setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
|
||||||
duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
|
duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
|
||||||
@@ -845,6 +850,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
|||||||
: mRecentsView.getNextPage();
|
: mRecentsView.getNextPage();
|
||||||
UserEventDispatcher.newInstance(mContext).logStateChangeAction(
|
UserEventDispatcher.newInstance(mContext).logStateChangeAction(
|
||||||
mLogAction, mLogDirection,
|
mLogAction, mLogDirection,
|
||||||
|
(int) mDownPos.x, (int) mDownPos.y,
|
||||||
ContainerType.NAVBAR, ContainerType.APP,
|
ContainerType.NAVBAR, ContainerType.APP,
|
||||||
endTarget.containerType,
|
endTarget.containerType,
|
||||||
pageIndex);
|
pageIndex);
|
||||||
@@ -1044,7 +1050,12 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void invalidateHandlerWithLauncher() {
|
private void invalidateHandlerWithLauncher() {
|
||||||
mLauncherTransitionController = null;
|
if (mLauncherTransitionController != null) {
|
||||||
|
if (mLauncherTransitionController.getAnimationPlayer().isStarted()) {
|
||||||
|
mLauncherTransitionController.getAnimationPlayer().cancel();
|
||||||
|
}
|
||||||
|
mLauncherTransitionController = null;
|
||||||
|
}
|
||||||
|
|
||||||
mRecentsView.setEnableFreeScroll(true);
|
mRecentsView.setEnableFreeScroll(true);
|
||||||
mRecentsView.setRunningTaskIconScaledDown(false);
|
mRecentsView.setRunningTaskIconScaledDown(false);
|
||||||
|
|||||||
@@ -1,72 +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.hints;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.FloatProperty;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
|
|
||||||
public class ChipsContainer extends FrameLayout {
|
|
||||||
|
|
||||||
private static final String TAG = "ChipsContainer";
|
|
||||||
|
|
||||||
public static final FloatProperty<ChipsContainer> HINT_VISIBILITY =
|
|
||||||
new FloatProperty<ChipsContainer>("hint_visibility") {
|
|
||||||
@Override
|
|
||||||
public void setValue(ChipsContainer chipsContainer, float v) {
|
|
||||||
chipsContainer.setHintVisibility(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Float get(ChipsContainer chipsContainer) {
|
|
||||||
return chipsContainer.mHintVisibility;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private float mHintVisibility;
|
|
||||||
|
|
||||||
public ChipsContainer(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChipsContainer(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChipsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChipsContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setView(View v) {
|
|
||||||
removeAllViews();
|
|
||||||
addView(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHintVisibility(float v) {
|
|
||||||
if (v == 1) {
|
|
||||||
setVisibility(VISIBLE);
|
|
||||||
} else {
|
|
||||||
setVisibility(GONE);
|
|
||||||
}
|
|
||||||
mHintVisibility = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -102,7 +102,7 @@ public class ClipAnimationHelper {
|
|||||||
public ClipAnimationHelper(Context context) {
|
public ClipAnimationHelper(Context context) {
|
||||||
mWindowCornerRadius = getWindowCornerRadius(context.getResources());
|
mWindowCornerRadius = getWindowCornerRadius(context.getResources());
|
||||||
mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(context.getResources());
|
mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(context.getResources());
|
||||||
mTaskCornerRadius = Themes.getDialogCornerRadius(context);
|
mTaskCornerRadius = TaskCornerRadius.get(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSourceStack(RemoteAnimationTargetCompat target) {
|
private void updateSourceStack(RemoteAnimationTargetCompat target) {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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 static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.android.launcher3.R;
|
||||||
|
import com.android.launcher3.util.Themes;
|
||||||
|
|
||||||
|
public class TaskCornerRadius {
|
||||||
|
|
||||||
|
public static float get(Context context) {
|
||||||
|
return supportsRoundedCornersOnWindows(context.getResources()) ?
|
||||||
|
Themes.getDialogCornerRadius(context):
|
||||||
|
context.getResources().getDimension(R.dimen.task_corner_radius_small);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ package com.android.quickstep.views;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Property;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
|
||||||
import com.android.launcher3.Utilities;
|
import com.android.launcher3.Utilities;
|
||||||
@@ -26,8 +27,22 @@ import com.android.quickstep.views.RecentsView.ScrollState;
|
|||||||
|
|
||||||
public class ClearAllButton extends Button implements PageCallbacks {
|
public class ClearAllButton extends Button implements PageCallbacks {
|
||||||
|
|
||||||
|
public static final Property<ClearAllButton, Float> VISIBILITY_ALPHA =
|
||||||
|
new Property<ClearAllButton, Float>(Float.class, "visibilityAlpha") {
|
||||||
|
@Override
|
||||||
|
public Float get(ClearAllButton view) {
|
||||||
|
return view.mVisibilityAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(ClearAllButton view, Float visibilityAlpha) {
|
||||||
|
view.setVisibilityAlpha(visibilityAlpha);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private float mScrollAlpha = 1;
|
private float mScrollAlpha = 1;
|
||||||
private float mContentAlpha = 1;
|
private float mContentAlpha = 1;
|
||||||
|
private float mVisibilityAlpha = 1;
|
||||||
|
|
||||||
private final boolean mIsRtl;
|
private final boolean mIsRtl;
|
||||||
|
|
||||||
@@ -58,6 +73,13 @@ public class ClearAllButton extends Button implements PageCallbacks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setVisibilityAlpha(float alpha) {
|
||||||
|
if (mVisibilityAlpha != alpha) {
|
||||||
|
mVisibilityAlpha = alpha;
|
||||||
|
updateAlpha();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPageScroll(ScrollState scrollState) {
|
public void onPageScroll(ScrollState scrollState) {
|
||||||
float width = getWidth();
|
float width = getWidth();
|
||||||
@@ -72,7 +94,7 @@ public class ClearAllButton extends Button implements PageCallbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateAlpha() {
|
private void updateAlpha() {
|
||||||
final float alpha = mScrollAlpha * mContentAlpha;
|
final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha;
|
||||||
setAlpha(alpha);
|
setAlpha(alpha);
|
||||||
setClickable(alpha == 1);
|
setClickable(alpha == 1);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-5
@@ -32,7 +32,6 @@ import android.os.UserHandle;
|
|||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@@ -59,7 +58,6 @@ public final class DigitalWellBeingToast extends LinearLayout {
|
|||||||
private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
|
private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
|
||||||
|
|
||||||
private Task mTask;
|
private Task mTask;
|
||||||
private ImageView mImage;
|
|
||||||
private TextView mText;
|
private TextView mText;
|
||||||
|
|
||||||
public DigitalWellBeingToast(Context context, AttributeSet attrs) {
|
public DigitalWellBeingToast(Context context, AttributeSet attrs) {
|
||||||
@@ -70,12 +68,15 @@ public final class DigitalWellBeingToast extends LinearLayout {
|
|||||||
mLauncherApps = context.getSystemService(LauncherApps.class);
|
mLauncherApps = context.getSystemService(LauncherApps.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TextView getTextView() {
|
||||||
|
return mText;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFinishInflate() {
|
protected void onFinishInflate() {
|
||||||
super.onFinishInflate();
|
super.onFinishInflate();
|
||||||
|
|
||||||
mText = findViewById(R.id.digital_well_being_remaining_time);
|
mText = findViewById(R.id.digital_well_being_remaining_time);
|
||||||
mImage = findViewById(R.id.digital_well_being_hourglass);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize(Task task, InitializeCallback callback) {
|
public void initialize(Task task, InitializeCallback callback) {
|
||||||
@@ -103,8 +104,6 @@ public final class DigitalWellBeingToast extends LinearLayout {
|
|||||||
} else {
|
} else {
|
||||||
setVisibility(VISIBLE);
|
setVisibility(VISIBLE);
|
||||||
mText.setText(getText(appRemainingTimeMs));
|
mText.setText(getText(appRemainingTimeMs));
|
||||||
mImage.setImageResource(appRemainingTimeMs > 0 ?
|
|
||||||
R.drawable.hourglass_top : R.drawable.hourglass_bottom);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.call(getContentDescriptionForTask(
|
callback.call(getContentDescriptionForTask(
|
||||||
|
|||||||
+23
-54
@@ -19,9 +19,9 @@ import static com.android.launcher3.LauncherState.ALL_APPS;
|
|||||||
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
|
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
|
||||||
import static com.android.launcher3.LauncherState.NORMAL;
|
import static com.android.launcher3.LauncherState.NORMAL;
|
||||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||||
|
import static com.android.launcher3.LauncherState.RECENTS_CLEAR_ALL_BUTTON;
|
||||||
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
|
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
|
||||||
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
|
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
|
||||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_HINTS_IN_OVERVIEW;
|
|
||||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
|
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
|
||||||
|
|
||||||
import android.animation.AnimatorSet;
|
import android.animation.AnimatorSet;
|
||||||
@@ -39,11 +39,10 @@ import com.android.launcher3.Launcher;
|
|||||||
import com.android.launcher3.LauncherState;
|
import com.android.launcher3.LauncherState;
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.anim.Interpolators;
|
import com.android.launcher3.anim.Interpolators;
|
||||||
import com.android.launcher3.util.PendingAnimation;
|
import com.android.launcher3.appprediction.PredictionUiStateManager;
|
||||||
import com.android.launcher3.views.BaseDragLayer;
|
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
|
||||||
import com.android.launcher3.views.ScrimView;
|
import com.android.launcher3.views.ScrimView;
|
||||||
import com.android.quickstep.SysUINavigationMode;
|
import com.android.quickstep.SysUINavigationMode;
|
||||||
import com.android.quickstep.hints.ChipsContainer;
|
|
||||||
import com.android.quickstep.util.ClipAnimationHelper;
|
import com.android.quickstep.util.ClipAnimationHelper;
|
||||||
import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
|
import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
|
||||||
import com.android.quickstep.util.LayoutUtils;
|
import com.android.quickstep.util.LayoutUtils;
|
||||||
@@ -55,7 +54,6 @@ import com.android.quickstep.util.LayoutUtils;
|
|||||||
public class LauncherRecentsView extends RecentsView<Launcher> {
|
public class LauncherRecentsView extends RecentsView<Launcher> {
|
||||||
|
|
||||||
private final TransformParams mTransformParams = new TransformParams();
|
private final TransformParams mTransformParams = new TransformParams();
|
||||||
private ChipsContainer mChipsContainer;
|
|
||||||
|
|
||||||
public LauncherRecentsView(Context context) {
|
public LauncherRecentsView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@@ -75,14 +73,6 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
|||||||
mActivity.getStateManager().goToState(NORMAL);
|
mActivity.getStateManager().goToState(NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onAttachedToWindow() {
|
|
||||||
super.onAttachedToWindow();
|
|
||||||
mChipsContainer = mActivity.findViewById(R.id.hints);
|
|
||||||
BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) mChipsContainer.getLayoutParams();
|
|
||||||
params.bottomMargin = mActivity.getDeviceProfile().chipHintBottomMarginPx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTranslationY(float translationY) {
|
public void setTranslationY(float translationY) {
|
||||||
super.setTranslationY(translationY);
|
super.setTranslationY(translationY);
|
||||||
@@ -94,16 +84,6 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHintVisibility(float v) {
|
|
||||||
if (mChipsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) {
|
|
||||||
mChipsContainer.setHintVisibility(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChipsContainer getChipsContainer() {
|
|
||||||
return mChipsContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(Canvas canvas) {
|
public void draw(Canvas canvas) {
|
||||||
maybeDrawEmptyMessage(canvas);
|
maybeDrawEmptyMessage(canvas);
|
||||||
@@ -154,37 +134,6 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
|||||||
return anim;
|
return anim;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
|
|
||||||
PendingAnimation anim = super.createTaskLauncherAnimation(tv, duration);
|
|
||||||
|
|
||||||
if (ENABLE_HINTS_IN_OVERVIEW.get()) {
|
|
||||||
anim.anim.play(ObjectAnimator.ofFloat(
|
|
||||||
mChipsContainer, ChipsContainer.HINT_VISIBILITY, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return anim;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
|
|
||||||
boolean shouldRemoveTask, long duration) {
|
|
||||||
PendingAnimation anim = super.createTaskDismissAnimation(taskView, animateTaskView,
|
|
||||||
shouldRemoveTask, duration);
|
|
||||||
|
|
||||||
if (ENABLE_HINTS_IN_OVERVIEW.get()) {
|
|
||||||
anim.anim.play(ObjectAnimator.ofFloat(
|
|
||||||
mChipsContainer, ChipsContainer.HINT_VISIBILITY, 0));
|
|
||||||
anim.addEndListener(onEndListener -> {
|
|
||||||
if (!onEndListener.isSuccess) {
|
|
||||||
mChipsContainer.setHintVisibility(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return anim;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void getTaskSize(DeviceProfile dp, Rect outRect) {
|
protected void getTaskSize(DeviceProfile dp, Rect outRect) {
|
||||||
LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
|
LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
|
||||||
@@ -247,4 +196,24 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
super.reset();
|
||||||
|
|
||||||
|
// We are moving to home or some other UI with no recents. Switch back to the home client,
|
||||||
|
// the home predictions should have been updated when the activity was resumed.
|
||||||
|
PredictionUiStateManager.INSTANCE.get(getContext()).switchClient(Client.HOME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOverviewStateEnabled(boolean enabled) {
|
||||||
|
super.setOverviewStateEnabled(enabled);
|
||||||
|
if (enabled) {
|
||||||
|
LauncherState state = mActivity.getStateManager().getState();
|
||||||
|
boolean hasClearAllButton = (state.getVisibleElements(mActivity)
|
||||||
|
& RECENTS_CLEAR_ALL_BUTTON) != 0;
|
||||||
|
setDisallowScrollToClearAll(!hasClearAllButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package com.android.quickstep.views;
|
|||||||
|
|
||||||
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
|
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
|
||||||
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
|
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
|
||||||
|
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
|
||||||
import static com.android.launcher3.anim.Interpolators.ACCEL;
|
import static com.android.launcher3.anim.Interpolators.ACCEL;
|
||||||
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
|
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
|
||||||
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
|
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
|
||||||
@@ -25,9 +26,10 @@ import static com.android.launcher3.anim.Interpolators.LINEAR;
|
|||||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
|
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
|
||||||
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
|
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
|
||||||
import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
|
import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
|
||||||
|
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
|
||||||
|
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
|
||||||
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
|
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
|
||||||
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
|
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
|
||||||
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
|
|
||||||
import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
|
import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
@@ -46,6 +48,7 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.Typeface;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -89,7 +92,6 @@ import com.android.launcher3.util.OverScroller;
|
|||||||
import com.android.launcher3.util.PendingAnimation;
|
import com.android.launcher3.util.PendingAnimation;
|
||||||
import com.android.launcher3.util.Themes;
|
import com.android.launcher3.util.Themes;
|
||||||
import com.android.launcher3.util.ViewPool;
|
import com.android.launcher3.util.ViewPool;
|
||||||
import com.android.quickstep.OverviewCallbacks;
|
|
||||||
import com.android.quickstep.RecentsAnimationWrapper;
|
import com.android.quickstep.RecentsAnimationWrapper;
|
||||||
import com.android.quickstep.RecentsModel;
|
import com.android.quickstep.RecentsModel;
|
||||||
import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
|
import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
|
||||||
@@ -169,6 +171,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
|||||||
private final ViewPool<TaskView> mTaskViewPool;
|
private final ViewPool<TaskView> mTaskViewPool;
|
||||||
|
|
||||||
private boolean mDwbToastShown;
|
private boolean mDwbToastShown;
|
||||||
|
private boolean mDisallowScrollToClearAll;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Call reloadIdNeeded in onTaskStackChanged.
|
* TODO: Call reloadIdNeeded in onTaskStackChanged.
|
||||||
@@ -322,6 +325,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
|||||||
mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary));
|
mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary));
|
||||||
mEmptyMessagePaint.setTextSize(getResources()
|
mEmptyMessagePaint.setTextSize(getResources()
|
||||||
.getDimension(R.dimen.recents_empty_message_text_size));
|
.getDimension(R.dimen.recents_empty_message_text_size));
|
||||||
|
mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context),
|
||||||
|
Typeface.NORMAL));
|
||||||
mEmptyMessagePadding = getResources()
|
mEmptyMessagePadding = getResources()
|
||||||
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
|
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
|
||||||
setWillNotDraw(false);
|
setWillNotDraw(false);
|
||||||
@@ -748,8 +753,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
|||||||
|
|
||||||
unloadVisibleTaskData();
|
unloadVisibleTaskData();
|
||||||
setCurrentPage(0);
|
setCurrentPage(0);
|
||||||
|
|
||||||
OverviewCallbacks.get(getContext()).onResetOverview();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1159,6 +1162,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private void dismissAllTasks(View view) {
|
private void dismissAllTasks(View view) {
|
||||||
runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
|
runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
|
||||||
|
mActivity.getUserEventDispatcher().logActionOnControl(TAP, CLEAR_ALL_BUTTON);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dismissCurrentTask() {
|
private void dismissCurrentTask() {
|
||||||
@@ -1606,4 +1610,33 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
|||||||
|
|
||||||
mRecentsAnimationWrapper.finish(toRecents, onFinishComplete);
|
mRecentsAnimationWrapper.finish(toRecents, onFinishComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
|
||||||
|
if (mDisallowScrollToClearAll != disallowScrollToClearAll) {
|
||||||
|
mDisallowScrollToClearAll = disallowScrollToClearAll;
|
||||||
|
updateMinAndMaxScrollX();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int computeMinScrollX() {
|
||||||
|
if (mIsRtl && mDisallowScrollToClearAll) {
|
||||||
|
// We aren't showing the clear all button, so use the leftmost task as the min scroll.
|
||||||
|
return getScrollForPage(getTaskViewCount() - 1);
|
||||||
|
}
|
||||||
|
return super.computeMinScrollX();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int computeMaxScrollX() {
|
||||||
|
if (!mIsRtl && mDisallowScrollToClearAll) {
|
||||||
|
// We aren't showing the clear all button, so use the rightmost task as the max scroll.
|
||||||
|
return getScrollForPage(getTaskViewCount() - 1);
|
||||||
|
}
|
||||||
|
return super.computeMaxScrollX();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClearAllButton getClearAllButton() {
|
||||||
|
return mClearAllButton;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -48,6 +48,7 @@ import com.android.launcher3.util.SystemUiController;
|
|||||||
import com.android.launcher3.util.Themes;
|
import com.android.launcher3.util.Themes;
|
||||||
import com.android.quickstep.TaskOverlayFactory;
|
import com.android.quickstep.TaskOverlayFactory;
|
||||||
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
|
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
|
||||||
|
import com.android.quickstep.util.TaskCornerRadius;
|
||||||
import com.android.systemui.shared.recents.model.Task;
|
import com.android.systemui.shared.recents.model.Task;
|
||||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||||
import com.android.systemui.shared.system.QuickStepContract;
|
import com.android.systemui.shared.system.QuickStepContract;
|
||||||
@@ -108,7 +109,7 @@ public class TaskThumbnailView extends View {
|
|||||||
|
|
||||||
public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
|
public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
mCornerRadius = Themes.getDialogCornerRadius(context);
|
mCornerRadius = TaskCornerRadius.get(context);
|
||||||
mOverlay = TaskOverlayFactory.INSTANCE.get(context).createOverlay(this);
|
mOverlay = TaskOverlayFactory.INSTANCE.get(context).createOverlay(this);
|
||||||
mPaint.setFilterBitmap(true);
|
mPaint.setFilterBitmap(true);
|
||||||
mBackgroundPaint.setColor(Color.WHITE);
|
mBackgroundPaint.setColor(Color.WHITE);
|
||||||
|
|||||||
@@ -211,6 +211,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
|||||||
return mMenuView;
|
return mMenuView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DigitalWellBeingToast getDigitalWellBeingToast() {
|
||||||
|
return mDigitalWellBeingToast;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates this task view to the given {@param task}.
|
* Updates this task view to the given {@param task}.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<group>
|
|
||||||
<clip-path android:pathData="M0,0H24V24H0Z M 0,0"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFFFF"
|
|
||||||
android:pathData="M6,2V8H6l4,4L6,16H6v6H18V16h0l-4,-4,4,-4h0V2Zm6,9.5,-4,-4V4h8V7.5Z"/>
|
|
||||||
</group>
|
|
||||||
</vector>
|
|
||||||
+2
-1
@@ -2,7 +2,8 @@
|
|||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24"
|
||||||
|
android:tint="?android:attr/textColorPrimary">
|
||||||
<group>
|
<group>
|
||||||
<clip-path android:pathData="M0,0H24V24H0Z M 0,0"/>
|
<clip-path android:pathData="M0,0H24V24H0Z M 0,0"/>
|
||||||
<path
|
<path
|
||||||
@@ -18,9 +18,16 @@
|
|||||||
android:width="24.0dp"
|
android:width="24.0dp"
|
||||||
android:height="24.0dp"
|
android:height="24.0dp"
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0"
|
||||||
|
android:tint="?android:attr/textColorPrimary">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFffffff"
|
android:strokeColor="#FFFFFFFF"
|
||||||
android:pathData="M16,12L16,4l1,0L17,2L7,2l0,2l1,0l0,8l-2,2l0,2l5.2,0l0,6l1.6,0l0,-6L18,16l0,-2L16,12z"/>
|
android:strokeAlpha=".008"
|
||||||
|
android:pathData="M0 0h24v24H0z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M17 4v7l2 3v2h-6v5l-1 1-1-1v-5H5v-2l2-3V4c0-1.1 0.9 -2 2-2h6c1.11 0 2 0.89 2
|
||||||
|
2zM9 4v7.75L7.5 14h9L15 11.75V4H9z" />
|
||||||
|
|
||||||
</vector>
|
</vector>
|
||||||
@@ -17,7 +17,8 @@
|
|||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24"
|
||||||
|
android:tint="?android:attr/textColorPrimary">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
|
android:src="@drawable/ic_hourglass_top"
|
||||||
/>
|
/>
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/digital_well_being_remaining_time"
|
android:id="@+id/digital_well_being_remaining_time"
|
||||||
|
|||||||
@@ -29,5 +29,5 @@
|
|||||||
|
|
||||||
<!-- Assistant Gesture -->
|
<!-- Assistant Gesture -->
|
||||||
<integer name="assistant_gesture_min_time_threshold">200</integer>
|
<integer name="assistant_gesture_min_time_threshold">200</integer>
|
||||||
<integer name="assistant_gesture_corner_deg_threshold">30</integer>
|
<integer name="assistant_gesture_corner_deg_threshold">20</integer>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
<dimen name="task_thumbnail_top_margin">24dp</dimen>
|
<dimen name="task_thumbnail_top_margin">24dp</dimen>
|
||||||
<dimen name="task_thumbnail_half_top_margin">12dp</dimen>
|
<dimen name="task_thumbnail_half_top_margin">12dp</dimen>
|
||||||
<dimen name="task_thumbnail_icon_size">48dp</dimen>
|
<dimen name="task_thumbnail_icon_size">48dp</dimen>
|
||||||
|
<!-- For screens without rounded corners -->
|
||||||
|
<dimen name="task_corner_radius_small">2dp</dimen>
|
||||||
|
|
||||||
<dimen name="recents_page_spacing">10dp</dimen>
|
<dimen name="recents_page_spacing">10dp</dimen>
|
||||||
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
|
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
|
||||||
@@ -36,7 +38,7 @@
|
|||||||
<dimen name="motion_pause_detector_speed_very_slow">0.0285dp</dimen>
|
<dimen name="motion_pause_detector_speed_very_slow">0.0285dp</dimen>
|
||||||
<dimen name="motion_pause_detector_speed_somewhat_fast">0.285dp</dimen>
|
<dimen name="motion_pause_detector_speed_somewhat_fast">0.285dp</dimen>
|
||||||
<dimen name="motion_pause_detector_speed_fast">0.5dp</dimen>
|
<dimen name="motion_pause_detector_speed_fast">0.5dp</dimen>
|
||||||
<dimen name="motion_pause_detector_min_displacement">48dp</dimen>
|
<dimen name="motion_pause_detector_min_displacement_from_app">36dp</dimen>
|
||||||
|
|
||||||
<!-- Launcher app transition -->
|
<!-- Launcher app transition -->
|
||||||
<dimen name="content_trans_y">50dp</dimen>
|
<dimen name="content_trans_y">50dp</dimen>
|
||||||
@@ -63,8 +65,8 @@
|
|||||||
<dimen name="shelf_surface_offset">24dp</dimen>
|
<dimen name="shelf_surface_offset">24dp</dimen>
|
||||||
|
|
||||||
<!-- Assistant Gestures -->
|
<!-- Assistant Gestures -->
|
||||||
<dimen name="gestures_assistant_size">28dp</dimen>
|
<dimen name="gestures_assistant_size">48dp</dimen>
|
||||||
<dimen name="gestures_assistant_drag_threshold">70dp</dimen>
|
<dimen name="gestures_assistant_drag_threshold">55dp</dimen>
|
||||||
|
|
||||||
<!-- Distance to move elements when swiping up to go home from launcher -->
|
<!-- Distance to move elements when swiping up to go home from launcher -->
|
||||||
<dimen name="home_pullback_distance">28dp</dimen>
|
<dimen name="home_pullback_distance">28dp</dimen>
|
||||||
|
|||||||
@@ -58,4 +58,13 @@
|
|||||||
<!-- Annotation shown on an app card in Recents, telling that the app has a usage limit set by
|
<!-- Annotation shown on an app card in Recents, telling that the app has a usage limit set by
|
||||||
the user, and a given time is left for it today [CHAR LIMIT=22] -->
|
the user, and a given time is left for it today [CHAR LIMIT=22] -->
|
||||||
<string name="time_left_for_app"><xliff:g id="time" example="7 minutes">%1$s</xliff:g> left today</string>
|
<string name="time_left_for_app"><xliff:g id="time" example="7 minutes">%1$s</xliff:g> left today</string>
|
||||||
|
|
||||||
|
<!-- Accessibility title for the row of all-apps containing app predictions. [CHAR LIMIT=50] -->
|
||||||
|
<string name="title_app_suggestions">App suggestions</string>
|
||||||
|
<!-- Label for the header text of the All Apps section in All Apps view, used to separate Predicted Apps and Actions section from All Apps section. [CHAR_LIMIT=50] -->
|
||||||
|
<string name="all_apps_label">All apps</string>
|
||||||
|
<!-- Text of the tip when user lands in all apps view for the first time, indicating where the tip toast points to is the predicted apps section. [CHAR_LIMIT=50] -->
|
||||||
|
<string name="all_apps_prediction_tip">Your predicted apps</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -87,7 +87,7 @@ public class UiFactory extends RecentsUiFactory {
|
|||||||
OverviewInteractionState.INSTANCE.get(launcher)
|
OverviewInteractionState.INSTANCE.get(launcher)
|
||||||
.setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
|
.setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
|
||||||
if (launcher != null && launcher.getDragLayer() != null) {
|
if (launcher != null && launcher.getDragLayer() != null) {
|
||||||
launcher.getDragLayer().setDisallowBackGesture(shouldBackButtonBeHidden);
|
launcher.getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,5 @@ public class OverviewCallbacks implements ResourceBasedOverride {
|
|||||||
|
|
||||||
public void onInitOverviewTransition() { }
|
public void onInitOverviewTransition() { }
|
||||||
|
|
||||||
public void onResetOverview() { }
|
|
||||||
|
|
||||||
public void closeAllWindows() { }
|
public void closeAllWindows() { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
|||||||
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
|
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
|
||||||
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
|
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
|
||||||
|
|
||||||
import static com.android.systemui.shared.system.PackageManagerWrapper
|
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
|
||||||
.ACTION_PREFERRED_ACTIVITY_CHANGED;
|
import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@@ -29,7 +29,6 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.os.PatternMatcher;
|
|
||||||
|
|
||||||
import com.android.systemui.shared.system.PackageManagerWrapper;
|
import com.android.systemui.shared.system.PackageManagerWrapper;
|
||||||
|
|
||||||
@@ -109,13 +108,9 @@ public final class OverviewComponentObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mUpdateRegisteredPackage = defaultHome.getPackageName();
|
mUpdateRegisteredPackage = defaultHome.getPackageName();
|
||||||
IntentFilter updateReceiver = new IntentFilter(ACTION_PACKAGE_ADDED);
|
mContext.registerReceiver(mOtherHomeAppUpdateReceiver, getPackageFilter(
|
||||||
updateReceiver.addAction(ACTION_PACKAGE_CHANGED);
|
mUpdateRegisteredPackage, ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED,
|
||||||
updateReceiver.addAction(ACTION_PACKAGE_REMOVED);
|
ACTION_PACKAGE_REMOVED));
|
||||||
updateReceiver.addDataScheme("package");
|
|
||||||
updateReceiver.addDataSchemeSpecificPart(mUpdateRegisteredPackage,
|
|
||||||
PatternMatcher.PATTERN_LITERAL);
|
|
||||||
mContext.registerReceiver(mOtherHomeAppUpdateReceiver, updateReceiver);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,15 +16,15 @@
|
|||||||
|
|
||||||
package com.android.quickstep;
|
package com.android.quickstep;
|
||||||
|
|
||||||
|
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||||
import com.android.systemui.shared.system.QuickStepContract;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -70,8 +70,6 @@ public class SysUINavigationMode {
|
|||||||
mContext = context;
|
mContext = context;
|
||||||
initializeMode();
|
initializeMode();
|
||||||
|
|
||||||
IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
|
|
||||||
filter.addDataScheme("package");
|
|
||||||
mContext.registerReceiver(new BroadcastReceiver() {
|
mContext.registerReceiver(new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@@ -81,7 +79,7 @@ public class SysUINavigationMode {
|
|||||||
dispatchModeChange();
|
dispatchModeChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, filter);
|
}, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeMode() {
|
private void initializeMode() {
|
||||||
|
|||||||
@@ -111,6 +111,14 @@ public class TestInformationProvider extends ContentProvider {
|
|||||||
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
|
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case TestProtocol.REQUEST_ENABLE_DRAG_LOGGING:
|
||||||
|
TestProtocol.sDebugTracing = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TestProtocol.REQUEST_DISABLE_DRAG_LOGGING:
|
||||||
|
TestProtocol.sDebugTracing = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,17 +36,19 @@ import com.android.systemui.shared.system.MetricsLoggerCompat;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class UserEventDispatcherExtension extends UserEventDispatcher {
|
public class UserEventDispatcherExtension extends UserEventDispatcher {
|
||||||
|
|
||||||
|
public static final int ALL_APPS_PREDICTION_TIPS = 2;
|
||||||
|
|
||||||
private static final String TAG = "UserEventDispatcher";
|
private static final String TAG = "UserEventDispatcher";
|
||||||
|
|
||||||
public UserEventDispatcherExtension(Context context) { }
|
public UserEventDispatcherExtension(Context context) { }
|
||||||
|
|
||||||
public void logStateChangeAction(int action, int dir, int srcChildTargetType,
|
public void logStateChangeAction(int action, int dir, int downX, int downY,
|
||||||
int srcParentContainerType, int dstContainerType,
|
int srcChildTargetType, int srcParentContainerType,
|
||||||
int pageIndex) {
|
int dstContainerType, int pageIndex) {
|
||||||
new MetricsLoggerCompat().visibility(MetricsLoggerCompat.OVERVIEW_ACTIVITY,
|
new MetricsLoggerCompat().visibility(MetricsLoggerCompat.OVERVIEW_ACTIVITY,
|
||||||
dstContainerType == LauncherLogProto.ContainerType.TASKSWITCHER);
|
dstContainerType == LauncherLogProto.ContainerType.TASKSWITCHER);
|
||||||
super.logStateChangeAction(action, dir, srcChildTargetType, srcParentContainerType,
|
super.logStateChangeAction(action, dir, downX, downY, srcChildTargetType,
|
||||||
dstContainerType, pageIndex);
|
srcParentContainerType, dstContainerType, pageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logActionTip(int actionType, int viewType) {
|
public void logActionTip(int actionType, int viewType) {
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ public class LayoutUtils {
|
|||||||
float extraSpace;
|
float extraSpace;
|
||||||
if (dp.isVerticalBarLayout()) {
|
if (dp.isVerticalBarLayout()) {
|
||||||
extraSpace = 0;
|
extraSpace = 0;
|
||||||
} else if (FeatureFlags.ENABLE_HINTS_IN_OVERVIEW.get()){
|
|
||||||
extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx + dp.chipHintHeightPx;
|
|
||||||
} else {
|
} else {
|
||||||
extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx;
|
extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,28 +38,26 @@ public class MotionPauseDetector {
|
|||||||
private final float mSpeedVerySlow;
|
private final float mSpeedVerySlow;
|
||||||
private final float mSpeedSomewhatFast;
|
private final float mSpeedSomewhatFast;
|
||||||
private final float mSpeedFast;
|
private final float mSpeedFast;
|
||||||
private final float mMinDisplacementForPause;
|
|
||||||
private final Alarm mForcePauseTimeout;
|
private final Alarm mForcePauseTimeout;
|
||||||
|
|
||||||
private Long mPreviousTime = null;
|
private Long mPreviousTime = null;
|
||||||
private Float mPreviousPosition = null;
|
private Float mPreviousPosition = null;
|
||||||
private Float mPreviousVelocity = null;
|
private Float mPreviousVelocity = null;
|
||||||
|
|
||||||
private TotalDisplacement mTotalDisplacement = new TotalDisplacement();
|
|
||||||
private Float mFirstPosition = null;
|
private Float mFirstPosition = null;
|
||||||
private Float mFirstOrthogonalPosition = null;
|
|
||||||
|
|
||||||
private OnMotionPauseListener mOnMotionPauseListener;
|
private OnMotionPauseListener mOnMotionPauseListener;
|
||||||
private boolean mIsPaused;
|
private boolean mIsPaused;
|
||||||
// Bias more for the first pause to make it feel extra responsive.
|
// Bias more for the first pause to make it feel extra responsive.
|
||||||
private boolean mHasEverBeenPaused;
|
private boolean mHasEverBeenPaused;
|
||||||
|
/** @see #setDisallowPause(boolean) */
|
||||||
|
private boolean mDisallowPause;
|
||||||
|
|
||||||
public MotionPauseDetector(Context context) {
|
public MotionPauseDetector(Context context) {
|
||||||
Resources res = context.getResources();
|
Resources res = context.getResources();
|
||||||
mSpeedVerySlow = res.getDimension(R.dimen.motion_pause_detector_speed_very_slow);
|
mSpeedVerySlow = res.getDimension(R.dimen.motion_pause_detector_speed_very_slow);
|
||||||
mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
|
mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
|
||||||
mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
|
mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
|
||||||
mMinDisplacementForPause = res.getDimension(R.dimen.motion_pause_detector_min_displacement);
|
|
||||||
mForcePauseTimeout = new Alarm();
|
mForcePauseTimeout = new Alarm();
|
||||||
mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
|
mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
|
||||||
}
|
}
|
||||||
@@ -72,29 +70,31 @@ public class MotionPauseDetector {
|
|||||||
mOnMotionPauseListener = listener;
|
mOnMotionPauseListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param disallowPause If true, we will not detect any pauses until this is set to false again.
|
||||||
|
*/
|
||||||
|
public void setDisallowPause(boolean disallowPause) {
|
||||||
|
mDisallowPause = disallowPause;
|
||||||
|
updatePaused(mIsPaused);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes velocity and acceleration to determine whether the motion is paused.
|
* Computes velocity and acceleration to determine whether the motion is paused.
|
||||||
* @param position The x or y component of the motion being tracked.
|
* @param position The x or y component of the motion being tracked.
|
||||||
* @param orthogonalPosition The x or y component (opposite of {@param position}) of the motion.
|
|
||||||
*
|
*
|
||||||
* TODO: Use historical positions as well, e.g. {@link MotionEvent#getHistoricalY(int, int)}.
|
* TODO: Use historical positions as well, e.g. {@link MotionEvent#getHistoricalY(int, int)}.
|
||||||
*/
|
*/
|
||||||
public void addPosition(float position, float orthogonalPosition, long time) {
|
public void addPosition(float position, long time) {
|
||||||
if (mFirstPosition == null) {
|
if (mFirstPosition == null) {
|
||||||
mFirstPosition = position;
|
mFirstPosition = position;
|
||||||
}
|
}
|
||||||
if (mFirstOrthogonalPosition == null) {
|
|
||||||
mFirstOrthogonalPosition = orthogonalPosition;
|
|
||||||
}
|
|
||||||
mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
|
mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
|
||||||
if (mPreviousTime != null && mPreviousPosition != null) {
|
if (mPreviousTime != null && mPreviousPosition != null) {
|
||||||
long changeInTime = Math.max(1, time - mPreviousTime);
|
long changeInTime = Math.max(1, time - mPreviousTime);
|
||||||
float changeInPosition = position - mPreviousPosition;
|
float changeInPosition = position - mPreviousPosition;
|
||||||
float velocity = changeInPosition / changeInTime;
|
float velocity = changeInPosition / changeInTime;
|
||||||
if (mPreviousVelocity != null) {
|
if (mPreviousVelocity != null) {
|
||||||
mTotalDisplacement.set(Math.abs(position - mFirstPosition),
|
checkMotionPaused(velocity, mPreviousVelocity);
|
||||||
Math.abs(orthogonalPosition - mFirstOrthogonalPosition));
|
|
||||||
checkMotionPaused(velocity, mPreviousVelocity, mTotalDisplacement);
|
|
||||||
}
|
}
|
||||||
mPreviousVelocity = velocity;
|
mPreviousVelocity = velocity;
|
||||||
}
|
}
|
||||||
@@ -102,8 +102,7 @@ public class MotionPauseDetector {
|
|||||||
mPreviousPosition = position;
|
mPreviousPosition = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkMotionPaused(float velocity, float prevVelocity,
|
private void checkMotionPaused(float velocity, float prevVelocity) {
|
||||||
TotalDisplacement totalDisplacement) {
|
|
||||||
float speed = Math.abs(velocity);
|
float speed = Math.abs(velocity);
|
||||||
float previousSpeed = Math.abs(prevVelocity);
|
float previousSpeed = Math.abs(prevVelocity);
|
||||||
boolean isPaused;
|
boolean isPaused;
|
||||||
@@ -125,16 +124,13 @@ public class MotionPauseDetector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean passedMinDisplacement = totalDisplacement.primary >= mMinDisplacementForPause;
|
|
||||||
boolean isDisplacementOrthogonal = totalDisplacement.orthogonal > totalDisplacement.primary;
|
|
||||||
if (!passedMinDisplacement || isDisplacementOrthogonal) {
|
|
||||||
mForcePauseTimeout.cancelAlarm();
|
|
||||||
isPaused = false;
|
|
||||||
}
|
|
||||||
updatePaused(isPaused);
|
updatePaused(isPaused);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePaused(boolean isPaused) {
|
private void updatePaused(boolean isPaused) {
|
||||||
|
if (mDisallowPause) {
|
||||||
|
isPaused = false;
|
||||||
|
}
|
||||||
if (mIsPaused != isPaused) {
|
if (mIsPaused != isPaused) {
|
||||||
mIsPaused = isPaused;
|
mIsPaused = isPaused;
|
||||||
if (mIsPaused) {
|
if (mIsPaused) {
|
||||||
@@ -151,8 +147,6 @@ public class MotionPauseDetector {
|
|||||||
mPreviousPosition = null;
|
mPreviousPosition = null;
|
||||||
mPreviousVelocity = null;
|
mPreviousVelocity = null;
|
||||||
mFirstPosition = null;
|
mFirstPosition = null;
|
||||||
mFirstOrthogonalPosition = null;
|
|
||||||
mTotalDisplacement.set(0, 0);
|
|
||||||
setOnMotionPauseListener(null);
|
setOnMotionPauseListener(null);
|
||||||
mIsPaused = mHasEverBeenPaused = false;
|
mIsPaused = mHasEverBeenPaused = false;
|
||||||
mForcePauseTimeout.cancelAlarm();
|
mForcePauseTimeout.cancelAlarm();
|
||||||
@@ -165,18 +159,4 @@ public class MotionPauseDetector {
|
|||||||
public interface OnMotionPauseListener {
|
public interface OnMotionPauseListener {
|
||||||
void onMotionPauseChanged(boolean isPaused);
|
void onMotionPauseChanged(boolean isPaused);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains the displacement from the first tracked position,
|
|
||||||
* along both the primary and orthogonal axes.
|
|
||||||
*/
|
|
||||||
private class TotalDisplacement {
|
|
||||||
public float primary;
|
|
||||||
public float orthogonal;
|
|
||||||
|
|
||||||
public void set(float primaryDisplacement, float orthogonalDisplacement) {
|
|
||||||
this.primary = primaryDisplacement;
|
|
||||||
this.orthogonal = orthogonalDisplacement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* 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 org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import android.app.prediction.AppPredictor;
|
||||||
|
import android.app.prediction.AppTarget;
|
||||||
|
import android.app.prediction.AppTargetId;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.pm.LauncherActivityInfo;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
|
import com.android.launcher3.BubbleTextView;
|
||||||
|
import com.android.launcher3.Launcher;
|
||||||
|
import com.android.launcher3.appprediction.PredictionRowView;
|
||||||
|
import com.android.launcher3.appprediction.PredictionUiStateManager;
|
||||||
|
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
|
||||||
|
import com.android.launcher3.compat.LauncherAppsCompat;
|
||||||
|
import com.android.launcher3.model.AppLaunchTracker;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class AppPredictionsUITests extends AbstractQuickStepTest {
|
||||||
|
private static final int DEFAULT_APP_LAUNCH_TIMES = 3;
|
||||||
|
private static final String TAG = "AppPredictionsUITests";
|
||||||
|
|
||||||
|
private LauncherActivityInfo mSampleApp1;
|
||||||
|
private LauncherActivityInfo mSampleApp2;
|
||||||
|
private LauncherActivityInfo mSampleApp3;
|
||||||
|
|
||||||
|
private AppPredictor.Callback mCallback;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(mTargetContext)
|
||||||
|
.getActivityList(null, Process.myUserHandle());
|
||||||
|
mSampleApp1 = activities.get(0);
|
||||||
|
mSampleApp2 = activities.get(1);
|
||||||
|
mSampleApp3 = activities.get(2);
|
||||||
|
|
||||||
|
// Disable app tracker
|
||||||
|
AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
|
||||||
|
|
||||||
|
mCallback = PredictionUiStateManager.INSTANCE.get(mTargetContext).appPredictorCallback(
|
||||||
|
Client.HOME);
|
||||||
|
|
||||||
|
mDevice.setOrientationNatural();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Throwable {
|
||||||
|
mDevice.unfreezeRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that prediction UI is updated as soon as we get predictions from the system
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPredictionExistsInAllApps() {
|
||||||
|
mActivityMonitor.startLauncher();
|
||||||
|
mLauncher.pressHome().switchToAllApps();
|
||||||
|
|
||||||
|
// There has not been any update, verify that progress bar is showing
|
||||||
|
waitForLauncherCondition("Prediction is not in loading state", launcher -> {
|
||||||
|
ProgressBar p = findLoadingBar(launcher);
|
||||||
|
return p != null && p.isShown();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dispatch an update
|
||||||
|
sendPredictionUpdate(mSampleApp1, mSampleApp2);
|
||||||
|
waitForLauncherCondition("Predictions were not updated in loading state",
|
||||||
|
launcher -> getPredictedApp(launcher).size() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test tat prediction update is deferred if it is already visible
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPredictionsDeferredUntilHome() {
|
||||||
|
mActivityMonitor.startLauncher();
|
||||||
|
sendPredictionUpdate(mSampleApp1, mSampleApp2);
|
||||||
|
mLauncher.pressHome().switchToAllApps();
|
||||||
|
waitForLauncherCondition("Predictions were not updated in loading state",
|
||||||
|
launcher -> getPredictedApp(launcher).size() == 2);
|
||||||
|
|
||||||
|
// Update predictions while all-apps is visible
|
||||||
|
sendPredictionUpdate(mSampleApp1, mSampleApp2, mSampleApp3);
|
||||||
|
assertEquals(2, getFromLauncher(this::getPredictedApp).size());
|
||||||
|
|
||||||
|
// Go home and go back to all-apps
|
||||||
|
mLauncher.pressHome().switchToAllApps();
|
||||||
|
assertEquals(3, getFromLauncher(this::getPredictedApp).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<BubbleTextView> getPredictedApp(Launcher launcher) {
|
||||||
|
PredictionRowView container = launcher.getAppsView().getFloatingHeaderView()
|
||||||
|
.findFixedRowByType(PredictionRowView.class);
|
||||||
|
|
||||||
|
ArrayList<BubbleTextView> predictedAppViews = new ArrayList<>();
|
||||||
|
for (int i = 0; i < container.getChildCount(); i++) {
|
||||||
|
View view = container.getChildAt(i);
|
||||||
|
if (view instanceof BubbleTextView && view.getVisibility() == View.VISIBLE) {
|
||||||
|
predictedAppViews.add((BubbleTextView) view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return predictedAppViews;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProgressBar findLoadingBar(Launcher launcher) {
|
||||||
|
PredictionRowView container = launcher.getAppsView().getFloatingHeaderView()
|
||||||
|
.findFixedRowByType(PredictionRowView.class);
|
||||||
|
|
||||||
|
for (int i = 0; i < container.getChildCount(); i++) {
|
||||||
|
View view = container.getChildAt(i);
|
||||||
|
if (view instanceof ProgressBar) {
|
||||||
|
return (ProgressBar) view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void sendPredictionUpdate(LauncherActivityInfo... activities) {
|
||||||
|
getOnUiThread(() -> {
|
||||||
|
List<AppTarget> targets = new ArrayList<>(activities.length);
|
||||||
|
for (LauncherActivityInfo info : activities) {
|
||||||
|
ComponentName cn = info.getComponentName();
|
||||||
|
AppTarget target = new AppTarget.Builder(new AppTargetId("app:" + cn))
|
||||||
|
.setTarget(cn.getPackageName(), info.getUser())
|
||||||
|
.setClassName(cn.getClassName())
|
||||||
|
.build();
|
||||||
|
targets.add(target);
|
||||||
|
}
|
||||||
|
mCallback.onTargetsAvailable(targets);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.android.quickstep;
|
||||||
|
|
||||||
|
import static androidx.test.InstrumentationRegistry.getInstrumentation;
|
||||||
|
|
||||||
|
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.usage.UsageStatsManager;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import com.android.launcher3.Launcher;
|
||||||
|
import com.android.quickstep.views.DigitalWellBeingToast;
|
||||||
|
import com.android.quickstep.views.RecentsView;
|
||||||
|
import com.android.quickstep.views.TaskView;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
public class DigitalWellBeingToastTest extends AbstractQuickStepTest {
|
||||||
|
private static final String CALCULATOR_PACKAGE =
|
||||||
|
resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToast() throws Exception {
|
||||||
|
final UsageStatsManager usageStatsManager =
|
||||||
|
mTargetContext.getSystemService(UsageStatsManager.class);
|
||||||
|
final int observerId = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final String[] packages = new String[]{CALCULATOR_PACKAGE};
|
||||||
|
|
||||||
|
// Set time limit for app.
|
||||||
|
runWithShellPermission(() ->
|
||||||
|
usageStatsManager.registerAppUsageLimitObserver(observerId, packages,
|
||||||
|
Duration.ofSeconds(600), Duration.ofSeconds(300),
|
||||||
|
PendingIntent.getActivity(mTargetContext, -1, new Intent(), 0)));
|
||||||
|
|
||||||
|
mLauncher.pressHome();
|
||||||
|
final DigitalWellBeingToast toast = getToast();
|
||||||
|
|
||||||
|
assertTrue("Toast is not visible", toast.isShown());
|
||||||
|
assertEquals("Toast text: ", "5 minutes left today", toast.getTextView().getText());
|
||||||
|
|
||||||
|
// Unset time limit for app.
|
||||||
|
runWithShellPermission(
|
||||||
|
() -> usageStatsManager.unregisterAppUsageLimitObserver(observerId));
|
||||||
|
|
||||||
|
mLauncher.pressHome();
|
||||||
|
assertFalse("Toast is visible", getToast().isShown());
|
||||||
|
} finally {
|
||||||
|
runWithShellPermission(
|
||||||
|
() -> usageStatsManager.unregisterAppUsageLimitObserver(observerId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DigitalWellBeingToast getToast() {
|
||||||
|
executeOnLauncher(launcher -> launcher.getStateManager().goToState(OVERVIEW));
|
||||||
|
waitForState("Launcher internal state didn't switch to Overview", OVERVIEW);
|
||||||
|
waitForLauncherCondition("No latest task", launcher -> getLatestTask(launcher) != null);
|
||||||
|
|
||||||
|
return getFromLauncher(launcher -> {
|
||||||
|
final TaskView task = getLatestTask(launcher);
|
||||||
|
assertTrue("Latest task is not Calculator",
|
||||||
|
CALCULATOR_PACKAGE.equals(task.getTask().getTopComponent().getPackageName()));
|
||||||
|
return task.getDigitalWellBeingToast();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskView getLatestTask(Launcher launcher) {
|
||||||
|
return launcher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runWithShellPermission(Runnable action) {
|
||||||
|
getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
|
||||||
|
try {
|
||||||
|
action.run();
|
||||||
|
} finally {
|
||||||
|
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import static androidx.test.InstrumentationRegistry.getInstrumentation;
|
|||||||
import static com.android.quickstep.NavigationModeSwitchRule.Mode.ALL;
|
import static com.android.quickstep.NavigationModeSwitchRule.Mode.ALL;
|
||||||
import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON;
|
import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON;
|
||||||
import static com.android.quickstep.NavigationModeSwitchRule.Mode.TWO_BUTTON;
|
import static com.android.quickstep.NavigationModeSwitchRule.Mode.TWO_BUTTON;
|
||||||
|
import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
|
||||||
import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_2BUTTON_OVERLAY;
|
import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_2BUTTON_OVERLAY;
|
||||||
import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_3BUTTON_OVERLAY;
|
import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_3BUTTON_OVERLAY;
|
||||||
import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
|
import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
|
||||||
@@ -85,9 +86,9 @@ public class NavigationModeSwitchRule implements TestRule {
|
|||||||
final LauncherInstrumentation.NavigationModel originalMode =
|
final LauncherInstrumentation.NavigationModel originalMode =
|
||||||
mLauncher.getNavigationModel();
|
mLauncher.getNavigationModel();
|
||||||
try {
|
try {
|
||||||
// if (mode == ZERO_BUTTON || mode == ALL) {
|
if (mode == ZERO_BUTTON || mode == ALL) {
|
||||||
// evaluateWithZeroButtons();
|
evaluateWithZeroButtons();
|
||||||
// }
|
}
|
||||||
if (mode == TWO_BUTTON || mode == ALL) {
|
if (mode == TWO_BUTTON || mode == ALL) {
|
||||||
evaluateWithTwoButtons();
|
evaluateWithTwoButtons();
|
||||||
}
|
}
|
||||||
@@ -131,7 +132,10 @@ public class NavigationModeSwitchRule implements TestRule {
|
|||||||
overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
|
overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
|
||||||
|
|
||||||
for (int i = 0; i != 100; ++i) {
|
for (int i = 0; i != 100; ++i) {
|
||||||
if (mLauncher.getNavigationModel() == expectedMode) return;
|
if (mLauncher.getNavigationModel() == expectedMode) {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
Thread.sleep(100);
|
Thread.sleep(100);
|
||||||
}
|
}
|
||||||
Assert.fail("Couldn't switch to " + overlayPackage);
|
Assert.fail("Couldn't switch to " + overlayPackage);
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore // Ignoring until gestural navigation event sequence settles
|
@Ignore // Ignoring until race condition repro framework is changes for multi-process case.
|
||||||
@NavigationModeSwitch(mode = Mode.TWO_BUTTON)
|
@NavigationModeSwitch(mode = Mode.TWO_BUTTON)
|
||||||
public void testPressHome() {
|
public void testPressHome() {
|
||||||
runTest(enterEvt(Launcher.ON_CREATE_EVT),
|
runTest(enterEvt(Launcher.ON_CREATE_EVT),
|
||||||
@@ -75,14 +75,6 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
|
|||||||
exitEvt(Launcher.ON_CREATE_EVT));
|
exitEvt(Launcher.ON_CREATE_EVT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore // Ignoring until gestural navigation event sequence settles
|
|
||||||
@NavigationModeSwitch(mode = Mode.TWO_BUTTON)
|
|
||||||
public void testSwipeToOverview() {
|
|
||||||
closeLauncherActivity();
|
|
||||||
mLauncher.getBackground().switchToOverview();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@NavigationModeSwitch
|
@NavigationModeSwitch
|
||||||
public void testStressPressHome() {
|
public void testStressPressHome() {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.quickstep;
|
package com.android.quickstep;
|
||||||
|
|
||||||
|
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
@@ -60,7 +62,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void startTestApps() throws Exception {
|
private void startTestApps() throws Exception {
|
||||||
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_BROWSER));
|
startAppFast(getAppPackageName());
|
||||||
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
|
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
|
||||||
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS));
|
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS));
|
||||||
|
|
||||||
@@ -72,7 +74,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@PortraitLandscape
|
@PortraitLandscape
|
||||||
@Ignore
|
@Ignore // Enable after b/131115533
|
||||||
public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException {
|
public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException {
|
||||||
mDevice.pressRecentApps();
|
mDevice.pressRecentApps();
|
||||||
waitForState("Launcher internal state didn't switch to Overview", LauncherState.OVERVIEW);
|
waitForState("Launcher internal state didn't switch to Overview", LauncherState.OVERVIEW);
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
<!-- Copyright (C) 2017 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.
|
|
||||||
-->
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41z" />
|
|
||||||
</vector>
|
|
||||||
@@ -17,8 +17,11 @@
|
|||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0"
|
||||||
|
android:tint="?android:attr/textColorHint" >
|
||||||
<path
|
<path
|
||||||
android:pathData="M20,6h-4V4c0,-1.11 -0.89,-2 -2,-2h-4C8.89,2 8,2.89 8,4v2H4C2.89,6 2.01,6.89 2.01,8L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V8C22,6.89 21.11,6 20,6zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2s2,0.9 2,2S13.1,15 12,15zM14,6h-4V4h4V6z"
|
android:pathData="M20 6h-4V4c0-1.11-0.89-2-2-2h-4c-1.11 0-2 0.89-2 2v2H4c-1.11 0-1.99 0.89 -1.99
|
||||||
android:fillColor="?android:attr/textColorHint"/>
|
2L2 19c0 1.11 0.89 2 2 2h16c1.11 0 2-0.89 2-2V8c0-1.11-0.89-2-2-2zM10
|
||||||
|
4h4v2h-4V4zm10 15H4V8h16v11z"
|
||||||
|
android:fillColor="@android:color/white"/>
|
||||||
</vector>
|
</vector>
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
android:tint="?android:attr/textColorHint" >
|
android:tint="?android:attr/textColorHint" >
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:pathData="M19,9H5c-0.55,0-1,0.45-1,1l0,0c0,0.55,0.45,1,1,1h14c0.55,0,1-0.45,1-1l0,0C20,9.45,19.55,9,19,9z M5,
|
android:pathData="M20,9H4v2h16V9z M4,15h16v-2H4V15z"
|
||||||
15h14c0.55,0,1-0.45,1-1l0,0c0-0.55-0.45-1-1-1H5c-0.55,0-1,0.45-1,1l0,0C4,14.55,4.45,15,5,15z"
|
|
||||||
android:fillColor="@android:color/white" />
|
android:fillColor="@android:color/white" />
|
||||||
</vector>
|
</vector>
|
||||||
@@ -21,12 +21,12 @@
|
|||||||
android:tint="?android:attr/textColorPrimary">
|
android:tint="?android:attr/textColorPrimary">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFF"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M 11 7 H 13 V 9 H 11 V 7 Z" />
|
android:pathData="M 11 7 H 13 V 9 H 11 V 7 Z" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFF"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M 11 11 H 13 V 17 H 11 V 11 Z" />
|
android:pathData="M 11 11 H 13 V 17 H 11 V 11 Z" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFF"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M12,20c-4.41,0-8-3.59-8-8 c0-4.41,3.59-8,8-8s8,3.59,8,8C20,16.41,16.41,20,12,20z" />
|
android:pathData="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M12,20c-4.41,0-8-3.59-8-8 c0-4.41,3.59-8,8-8s8,3.59,8,8C20,16.41,16.41,20,12,20z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -17,11 +17,13 @@
|
|||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24"
|
||||||
|
android:tint="?android:attr/textColorPrimary">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="?android:attr/textColorPrimary"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" />
|
android:pathData="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z" />
|
||||||
<path
|
<path
|
||||||
android:pathData="M0 0h24v24H0z" />
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M 17 11.5 L 15.59 10.09 L 13 12.67 L 13 4 L 11 4 L 11 12.67 L 8.41 10.09 L 7 11.5 L 12 16.5 Z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?android:attr/textColorPrimary">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:pathData="M10,20 C4.49,20 0,15.51 0,10 C0,4.49 4.49,0 10,0 C15.51,0 20,4.04 20,9
|
||||||
|
C20,12.31 17.31,15 14,15 L12.23,15 C11.95,15 11.73,15.22 11.73,15.5 C11.73,15.62
|
||||||
|
11.78,15.73 11.86,15.83 C12.27,16.3 12.5,16.89 12.5,17.5 C12.5,18.88 11.38,20
|
||||||
|
10,20 Z M10,2 C5.59,2 2,5.59 2,10 C2,14.41 5.59,18 10,18 C10.28,18 10.5,17.78
|
||||||
|
10.5,17.5 C10.5,17.34 10.42,17.22 10.36,17.15 C9.95,16.69 9.73,16.1 9.73,15.5
|
||||||
|
C9.73,14.12 10.85,13 12.23,13 L14,13 C16.21,13 18,11.21 18,9 C18,5.14 14.41,2
|
||||||
|
10,2 Z M4.5,11 C5.32842712,11 6,10.3284271 6,9.5 C6,8.67157288 5.32842712,8
|
||||||
|
4.5,8 C3.67157288,8 3,8.67157288 3,9.5 C3,10.3284271 3.67157288,11 4.5,11 Z
|
||||||
|
M7.5,7 C8.32842712,7 9,6.32842712 9,5.5 C9,4.67157288 8.32842712,4 7.5,4
|
||||||
|
C6.67157288,4 6,4.67157288 6,5.5 C6,6.32842712 6.67157288,7 7.5,7 Z M12.5,7
|
||||||
|
C13.3284271,7 14,6.32842712 14,5.5 C14,4.67157288 13.3284271,4 12.5,4
|
||||||
|
C11.6715729,4 11,4.67157288 11,5.5 C11,6.32842712 11.6715729,7 12.5,7 Z M15.5,11
|
||||||
|
C16.3284271,11 17,10.3284271 17,9.5 C17,8.67157288 16.3284271,8 15.5,8
|
||||||
|
C14.6715729,8 14,8.67157288 14,9.5 C14,10.3284271 14.6715729,11 15.5,11 Z" />
|
||||||
|
</vector>
|
||||||
@@ -14,14 +14,12 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24.0"
|
android:viewportHeight="24.0"
|
||||||
android:viewportHeight="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:tint="?android:attr/textColorPrimary" >
|
android:tint="?android:attr/textColorPrimary">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M13.41,12l5.29-5.29c0.39-0.39,0.39-1.02,0-1.41c-0.39-0.39-1.02-0.39-1.41,0L12,10.59L6.71,
|
android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41z" />
|
||||||
5.29c-0.39-0.39-1.02-0.39-1.41,0c-0.39,0.39-0.39,1.02,0,1.41L10.59,12l-5.29,5.29c-0.39,0.39-0.39,1.02,
|
|
||||||
0,1.41c0.39,0.39,1.02,0.39,1.41,0L12,13.41l5.29,5.29c0.39,0.39,1.02,0.39,1.41,0c0.39-0.39,0.39-1.02,0-1.41L13.41,12z"/>
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Copyright (C) 2016 The Android Open Source Project
|
|||||||
android:viewportHeight="24.0"
|
android:viewportHeight="24.0"
|
||||||
android:tint="?android:attr/textColorPrimary" >
|
android:tint="?android:attr/textColorPrimary" >
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M13.85,22.25h-3.7c-0.74,0-1.36-0.54-1.45-1.27l-0.27-1.89c-0.27-0.14-0.53-0.29-0.79-0.46l-1.8,0.72
|
android:pathData="M13.85,22.25h-3.7c-0.74,0-1.36-0.54-1.45-1.27l-0.27-1.89c-0.27-0.14-0.53-0.29-0.79-0.46l-1.8,0.72
|
||||||
c-0.7,0.26-1.47-0.03-1.81-0.65L2.2,15.53c-0.35-0.66-0.2-1.44,0.36-1.88l1.53-1.19c-0.01-0.15-0.02-0.3-0.02-0.46
|
c-0.7,0.26-1.47-0.03-1.81-0.65L2.2,15.53c-0.35-0.66-0.2-1.44,0.36-1.88l1.53-1.19c-0.01-0.15-0.02-0.3-0.02-0.46
|
||||||
c0-0.15,0.01-0.31,0.02-0.46l-1.52-1.19C1.98,9.9,1.83,9.09,2.2,8.47l1.85-3.19c0.34-0.62,1.11-0.9,1.79-0.63l1.81,0.73
|
c0-0.15,0.01-0.31,0.02-0.46l-1.52-1.19C1.98,9.9,1.83,9.09,2.2,8.47l1.85-3.19c0.34-0.62,1.11-0.9,1.79-0.63l1.81,0.73
|
||||||
@@ -38,6 +38,6 @@ Copyright (C) 2016 The Android Open Source Project
|
|||||||
M18.22,6.27c0,0.01,0.01,0.02,0.01,0.02L18.22,6.27z M5.79,6.25L5.78,6.27C5.78,6.27,5.79,6.26,5.79,6.25z M13.31,3.28
|
M18.22,6.27c0,0.01,0.01,0.02,0.01,0.02L18.22,6.27z M5.79,6.25L5.78,6.27C5.78,6.27,5.79,6.26,5.79,6.25z M13.31,3.28
|
||||||
c0,0.01,0,0.01,0,0.02L13.31,3.28z M10.69,3.26l0,0.02C10.69,3.27,10.69,3.27,10.69,3.26z"/>
|
c0,0.01,0,0.01,0,0.02L13.31,3.28z M10.69,3.26l0,0.02C10.69,3.27,10.69,3.27,10.69,3.26z"/>
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M8.5,12a3.5,3.5 0 1,0 7,0a3.5,3.5 0 1,0 -7,0"/>
|
android:pathData="M8.5,12a3.5,3.5 0 1,0 7,0a3.5,3.5 0 1,0 -7,0"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -20,12 +20,12 @@
|
|||||||
android:viewportHeight="24.0"
|
android:viewportHeight="24.0"
|
||||||
android:tint="?android:attr/textColorPrimary" >
|
android:tint="?android:attr/textColorPrimary" >
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M15,4V3H9v1H4v2h1v13c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V6h1V4H15z M17,19H7V6h10V19z" />
|
android:pathData="M15,4V3H9v1H4v2h1v13c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V6h1V4H15z M17,19H7V6h10V19z" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M 9 8 H 11 V 17 H 9 V 8 Z" />
|
android:pathData="M 9 8 H 11 V 17 H 9 V 8 Z" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M 13 8 H 15 V 17 H 13 V 8 Z" />
|
android:pathData="M 13 8 H 15 V 17 H 13 V 8 Z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
Copyright (C) 2016 The Android Open Source Project
|
Copyright (C) 2016 The Android Open Source Project
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -17,9 +17,10 @@ Copyright (C) 2016 The Android Open Source Project
|
|||||||
android:width="@dimen/options_menu_icon_size"
|
android:width="@dimen/options_menu_icon_size"
|
||||||
android:height="@dimen/options_menu_icon_size"
|
android:height="@dimen/options_menu_icon_size"
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0"
|
||||||
|
android:tint="?android:attr/textColorPrimary">
|
||||||
<path
|
<path
|
||||||
android:fillColor="?android:attr/textColorPrimary"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M9,12.71l2.14,2.58l3-3.87L18,16.57H6L9,12.71z M5,5h6V3H5C3.9,3,3,3.9,3,5v6h2V5z M19,19h-6v2h6c1.1,0,2-0.9,2-2v-6h-2V19z
|
android:pathData="M9,12.71l2.14,2.58l3-3.87L18,16.57H6L9,12.71z M5,5h6V3H5C3.9,3,3,3.9,3,5v6h2V5z M19,19h-6v2h6c1.1,0,2-0.9,2-2v-6h-2V19z
|
||||||
M5,19v-6H3v6c0,1.1,0.9,2,2,2h6v-2H5z M19,5v6h2V5c0-1.1-0.9-2-2-2h-6v2H19z M16,9c0.55,0,1-0.45,1-1s-0.45-1-1-1
|
M5,19v-6H3v6c0,1.1,0.9,2,2,2h6v-2H5z M19,5v6h2V5c0-1.1-0.9-2-2-2h-6v2H19z M16,9c0.55,0,1-0.45,1-1s-0.45-1-1-1
|
||||||
c-0.55,0-1,0.45-1,1S15.45,9,16,9z"/>
|
c-0.55,0-1,0.45-1,1S15.45,9,16,9z"/>
|
||||||
|
|||||||
@@ -20,8 +20,12 @@
|
|||||||
android:viewportHeight="24.0"
|
android:viewportHeight="24.0"
|
||||||
android:tint="?android:attr/textColorPrimary" >
|
android:tint="?android:attr/textColorPrimary" >
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFFFF"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M22.85,19.39L12.93,2.25c-0.41-0.71-1.44-0.71-1.85,0L1.15,19.39C0.73,20.11,1.25,21,2.07,21h19.85
|
android:pathData="M12,5.99L19.53,19H4.47L12,5.99 M12,2L1,21h22L12,2L12,2z" />
|
||||||
C22.75,21,23.27,20.11,22.85,19.39z M11,10c0-0.55,0.45-1,1-1s1,0.45,1,1v3c0,0.55-0.45,1-1,1s-1-0.45-1-1V10z M12,18.2
|
<path
|
||||||
c-0.61,0-1.1-0.49-1.1-1.1S11.39,16,12,16s1.1,0.49,1.1,1.1C13.1,17.71,12.61,18.2,12,18.2z"/>
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M 13 16 L 11 16 L 11 18 L 13 18 L 13 16 Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M 13 10 L 11 10 L 11 14 L 13 14 L 13 10 Z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ Copyright (C) 2016 The Android Open Source Project
|
|||||||
android:width="@dimen/options_menu_icon_size"
|
android:width="@dimen/options_menu_icon_size"
|
||||||
android:height="@dimen/options_menu_icon_size"
|
android:height="@dimen/options_menu_icon_size"
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0"
|
||||||
|
android:tint="?android:attr/textColorPrimary">
|
||||||
<path
|
<path
|
||||||
android:fillColor="?android:attr/textColorPrimary"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M16.66,4.52l2.83,2.83l-2.83,2.83l-2.83-2.83L16.66,4.52 M9,5v4H5V5H9 M19,15v4h-4v-4H19 M9,15v4H5v-4H9 M16.66,1.69
|
android:pathData="M16.66,4.52l2.83,2.83l-2.83,2.83l-2.83-2.83L16.66,4.52 M9,5v4H5V5H9 M19,15v4h-4v-4H19 M9,15v4H5v-4H9 M16.66,1.69
|
||||||
L11,7.34L16.66,13l5.66-5.66L16.66,1.69L16.66,1.69z M11,3H3v8h8V7.34V3L11,3z M21,13h-4.34H13v8h8V13L21,13z M11,13H3v8h8V13L11,13z"/>
|
L11,7.34L16.66,13l5.66-5.66L16.66,1.69L16.66,1.69z M11,3H3v8h8V7.34V3L11,3z M21,13h-4.34H13v8h8V13L21,13z M11,13H3v8h8V13L11,13z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
android:clipChildren="true"
|
android:clipChildren="true"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:saveEnabled="false" >
|
android:saveEnabled="false">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
layout="@layout/all_apps_rv_layout"
|
layout="@layout/all_apps_rv_layout"
|
||||||
@@ -47,7 +47,8 @@
|
|||||||
android:layout_height="@dimen/all_apps_header_tab_height"
|
android:layout_height="@dimen/all_apps_header_tab_height"
|
||||||
android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
|
android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
|
||||||
android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
|
android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
style="@style/TextHeadline">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/tab_personal"
|
android:id="@+id/tab_personal"
|
||||||
@@ -55,7 +56,6 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:fontFamily="sans-serif-medium"
|
|
||||||
android:text="@string/all_apps_personal_tab"
|
android:text="@string/all_apps_personal_tab"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
android:textColor="@color/all_apps_tab_text"
|
android:textColor="@color/all_apps_tab_text"
|
||||||
@@ -67,7 +67,6 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:fontFamily="sans-serif-medium"
|
|
||||||
android:text="@string/all_apps_work_tab"
|
android:text="@string/all_apps_work_tab"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
android:textColor="@color/all_apps_tab_text"
|
android:textColor="@color/all_apps_tab_text"
|
||||||
|
|||||||
@@ -29,11 +29,10 @@
|
|||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
|
android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
|
||||||
android:paddingEnd="@dimen/popup_padding_end"
|
android:paddingEnd="@dimen/popup_padding_end"
|
||||||
android:drawableEnd="@drawable/deep_shortcuts_drag_handle"
|
android:drawableEnd="@drawable/ic_drag_handle"
|
||||||
android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
|
android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:fontFamily="sans-serif"
|
|
||||||
launcher:layoutHorizontal="true"
|
launcher:layoutHorizontal="true"
|
||||||
launcher:iconDisplay="shortcut_popup"
|
launcher:iconDisplay="shortcut_popup"
|
||||||
launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
|
launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user