Merging ub-launcher3-master, build 5356125
Test: Manual Bug: 112934365 Bug: 114136250 Bug: 118441555 Bug: 123900446 Bug: 123904290 Bug: 123906429 Bug: 124288578 Bug: 124620962 Bug: 124730625 Bug: 125364936 Bug: 125551024 Bug: 126045978 Bug: 126229665 Bug: 126289691 Bug: 126327184 Bug: 126336729 Bug: 126416861 Bug: 126767319 Bug: 127282292 Bug: 127345257 Bug: 127532177 Change-Id: I6f56221b8a7009d1f14ba47a0c63822d3f3ba06a
This commit is contained in:
+1
-1
@@ -320,7 +320,7 @@ LOCAL_RESOURCE_DIR := \
|
||||
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
|
||||
LOCAL_PROGUARD_ENABLED := full
|
||||
|
||||
LOCAL_PACKAGE_NAME := Launcher3QuickStepGoIconRecents
|
||||
LOCAL_PACKAGE_NAME := Launcher3GoIconRecents
|
||||
LOCAL_PRIVILEGED_MODULE := true
|
||||
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
|
||||
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
|
||||
|
||||
@@ -18,13 +18,11 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center">
|
||||
<!-- TODO(114136250): Remove this temporary placeholder view for Go recents -->
|
||||
<TextView
|
||||
android:orientation="vertical">
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/recent_task_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="Stub!"
|
||||
android:textSize="40sp"/>
|
||||
android:scrollbars="none"/>
|
||||
</com.android.quickstep.views.IconRecentsView>
|
||||
@@ -19,14 +19,12 @@ package com.android.launcher3.uioverrides;
|
||||
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.views.IconRecentsView;
|
||||
|
||||
/**
|
||||
* Definition for overview state
|
||||
@@ -49,6 +47,12 @@ public class OverviewState extends LauncherState {
|
||||
return new float[] {1f, 0f};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateEnabled(Launcher launcher) {
|
||||
IconRecentsView recentsView = launcher.getOverviewPanel();
|
||||
recentsView.onBeginTransitionToOverview();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
|
||||
return new PageAlphaProvider(DEACCEL_2) {
|
||||
|
||||
@@ -35,7 +35,8 @@ import java.util.ArrayList;
|
||||
* Provides recents-related {@link UiFactory} logic and classes.
|
||||
*/
|
||||
public abstract class RecentsUiFactory {
|
||||
|
||||
|
||||
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true;
|
||||
// Scale recents takes before animating in
|
||||
private static final float RECENTS_PREPARE_SCALE = 1.33f;
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView.Adapter;
|
||||
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
|
||||
* appropriate {@link Task} from the recents task list.
|
||||
*/
|
||||
public final class TaskAdapter extends Adapter<TaskHolder> {
|
||||
|
||||
private static final int MAX_TASKS_TO_DISPLAY = 6;
|
||||
private static final String TAG = "TaskAdapter";
|
||||
private final TaskListLoader mLoader;
|
||||
|
||||
public TaskAdapter(@NonNull TaskListLoader loader) {
|
||||
mLoader = loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
// TODO: Swap in an actual task view here (view w/ icon, label, etc.)
|
||||
TextView stubView = new TextView(parent.getContext());
|
||||
return new TaskHolder(stubView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(TaskHolder holder, int position) {
|
||||
ArrayList<Task> tasks = mLoader.getCurrentTaskList();
|
||||
if (position >= tasks.size()) {
|
||||
// Task list has updated.
|
||||
return;
|
||||
}
|
||||
holder.bindTask(tasks.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return Math.min(mLoader.getCurrentTaskList().size(), MAX_TASKS_TO_DISPLAY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
|
||||
/**
|
||||
* A recycler view holder that holds the task view and binds {@link Task} content (app title, icon,
|
||||
* etc.) to the view.
|
||||
*/
|
||||
final class TaskHolder extends ViewHolder {
|
||||
|
||||
// TODO: Implement the actual task view to be held.
|
||||
// For now, we just use a simple text view.
|
||||
private final TextView mStubView;
|
||||
|
||||
public TaskHolder(TextView stubView) {
|
||||
super(stubView);
|
||||
mStubView = stubView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind task content to the view. This includes the task icon and title as well as binding
|
||||
* input handlers such as which task to launch/remove.
|
||||
*
|
||||
* @param task the task to bind to the view this
|
||||
*/
|
||||
public void bindTask(Task task) {
|
||||
mStubView.setText("Stub task view: " + task.titleDescription);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* This class is responsible for maintaining the list of tasks and the task content. The list must
|
||||
* be updated explicitly with {@link #loadTaskList} whenever the list needs to be
|
||||
* up-to-date.
|
||||
*/
|
||||
public final class TaskListLoader {
|
||||
|
||||
private final RecentsModel mRecentsModel;
|
||||
|
||||
private ArrayList<Task> mTaskList = new ArrayList<>();
|
||||
private int mTaskListChangeId;
|
||||
|
||||
public TaskListLoader(Context context) {
|
||||
mRecentsModel = RecentsModel.INSTANCE.get(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current task list as of the last completed load (see
|
||||
* {@link #loadTaskList}). This list of tasks is guaranteed to always have all its task
|
||||
* content loaded.
|
||||
*
|
||||
* @return the current list of tasks w/ all content loaded
|
||||
*/
|
||||
public ArrayList<Task> getCurrentTaskList() {
|
||||
return mTaskList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the most recent tasks and updates the task list asynchronously. In addition it
|
||||
* loads the content for each task (icon and label). The callback and task list being updated
|
||||
* only occur when all task content is fully loaded and up-to-date.
|
||||
*
|
||||
* @param onTasksLoadedCallback callback for when the tasks are fully loaded. Done on the UI
|
||||
* thread
|
||||
*/
|
||||
public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onTasksLoadedCallback) {
|
||||
if (mRecentsModel.isTaskListValid(mTaskListChangeId)) {
|
||||
// Current task list is already up to date. No need to update.
|
||||
if (onTasksLoadedCallback != null) {
|
||||
onTasksLoadedCallback.accept(mTaskList);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// TODO: Look into error checking / more robust handling for when things go wrong.
|
||||
mTaskListChangeId = mRecentsModel.getTasks(tasks -> {
|
||||
// Reverse tasks to put most recent at the bottom of the view
|
||||
Collections.reverse(tasks);
|
||||
// Load task content
|
||||
loadTaskContents(tasks, () -> {
|
||||
mTaskList = tasks;
|
||||
if (onTasksLoadedCallback != null) {
|
||||
onTasksLoadedCallback.accept(mTaskList);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads task content for a list of tasks, including the label and the icon. Uses the list of
|
||||
* tasks since the last load as a cache for loaded content.
|
||||
*
|
||||
* @param tasksToLoad list of tasks that need to load their content
|
||||
* @param onLoadedCallback runnable to run after all tasks have loaded their content
|
||||
*/
|
||||
private void loadTaskContents(ArrayList<Task> tasksToLoad,
|
||||
@Nullable Runnable onLoadedCallback) {
|
||||
AtomicInteger loadRequestsCount = new AtomicInteger(0);
|
||||
for (Task task : tasksToLoad) {
|
||||
int index = mTaskList.indexOf(task);
|
||||
if (index >= 0) {
|
||||
// If we've already loaded the task and have its content then just copy it over.
|
||||
Task loadedTask = mTaskList.get(index);
|
||||
task.titleDescription = loadedTask.titleDescription;
|
||||
task.icon = loadedTask.icon;
|
||||
} else {
|
||||
// Otherwise, load the content in the background.
|
||||
loadRequestsCount.getAndIncrement();
|
||||
mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> {
|
||||
if (loadRequestsCount.decrementAndGet() == 0 && onLoadedCallback != null) {
|
||||
onLoadedCallback.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (loadRequestsCount.get() == 0 && onLoadedCallback != null) {
|
||||
onLoadedCallback.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,24 @@
|
||||
*/
|
||||
package com.android.quickstep.views;
|
||||
|
||||
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.ViewDebug;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.quickstep.TaskAdapter;
|
||||
import com.android.quickstep.TaskListLoader;
|
||||
|
||||
/**
|
||||
* Root view for the icon recents view.
|
||||
* Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code
|
||||
* base.
|
||||
*/
|
||||
public final class IconRecentsView extends FrameLayout {
|
||||
|
||||
@@ -45,6 +55,11 @@ public final class IconRecentsView extends FrameLayout {
|
||||
@Override
|
||||
public void setValue(IconRecentsView view, float v) {
|
||||
ALPHA.set(view, v);
|
||||
if (view.getVisibility() != VISIBLE && v > 0) {
|
||||
view.setVisibility(VISIBLE);
|
||||
} else if (view.getVisibility() != GONE && v == 0){
|
||||
view.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,10 +73,44 @@ public final class IconRecentsView extends FrameLayout {
|
||||
* is top aligned and 0.5 is centered vertically.
|
||||
*/
|
||||
@ViewDebug.ExportedProperty(category = "launcher")
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private float mTranslationYFactor;
|
||||
private TaskAdapter mTaskAdapter;
|
||||
private RecyclerView mTaskRecyclerView;
|
||||
private TaskListLoader mTaskLoader;
|
||||
|
||||
public IconRecentsView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mTaskLoader = new TaskListLoader(mContext);
|
||||
mTaskAdapter = new TaskAdapter(mTaskLoader);
|
||||
mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
|
||||
mTaskRecyclerView.setAdapter(mTaskAdapter);
|
||||
mTaskRecyclerView.setLayoutManager(
|
||||
new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic for when we know we are going to overview/recents and will be putting up the recents
|
||||
* view. This should be used to prepare recents (e.g. load any task data, etc.) before it
|
||||
* becomes visible.
|
||||
*
|
||||
* TODO: Hook this up for fallback recents activity as well
|
||||
*/
|
||||
public void onBeginTransitionToOverview() {
|
||||
// Load any task changes
|
||||
mTaskLoader.loadTaskList(tasks -> {
|
||||
// TODO: Put up some loading UI while task content is loading. May have to do something
|
||||
// smarter when animating from app to overview.
|
||||
mTaskAdapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
public void setTranslationYFactor(float translationFactor) {
|
||||
|
||||
@@ -48,7 +48,7 @@ public class BaseIconFactory implements AutoCloseable {
|
||||
private ShadowGenerator mShadowGenerator;
|
||||
|
||||
private Drawable mWrapperIcon;
|
||||
private int mWrapperBackgroundColor;
|
||||
private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
|
||||
|
||||
protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
|
||||
mContext = context.getApplicationContext();
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
|
||||
<service
|
||||
android:name="com.android.quickstep.TouchInteractionService"
|
||||
android:permission="android.permission.STATUS_BAR_SERVICE" >
|
||||
android:permission="android.permission.STATUS_BAR_SERVICE"
|
||||
android:directBootAware="true" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.QUICKSTEP_SERVICE" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2019 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<com.android.quickstep.hints.HintView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/chip_hint_height"
|
||||
android:layout_gravity="center_horizontal|bottom"
|
||||
android:paddingStart="@dimen/chip_hint_start_padding"
|
||||
android:paddingEnd="@dimen/chip_hint_end_padding"
|
||||
android:background="@drawable/chip_hint_background_light"
|
||||
android:gravity="center"
|
||||
android:layout_marginHorizontal="@dimen/chip_hint_horizontal_margin"
|
||||
android:orientation="horizontal"
|
||||
android:elevation="@dimen/chip_hint_elevation"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="@dimen/chip_icon_size"
|
||||
android:layout_height="@dimen/chip_icon_size"
|
||||
android:visibility="gone"
|
||||
android:scaleType="fitCenter"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@null"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/chip_text_height"
|
||||
android:paddingTop="@dimen/chip_text_top_padding"
|
||||
android:paddingStart="@dimen/chip_text_start_padding"
|
||||
android:fontFamily="google-sans-medium"
|
||||
android:textAlignment="textStart"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/chip_hint_foreground_color"
|
||||
android:textSize="@dimen/chip_text_size"
|
||||
android:ellipsize="none"
|
||||
android:includeFontPadding="true"/>
|
||||
|
||||
|
||||
</com.android.quickstep.hints.HintView>
|
||||
@@ -14,9 +14,10 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<com.android.quickstep.hints.HintsContainer
|
||||
<com.android.quickstep.hints.ChipsContainer
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="@dimen/chip_hint_height"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="horizontal"/>
|
||||
+1
-1
@@ -44,6 +44,7 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public abstract class RecentsUiFactory {
|
||||
|
||||
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
|
||||
private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
|
||||
WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
|
||||
|
||||
@@ -61,7 +62,6 @@ public abstract class RecentsUiFactory {
|
||||
|
||||
if (swipeUpToHome) {
|
||||
list.add(new FlingAndHoldTouchController(launcher));
|
||||
list.add(new OverviewToAllAppsTouchController(launcher));
|
||||
} else {
|
||||
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
|
||||
list.add(new OverviewToAllAppsTouchController(launcher));
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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.MotionEvent.ACTION_CANCEL;
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_MOVE;
|
||||
import static android.view.MotionEvent.ACTION_POINTER_UP;
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowManager;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
import com.android.systemui.shared.system.NavigationBarCompat;
|
||||
import com.android.systemui.shared.system.WindowManagerWrapper;
|
||||
import com.android.launcher3.R;
|
||||
|
||||
/**
|
||||
* Touch consumer for handling events to launch assistant from launcher
|
||||
*/
|
||||
public class AssistantTouchConsumer implements InputConsumer {
|
||||
private static final String TAG = "AssistantTouchConsumer";
|
||||
|
||||
private final PointF mDownPos = new PointF();
|
||||
private final PointF mLastPos = new PointF();
|
||||
private int mActivePointerId = -1;
|
||||
|
||||
private final int mDisplayRotation;
|
||||
private final Rect mStableInsets = new Rect();
|
||||
|
||||
private final float mDragSlop;
|
||||
private final float mTouchSlop;
|
||||
private final float mThreshold;
|
||||
|
||||
private float mStartDisplacement;
|
||||
private boolean mPassedDragSlop;
|
||||
private boolean mPassedTouchSlop;
|
||||
private long mPassedTouchSlopTime;
|
||||
private boolean mLaunchedAssistant;
|
||||
private float mLastProgress;
|
||||
|
||||
private final ISystemUiProxy mSysUiProxy;
|
||||
|
||||
public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy) {
|
||||
mSysUiProxy = systemUiProxy;
|
||||
|
||||
mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
|
||||
mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
|
||||
mThreshold = context.getResources().getDimension(R.dimen.gestures_assistant_threshold);
|
||||
|
||||
Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
|
||||
mDisplayRotation = display.getRotation();
|
||||
WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return TYPE_ASSISTANT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMotionEvent(MotionEvent ev) {
|
||||
// TODO add logging
|
||||
switch (ev.getActionMasked()) {
|
||||
case ACTION_DOWN: {
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
mDownPos.set(ev.getX(), ev.getY());
|
||||
mLastPos.set(mDownPos);
|
||||
mLastProgress = -1;
|
||||
break;
|
||||
}
|
||||
case ACTION_POINTER_UP: {
|
||||
int ptrIdx = ev.getActionIndex();
|
||||
int ptrId = ev.getPointerId(ptrIdx);
|
||||
if (ptrId == mActivePointerId) {
|
||||
final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
|
||||
mDownPos.set(
|
||||
ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
|
||||
ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
|
||||
mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
|
||||
mActivePointerId = ev.getPointerId(newPointerIdx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_MOVE: {
|
||||
int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex == -1) {
|
||||
break;
|
||||
}
|
||||
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
|
||||
float displacement = getDisplacement(ev);
|
||||
|
||||
if (!mPassedDragSlop) {
|
||||
// Normal gesture, ensure we pass the drag slop before we start tracking
|
||||
// the gesture
|
||||
if (Math.abs(displacement) > mDragSlop) {
|
||||
mPassedDragSlop = true;
|
||||
mStartDisplacement = displacement;
|
||||
mPassedTouchSlopTime = SystemClock.uptimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
if (!mPassedTouchSlop) {
|
||||
if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) >=
|
||||
mTouchSlop) {
|
||||
mPassedTouchSlop = true;
|
||||
if (!mPassedDragSlop) {
|
||||
mPassedDragSlop = true;
|
||||
mStartDisplacement = displacement;
|
||||
mPassedTouchSlopTime = SystemClock.uptimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mPassedDragSlop) {
|
||||
// Move
|
||||
float distance = mStartDisplacement - displacement;
|
||||
if (distance >= 0) {
|
||||
onAssistantProgress(distance / mThreshold);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_CANCEL:
|
||||
break;
|
||||
case ACTION_UP: {
|
||||
if (ev.getEventTime() - mPassedTouchSlopTime < ViewConfiguration.getTapTimeout()) {
|
||||
onAssistantProgress(1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onAssistantProgress(float progress) {
|
||||
if (mLastProgress == progress) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mSysUiProxy.onAssistantProgress(Math.max(0, Math.min(1, progress)));
|
||||
if (progress >= 1 && !mLaunchedAssistant) {
|
||||
mSysUiProxy.startAssistant(new Bundle());
|
||||
mLaunchedAssistant = true;
|
||||
}
|
||||
mLastProgress = progress;
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to notify SysUI to start/send assistant progress: " + progress, e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNavBarOnRight() {
|
||||
return mDisplayRotation == Surface.ROTATION_90 && mStableInsets.right > 0;
|
||||
}
|
||||
|
||||
private boolean isNavBarOnLeft() {
|
||||
return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
|
||||
}
|
||||
|
||||
private float getDisplacement(MotionEvent ev) {
|
||||
float eventX = ev.getX();
|
||||
float eventY = ev.getY();
|
||||
float displacement = eventY - mDownPos.y;
|
||||
if (isNavBarOnRight()) {
|
||||
displacement = eventX - mDownPos.x;
|
||||
} else if (isNavBarOnLeft()) {
|
||||
displacement = mDownPos.x - eventX;
|
||||
}
|
||||
return displacement;
|
||||
}
|
||||
|
||||
static boolean withinTouchRegion(Context context, float x) {
|
||||
return x > context.getResources().getDisplayMetrics().widthPixels
|
||||
- context.getResources().getDimension(R.dimen.gestures_assistant_width);
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
/**
|
||||
* A dummy input consumer used when the device is still locked, e.g. from secure camera.
|
||||
*/
|
||||
public class DeviceLockedInputConsumer implements InputConsumer {
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public DeviceLockedInputConsumer(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return TYPE_DEVICE_LOCKED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMotionEvent(MotionEvent ev) {
|
||||
// For now, just start the home intent so user is prompted to unlock the device.
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mContext.startActivity(new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,16 @@ import android.view.MotionEvent;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public interface InputConsumer {
|
||||
InputConsumer NO_OP = new InputConsumer() { };
|
||||
|
||||
int TYPE_NO_OP = 0;
|
||||
int TYPE_OVERVIEW = 1;
|
||||
int TYPE_OTHER_ACTIVITY = 2;
|
||||
int TYPE_ASSISTANT = 3;
|
||||
int TYPE_DEVICE_LOCKED = 4;
|
||||
|
||||
InputConsumer NO_OP = () -> TYPE_NO_OP;
|
||||
|
||||
int getType();
|
||||
|
||||
default boolean isActive() {
|
||||
return false;
|
||||
|
||||
+12
-10
@@ -100,17 +100,19 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
|
||||
public HomeAnimationFactory prepareHomeUI(Launcher activity) {
|
||||
final DeviceProfile dp = activity.getDeviceProfile();
|
||||
final RecentsView recentsView = activity.getOverviewPanel();
|
||||
final ComponentName component = recentsView.getRunningTaskView().getTask().key
|
||||
.sourceComponent;
|
||||
|
||||
final View workspaceView = activity.getWorkspace().getFirstMatchForAppClose(component);
|
||||
final FloatingIconView floatingView = workspaceView == null ? null
|
||||
: new FloatingIconView(activity);
|
||||
final Rect iconLocation = new Rect();
|
||||
if (floatingView != null) {
|
||||
floatingView.matchPositionOf(activity, workspaceView, true /* hideOriginal */,
|
||||
iconLocation);
|
||||
final TaskView runningTaskView = recentsView.getRunningTaskView();
|
||||
final View workspaceView;
|
||||
if (runningTaskView != null) {
|
||||
ComponentName component = runningTaskView.getTask().key.sourceComponent;
|
||||
workspaceView = activity.getWorkspace().getFirstMatchForAppClose(component);
|
||||
} else {
|
||||
workspaceView = null;
|
||||
}
|
||||
final Rect iconLocation = new Rect();
|
||||
final FloatingIconView floatingView = workspaceView == null ? null
|
||||
: FloatingIconView.getFloatingIconView(activity, workspaceView,
|
||||
true /* hideOriginal */, false /* useDrawableAsIs */,
|
||||
activity.getDeviceProfile().getAspectRatioWithInsets(), iconLocation, null);
|
||||
|
||||
return new HomeAnimationFactory() {
|
||||
@Nullable
|
||||
|
||||
+23
-6
@@ -36,6 +36,8 @@ import android.content.Intent;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.Display;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
@@ -105,6 +107,12 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
// TODO: Start displacement should have both x and y
|
||||
private float mStartDisplacement;
|
||||
|
||||
private Handler mMainThreadHandler;
|
||||
private Runnable mCancelRecentsAnimationRunnable = () -> {
|
||||
ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
|
||||
true /* restoreHomeStackPosition */);
|
||||
};
|
||||
|
||||
public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo,
|
||||
RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
|
||||
boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
|
||||
@@ -113,6 +121,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
SwipeSharedState swipeSharedState) {
|
||||
super(base);
|
||||
|
||||
mMainThreadHandler = new Handler(Looper.getMainLooper());
|
||||
mRunningTask = runningTaskInfo;
|
||||
mRecentsModel = recentsModel;
|
||||
mHomeIntent = homeIntent;
|
||||
@@ -138,6 +147,11 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
mPassedTouchSlop = mPassedDragSlop = mSwipeSharedState.getActiveListener() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return TYPE_OTHER_ACTIVITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMotionEvent(MotionEvent ev) {
|
||||
if (mVelocityTracker == null) {
|
||||
@@ -216,7 +230,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
mTouchSlop) {
|
||||
mPassedTouchSlop = true;
|
||||
|
||||
TOUCH_INTERACTION_LOG.startQuickStep();
|
||||
TOUCH_INTERACTION_LOG.addLog("startQuickstep");
|
||||
if (mIsDeferredDownTarget) {
|
||||
// Deferred gesture, start the animation and gesture tracking once
|
||||
// we pass the actual touch slop
|
||||
@@ -279,7 +293,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
}
|
||||
|
||||
private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
|
||||
TOUCH_INTERACTION_LOG.startRecentsAnimation();
|
||||
TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation");
|
||||
|
||||
RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
|
||||
final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
|
||||
@@ -328,10 +342,12 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
onConsumerAboutToBeSwitched();
|
||||
onInteractionGestureFinished();
|
||||
|
||||
// Also clean up in case the system has handled the UP and canceled the animation before
|
||||
// we had a chance to start the recents animation. In such a case, we will not receive
|
||||
ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
|
||||
true /* restoreHomeStackPosition */);
|
||||
// Cancel the recents animation if SysUI happens to handle UP before we have a chance
|
||||
// to start the recents animation. In addition, workaround for b/126336729 by delaying
|
||||
// the cancel of the animation for a period, in case SysUI is slow to handle UP and we
|
||||
// handle DOWN & UP and move the home stack before SysUI can start the activity
|
||||
mMainThreadHandler.removeCallbacks(mCancelRecentsAnimationRunnable);
|
||||
mMainThreadHandler.postDelayed(mCancelRecentsAnimationRunnable, 100);
|
||||
}
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
@@ -341,6 +357,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
@Override
|
||||
public void onConsumerAboutToBeSwitched() {
|
||||
Preconditions.assertUIThread();
|
||||
mMainThreadHandler.removeCallbacks(mCancelRecentsAnimationRunnable);
|
||||
if (mInteractionHandler != null) {
|
||||
// The consumer is being switched while we are active. Set up the shared state to be
|
||||
// used by the next animation
|
||||
|
||||
@@ -59,6 +59,11 @@ public class OverviewInputConsumer<T extends BaseDraggingActivity>
|
||||
mStartingInActivityBounds = startingInActivityBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return TYPE_OVERVIEW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMotionEvent(MotionEvent ev) {
|
||||
if (mInvalidated) {
|
||||
@@ -120,7 +125,7 @@ public class OverviewInputConsumer<T extends BaseDraggingActivity>
|
||||
OverviewCallbacks.get(mActivity).closeAllWindows();
|
||||
ActivityManagerWrapper.getInstance()
|
||||
.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
|
||||
TOUCH_INTERACTION_LOG.startQuickStep();
|
||||
TOUCH_INTERACTION_LOG.addLog("startQuickstep");
|
||||
}
|
||||
|
||||
mTrackingStarted = true;
|
||||
@@ -152,4 +157,4 @@ public class OverviewInputConsumer<T extends BaseDraggingActivity>
|
||||
}
|
||||
return new OverviewInputConsumer(activity, startingInActivityBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +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.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Keeps track of debugging logs for a particular quickstep gesture.
|
||||
*/
|
||||
public class TouchInteractionLog {
|
||||
|
||||
// The number of gestures to log
|
||||
private static final int MAX_NUM_LOG_GESTURES = 5;
|
||||
|
||||
private final Calendar mCalendar = Calendar.getInstance();
|
||||
private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MMM dd - kk:mm:ss:SSS");
|
||||
private final LinkedList<ArrayList<String>> mGestureLogs = new LinkedList<>();
|
||||
|
||||
public void prepareForNewGesture() {
|
||||
mGestureLogs.add(new ArrayList<>());
|
||||
while (mGestureLogs.size() > MAX_NUM_LOG_GESTURES) {
|
||||
mGestureLogs.pop();
|
||||
}
|
||||
getCurrentLog().add("[" + mDateFormat.format(mCalendar.getTime()) + "]");
|
||||
}
|
||||
|
||||
public void setInputConsumer(InputConsumer consumer) {
|
||||
getCurrentLog().add("tc=" + consumer.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
public void addMotionEvent(MotionEvent event) {
|
||||
getCurrentLog().add("ev=" + event.getActionMasked());
|
||||
}
|
||||
|
||||
public void startQuickStep() {
|
||||
getCurrentLog().add("qstStart");
|
||||
}
|
||||
|
||||
public void startRecentsAnimation() {
|
||||
getCurrentLog().add("raStart");
|
||||
}
|
||||
|
||||
public void startRecentsAnimationCallback(int numTargets) {
|
||||
getCurrentLog().add("raStartCb=" + numTargets);
|
||||
}
|
||||
|
||||
public void cancelRecentsAnimation() {
|
||||
getCurrentLog().add("raCancel");
|
||||
}
|
||||
|
||||
public void finishRecentsAnimation(boolean toHome) {
|
||||
getCurrentLog().add("raFinish=" + toHome);
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.println("TouchInteractionLog {");
|
||||
for (ArrayList<String> gesture : mGestureLogs) {
|
||||
pw.print(" ");
|
||||
for (String log : gesture) {
|
||||
pw.print(log + " ");
|
||||
}
|
||||
pw.println();
|
||||
}
|
||||
pw.println("}");
|
||||
}
|
||||
|
||||
private ArrayList<String> getCurrentLog() {
|
||||
return mGestureLogs.getLast();
|
||||
}
|
||||
}
|
||||
+86
-15
@@ -23,13 +23,18 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYS
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.Region;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.Choreographer;
|
||||
@@ -37,6 +42,10 @@ import android.view.InputEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.compat.UserManagerCompat;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.logging.EventLogArray;
|
||||
import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.launcher3.util.UiThreadHelper;
|
||||
import com.android.systemui.shared.recents.IOverviewProxy;
|
||||
@@ -49,6 +58,8 @@ import com.android.systemui.shared.system.InputConsumerController;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Service connected by system-UI for handling touch interaction.
|
||||
@@ -60,7 +71,8 @@ public class TouchInteractionService extends Service {
|
||||
public static final LooperExecutor BACKGROUND_EXECUTOR =
|
||||
new LooperExecutor(UiThreadHelper.getBackgroundLooper());
|
||||
|
||||
public static final TouchInteractionLog TOUCH_INTERACTION_LOG = new TouchInteractionLog();
|
||||
public static final EventLogArray TOUCH_INTERACTION_LOG =
|
||||
new EventLogArray("touch_interaction_log", 40);
|
||||
|
||||
public static final int EDGE_NAV_BAR = 1 << 8;
|
||||
|
||||
@@ -75,8 +87,10 @@ public class TouchInteractionService extends Service {
|
||||
public void onInitialize(Bundle bundle) {
|
||||
mISystemUiProxy = ISystemUiProxy.Stub
|
||||
.asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
|
||||
mRecentsModel.setSystemUiProxy(mISystemUiProxy);
|
||||
mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
|
||||
runWhenUserUnlocked(() -> {
|
||||
mRecentsModel.setSystemUiProxy(mISystemUiProxy);
|
||||
mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
|
||||
});
|
||||
|
||||
disposeEventHandlers();
|
||||
mInputEventReceiver = InputChannelCompat.fromBundle(bundle, KEY_EXTRA_INPUT_CHANNEL,
|
||||
@@ -128,8 +142,10 @@ public class TouchInteractionService extends Service {
|
||||
|
||||
public void onBind(ISystemUiProxy iSystemUiProxy) {
|
||||
mISystemUiProxy = iSystemUiProxy;
|
||||
mRecentsModel.setSystemUiProxy(mISystemUiProxy);
|
||||
mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
|
||||
runWhenUserUnlocked(() -> {
|
||||
mRecentsModel.setSystemUiProxy(mISystemUiProxy);
|
||||
mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
|
||||
});
|
||||
|
||||
// On Bind is received before onInitialize which will dispose these handlers
|
||||
disposeEventHandlers();
|
||||
@@ -138,7 +154,6 @@ public class TouchInteractionService extends Service {
|
||||
TouchInteractionService.this::onInputEvent);
|
||||
mDeprecatedDispatcher = pair.first;
|
||||
mInputEventReceiver = pair.second;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,6 +163,7 @@ public class TouchInteractionService extends Service {
|
||||
return sConnected;
|
||||
}
|
||||
|
||||
private KeyguardManager mKM;
|
||||
private ActivityManagerWrapper mAM;
|
||||
private RecentsModel mRecentsModel;
|
||||
private ISystemUiProxy mISystemUiProxy;
|
||||
@@ -159,6 +175,17 @@ public class TouchInteractionService extends Service {
|
||||
private InputConsumerController mInputConsumer;
|
||||
private SwipeSharedState mSwipeSharedState;
|
||||
|
||||
private boolean mIsUserUnlocked;
|
||||
private List<Runnable> mOnUserUnlockedCallbacks;
|
||||
private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
|
||||
initWhenUserUnlocked();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private InputConsumer mConsumer = InputConsumer.NO_OP;
|
||||
private Choreographer mMainChoreographer;
|
||||
|
||||
@@ -170,10 +197,29 @@ public class TouchInteractionService extends Service {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// Initialize anything here that is needed in direct boot mode.
|
||||
// Everything else should be initialized in initWhenUserUnlocked() below.
|
||||
mKM = getSystemService(KeyguardManager.class);
|
||||
mMainChoreographer = Choreographer.getInstance();
|
||||
mOnUserUnlockedCallbacks = new ArrayList<>();
|
||||
|
||||
if (UserManagerCompat.getInstance(this).isUserUnlocked(Process.myUserHandle())) {
|
||||
initWhenUserUnlocked();
|
||||
} else {
|
||||
mIsUserUnlocked = false;
|
||||
registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
|
||||
}
|
||||
|
||||
sConnected = true;
|
||||
}
|
||||
|
||||
private void initWhenUserUnlocked() {
|
||||
mIsUserUnlocked = true;
|
||||
|
||||
mAM = ActivityManagerWrapper.getInstance();
|
||||
mRecentsModel = RecentsModel.INSTANCE.get(this);
|
||||
mOverviewComponentObserver = new OverviewComponentObserver(this);
|
||||
mMainChoreographer = Choreographer.getInstance();
|
||||
|
||||
mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver);
|
||||
mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
|
||||
@@ -183,18 +229,34 @@ public class TouchInteractionService extends Service {
|
||||
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
|
||||
mInputConsumer.registerInputConsumer();
|
||||
|
||||
sConnected = true;
|
||||
for (Runnable callback : mOnUserUnlockedCallbacks) {
|
||||
callback.run();
|
||||
}
|
||||
mOnUserUnlockedCallbacks.clear();
|
||||
|
||||
// Temporarily disable model preload
|
||||
// new ModelPreload().start(this);
|
||||
|
||||
Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
|
||||
}
|
||||
|
||||
private void runWhenUserUnlocked(Runnable callback) {
|
||||
if (mIsUserUnlocked) {
|
||||
callback.run();
|
||||
} else {
|
||||
mOnUserUnlockedCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mInputConsumer.unregisterInputConsumer();
|
||||
mOverviewComponentObserver.onDestroy();
|
||||
if (mIsUserUnlocked) {
|
||||
mInputConsumer.unregisterInputConsumer();
|
||||
mOverviewComponentObserver.onDestroy();
|
||||
}
|
||||
disposeEventHandlers();
|
||||
sConnected = false;
|
||||
Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -221,19 +283,24 @@ public class TouchInteractionService extends Service {
|
||||
return;
|
||||
}
|
||||
MotionEvent event = (MotionEvent) ev;
|
||||
TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
|
||||
if (event.getAction() == ACTION_DOWN) {
|
||||
TOUCH_INTERACTION_LOG.prepareForNewGesture();
|
||||
boolean useSharedState = mConsumer.isActive();
|
||||
mConsumer.onConsumerAboutToBeSwitched();
|
||||
mConsumer = newConsumer(useSharedState, event);
|
||||
TOUCH_INTERACTION_LOG.setInputConsumer(mConsumer);
|
||||
TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
|
||||
}
|
||||
TOUCH_INTERACTION_LOG.addMotionEvent(event);
|
||||
|
||||
mConsumer.onMotionEvent(event);
|
||||
}
|
||||
|
||||
private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
|
||||
// TODO: this makes a binder call every touch down. we should move to a listener pattern.
|
||||
if (mKM.isDeviceLocked()) {
|
||||
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched
|
||||
// while device is locked even after exiting direct boot mode (e.g. camera).
|
||||
return new DeviceLockedInputConsumer(this);
|
||||
}
|
||||
|
||||
RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
|
||||
if (!useSharedState) {
|
||||
mSwipeSharedState.clearAllState();
|
||||
@@ -241,6 +308,10 @@ public class TouchInteractionService extends Service {
|
||||
|
||||
if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
|
||||
return InputConsumer.NO_OP;
|
||||
} else if (mOverviewInteractionState.isSwipeUpGestureEnabled()
|
||||
&& FeatureFlags.ENABLE_ASSISTANT_GESTURE.get()
|
||||
&& AssistantTouchConsumer.withinTouchRegion(this, event.getX())) {
|
||||
return new AssistantTouchConsumer(this, mRecentsModel.getSystemUiProxy());
|
||||
} else if (mSwipeSharedState.goingToLauncher ||
|
||||
mOverviewComponentObserver.getActivityControlHelper().isResumed()) {
|
||||
return OverviewInputConsumer.newInstance(
|
||||
@@ -271,6 +342,6 @@ public class TouchInteractionService extends Service {
|
||||
|
||||
@Override
|
||||
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
TOUCH_INTERACTION_LOG.dump(pw);
|
||||
TOUCH_INTERACTION_LOG.dump("", pw);
|
||||
}
|
||||
}
|
||||
|
||||
+22
-13
@@ -671,7 +671,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
initTransitionEndpoints(dp);
|
||||
|
||||
mRecentsAnimationWrapper.setController(targetSet);
|
||||
TOUCH_INTERACTION_LOG.startRecentsAnimationCallback(targetSet.apps.length);
|
||||
TOUCH_INTERACTION_LOG.addLog("startRecentsAnimationCallback", targetSet.apps.length);
|
||||
setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
|
||||
|
||||
mPassedOverviewThreshold = false;
|
||||
@@ -682,7 +682,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
mRecentsAnimationWrapper.setController(null);
|
||||
mActivityInitListener.unregister();
|
||||
setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
|
||||
TOUCH_INTERACTION_LOG.cancelRecentsAnimation();
|
||||
TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation");
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@@ -972,27 +972,36 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
final RectF currentRect = new RectF();
|
||||
|
||||
final View floatingView = homeAnimationFactory.getFloatingView();
|
||||
final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
|
||||
|
||||
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
|
||||
if (floatingView instanceof FloatingIconView) {
|
||||
if (isFloatingIconView) {
|
||||
anim.addListener((FloatingIconView) floatingView);
|
||||
}
|
||||
|
||||
// We want the window alpha to be 0 once this threshold is met, so that the
|
||||
// FolderIconView can be seen morphing into the icon shape.
|
||||
final float windowAlphaThreshold = isFloatingIconView ? 0.75f : 1f;
|
||||
anim.addUpdateListener(animation -> {
|
||||
float progress = animation.getAnimatedFraction();
|
||||
float interpolatedProgress = Interpolators.ACCEL_2.getInterpolation(progress);
|
||||
float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress);
|
||||
// Initially go towards original target (task view in recents),
|
||||
// but accelerate towards the final target.
|
||||
// TODO: This is technically not correct. Instead, motion should continue at
|
||||
// the released velocity but accelerate towards the target.
|
||||
targetRect.set(rectFEvaluator.evaluate(interpolatedProgress,
|
||||
originalTarget, finalTarget));
|
||||
currentRect.set(rectFEvaluator.evaluate(progress, startRect, targetRect));
|
||||
float alpha = 1 - interpolatedProgress;
|
||||
mTransformParams.setCurrentRectAndTargetAlpha(currentRect, alpha)
|
||||
currentRect.set(rectFEvaluator.evaluate(interpolatedProgress, startRect, targetRect));
|
||||
|
||||
float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0,
|
||||
windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR);
|
||||
mTransformParams.setCurrentRectAndTargetAlpha(currentRect, 1f - iconAlpha)
|
||||
.setSyncTransactionApplier(mSyncTransactionApplier);
|
||||
mClipAnimationHelper.applyTransform(targetSet, mTransformParams);
|
||||
|
||||
if (floatingView instanceof FloatingIconView) {
|
||||
((FloatingIconView) floatingView).update(currentRect, 1f - alpha);
|
||||
if (isFloatingIconView) {
|
||||
((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress,
|
||||
windowAlphaThreshold);
|
||||
}
|
||||
});
|
||||
anim.addListener(new AnimationSuccessListener() {
|
||||
@@ -1016,7 +1025,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
@UiThread
|
||||
private void resumeLastTask() {
|
||||
mRecentsAnimationWrapper.finish(false /* toRecents */, null);
|
||||
TOUCH_INTERACTION_LOG.finishRecentsAnimation(false);
|
||||
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@@ -1034,7 +1043,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
mMainThreadHandler);
|
||||
});
|
||||
}
|
||||
TOUCH_INTERACTION_LOG.finishRecentsAnimation(false);
|
||||
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
|
||||
doLogGesture(NEW_TASK);
|
||||
}
|
||||
|
||||
@@ -1143,7 +1152,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
() -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
|
||||
}
|
||||
}
|
||||
TOUCH_INTERACTION_LOG.finishRecentsAnimation(true);
|
||||
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
|
||||
}
|
||||
|
||||
private void finishCurrentTransitionToHome() {
|
||||
@@ -1151,7 +1160,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
mRecentsAnimationWrapper.finish(true /* toRecents */,
|
||||
() -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
|
||||
}
|
||||
TOUCH_INTERACTION_LOG.finishRecentsAnimation(true);
|
||||
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
|
||||
doLogGesture(HOME);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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,218 +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 static com.android.quickstep.hints.UiHintListenerConstants.HINTS_KEY;
|
||||
import static com.android.quickstep.hints.UiHintListenerConstants.ON_HINTS_RETURNED_CODE;
|
||||
import static com.android.quickstep.hints.UiInterfaceConstants.REQUEST_HINTS_CODE;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class HintsContainer extends LinearLayout {
|
||||
|
||||
private static final String TAG = "HintsView";
|
||||
|
||||
public static final FloatProperty<HintsContainer> HINT_VISIBILITY =
|
||||
new FloatProperty<HintsContainer>("hint_visibility") {
|
||||
@Override
|
||||
public void setValue(HintsContainer hintsContainer, float v) {
|
||||
hintsContainer.setHintVisibility(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(HintsContainer hintsContainer) {
|
||||
return hintsContainer.mHintVisibility;
|
||||
}
|
||||
};
|
||||
|
||||
private static Intent mServiceIntent =
|
||||
new Intent("com.android.systemui.action.UI_PULL_INTERFACE")
|
||||
.setClassName(
|
||||
"com.android.systemui.navbarhint",
|
||||
"com.android.systemui.navbarhint.service.HintService");
|
||||
|
||||
@Nullable
|
||||
private Messenger mHintServiceInterface;
|
||||
private UiHintListener mUiHintListener;
|
||||
private boolean mBound = false;
|
||||
private float mHintVisibility;
|
||||
|
||||
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||
mHintServiceInterface = new Messenger(iBinder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
mHintServiceInterface = null;
|
||||
attemptBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindingDied(ComponentName componentName) {
|
||||
mHintServiceInterface = null;
|
||||
attemptBinding();
|
||||
}
|
||||
};
|
||||
|
||||
public HintsContainer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public HintsContainer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public HintsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public HintsContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
if (mUiHintListener == null) {
|
||||
mUiHintListener = new UiHintListener(this);
|
||||
}
|
||||
if (!mBound) {
|
||||
attemptBinding();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
if (mBound) {
|
||||
getContext().unbindService(mServiceConnection);
|
||||
mBound = false;
|
||||
}
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
public void setHintVisibility(float v) {
|
||||
if (v == 1) {
|
||||
getHints();
|
||||
setVisibility(VISIBLE);
|
||||
} else {
|
||||
setVisibility(GONE);
|
||||
}
|
||||
mHintVisibility = v;
|
||||
}
|
||||
|
||||
private void attemptBinding() {
|
||||
if (mBound) {
|
||||
getContext().unbindService(mServiceConnection);
|
||||
mBound = false;
|
||||
}
|
||||
boolean success = getContext().bindService(mServiceIntent,
|
||||
mServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
|
||||
if (success) {
|
||||
mBound = true;
|
||||
} else {
|
||||
Log.w(TAG, "Binding to hint supplier failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void sendOnHintTap(Bundle hint) {
|
||||
if (mHintServiceInterface != null) {
|
||||
Message msg = Message.obtain(null, UiInterfaceConstants.ON_HINT_TAP_CODE);
|
||||
Bundle data = new Bundle();
|
||||
data.putString(UiInterfaceConstants.HINT_ID_KEY, HintUtil.getId(hint));
|
||||
data.putInt(UiInterfaceConstants.WIDTH_PX_KEY, getWidth());
|
||||
data.putInt(UiInterfaceConstants.HEIGHT_PX_KEY, getHeight());
|
||||
data.putInt(UiInterfaceConstants.HINT_SPACE_WIDTH_PX_KEY, 0);
|
||||
data.putInt(UiInterfaceConstants.HINT_SPACE_HEIGHT_PX_KEY, 0);
|
||||
msg.setData(data);
|
||||
try {
|
||||
mHintServiceInterface.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to send hint tap", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getHints() {
|
||||
if (mHintServiceInterface != null) {
|
||||
try {
|
||||
Message m = Message.obtain(null, REQUEST_HINTS_CODE);
|
||||
m.replyTo = new Messenger(mUiHintListener);
|
||||
mHintServiceInterface.send(m);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to send message", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class UiHintListener extends Handler {
|
||||
private HintsContainer mView;
|
||||
|
||||
UiHintListener(HintsContainer v) {
|
||||
mView = v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case ON_HINTS_RETURNED_CODE:
|
||||
handleHints(msg);
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "UiPullInterface got unrecognized code: " + msg.what);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHints(Message msg) {
|
||||
Bundle bundle = msg.getData();
|
||||
ArrayList<Bundle> hints = bundle.getParcelableArrayList(HINTS_KEY);
|
||||
|
||||
if (hints != null) {
|
||||
mView.removeAllViews();
|
||||
|
||||
for (Bundle hint : hints) {
|
||||
HintView h = (HintView) LayoutInflater.from(mView.getContext()).inflate(
|
||||
R.layout.hint, mView, false);
|
||||
h.setHint(hint);
|
||||
h.setOnClickListener((v) -> mView.sendOnHintTap(hint));
|
||||
mView.addView(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,6 +185,7 @@ public class ClipAnimationHelper {
|
||||
if (mSupportsRoundedCornersOnWindows) {
|
||||
cornerRadius = Utilities.mapRange(params.progress, mWindowCornerRadius,
|
||||
mTaskCornerRadius);
|
||||
mCurrentCornerRadius = cornerRadius;
|
||||
}
|
||||
}
|
||||
alpha = mTaskAlphaCallback.apply(app, params.targetAlpha);
|
||||
|
||||
+7
-2
@@ -50,6 +50,7 @@ import java.util.Locale;
|
||||
|
||||
public final class DigitalWellBeingToast extends LinearLayout {
|
||||
static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
|
||||
static final int MINUTE_MS = 60000;
|
||||
|
||||
public interface InitializeCallback {
|
||||
void call(float saturation, String contentDescription);
|
||||
@@ -185,7 +186,11 @@ public final class DigitalWellBeingToast extends LinearLayout {
|
||||
/* forceFormatWidth= */ false);
|
||||
}
|
||||
|
||||
private String getShorterReadableDuration(Duration duration) {
|
||||
private String getRoundedUpToMinuteReadableDuration(long remainingTime) {
|
||||
final Duration duration = Duration.ofMillis(
|
||||
remainingTime > MINUTE_MS ?
|
||||
(remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS :
|
||||
remainingTime);
|
||||
return getReadableDuration(
|
||||
duration, FormatWidth.NARROW, R.string.shorter_duration_less_than_one_minute);
|
||||
}
|
||||
@@ -196,7 +201,7 @@ public final class DigitalWellBeingToast extends LinearLayout {
|
||||
resources.getString(R.string.app_in_grayscale) :
|
||||
resources.getString(
|
||||
R.string.time_left_for_app,
|
||||
getShorterReadableDuration(Duration.ofMillis(remainingTime)));
|
||||
getRoundedUpToMinuteReadableDuration(remainingTime));
|
||||
}
|
||||
|
||||
public void openAppUsageSettings() {
|
||||
|
||||
+15
-9
@@ -42,9 +42,10 @@ import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.util.PendingAnimation;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
import com.android.launcher3.views.ScrimView;
|
||||
import com.android.quickstep.OverviewInteractionState;
|
||||
import com.android.quickstep.hints.HintsContainer;
|
||||
import com.android.quickstep.hints.ChipsContainer;
|
||||
import com.android.quickstep.util.ClipAnimationHelper;
|
||||
import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
|
||||
import com.android.quickstep.util.LayoutUtils;
|
||||
@@ -77,7 +78,7 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
||||
private float mTranslationYFactor;
|
||||
|
||||
private final TransformParams mTransformParams = new TransformParams();
|
||||
private HintsContainer mHintsContainer;
|
||||
private ChipsContainer mChipsContainer;
|
||||
|
||||
public LauncherRecentsView(Context context) {
|
||||
this(context, null);
|
||||
@@ -111,8 +112,9 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
mHintsContainer = mActivity.findViewById(R.id.hints);
|
||||
mHintsContainer.setPadding(0, 0, 0, mActivity.getDeviceProfile().chipHintBottomMarginPx);
|
||||
mChipsContainer = mActivity.findViewById(R.id.hints);
|
||||
BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) mChipsContainer.getLayoutParams();
|
||||
params.bottomMargin = mActivity.getDeviceProfile().chipHintBottomMarginPx;
|
||||
}
|
||||
|
||||
public void setTranslationYFactor(float translationFactor) {
|
||||
@@ -131,11 +133,15 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
||||
}
|
||||
|
||||
public void setHintVisibility(float v) {
|
||||
if (mHintsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) {
|
||||
mHintsContainer.setHintVisibility(v);
|
||||
if (mChipsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) {
|
||||
mChipsContainer.setHintVisibility(v);
|
||||
}
|
||||
}
|
||||
|
||||
public ChipsContainer getChipsContainer() {
|
||||
return mChipsContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
maybeDrawEmptyMessage(canvas);
|
||||
@@ -192,7 +198,7 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
||||
|
||||
if (ENABLE_HINTS_IN_OVERVIEW.get()) {
|
||||
anim.anim.play(ObjectAnimator.ofFloat(
|
||||
mHintsContainer, HintsContainer.HINT_VISIBILITY, 0));
|
||||
mChipsContainer, ChipsContainer.HINT_VISIBILITY, 0));
|
||||
}
|
||||
|
||||
return anim;
|
||||
@@ -206,10 +212,10 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
||||
|
||||
if (ENABLE_HINTS_IN_OVERVIEW.get()) {
|
||||
anim.anim.play(ObjectAnimator.ofFloat(
|
||||
mHintsContainer, HintsContainer.HINT_VISIBILITY, 0));
|
||||
mChipsContainer, ChipsContainer.HINT_VISIBILITY, 0));
|
||||
anim.addEndListener(onEndListener -> {
|
||||
if (!onEndListener.isSuccess) {
|
||||
mHintsContainer.setHintVisibility(1);
|
||||
mChipsContainer.setHintVisibility(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ import com.android.launcher3.util.ViewPool;
|
||||
import com.android.quickstep.OverviewCallbacks;
|
||||
import com.android.quickstep.RecentsAnimationWrapper;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
|
||||
import com.android.quickstep.TaskThumbnailCache;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.util.ClipAnimationHelper;
|
||||
@@ -113,7 +114,7 @@ import java.util.function.Consumer;
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
|
||||
TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
|
||||
InvariantDeviceProfile.OnIDPChangeListener {
|
||||
InvariantDeviceProfile.OnIDPChangeListener, TaskThumbnailChangeListener {
|
||||
|
||||
private static final String TAG = RecentsView.class.getSimpleName();
|
||||
|
||||
@@ -170,14 +171,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
* TODO: Call reloadIdNeeded in onTaskStackChanged.
|
||||
*/
|
||||
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
|
||||
@Override
|
||||
public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
|
||||
if (!mHandleTaskStackChanges) {
|
||||
return;
|
||||
}
|
||||
updateThumbnail(taskId, snapshot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
|
||||
if (!mHandleTaskStackChanges) {
|
||||
@@ -262,7 +255,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
|
||||
private boolean mOverviewStateEnabled;
|
||||
private boolean mHandleTaskStackChanges;
|
||||
private Runnable mNextPageSwitchRunnable;
|
||||
private boolean mSwipeDownShouldLaunchApp;
|
||||
private boolean mTouchDownToStartHome;
|
||||
private final int mTouchSlop;
|
||||
@@ -340,6 +332,19 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
return mIsRtl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
|
||||
if (mHandleTaskStackChanges) {
|
||||
TaskView taskView = getTaskView(taskId);
|
||||
if (taskView != null) {
|
||||
Task task = taskView.getTask();
|
||||
taskView.getThumbnail().setThumbnail(task, thumbnailData);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
|
||||
TaskView taskView = getTaskView(taskId);
|
||||
if (taskView != null) {
|
||||
@@ -371,6 +376,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
|
||||
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
|
||||
mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this);
|
||||
RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
|
||||
mIdp.addOnChangeListener(this);
|
||||
}
|
||||
|
||||
@@ -382,6 +388,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
|
||||
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
|
||||
mSyncTransactionApplier = null;
|
||||
RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
|
||||
mIdp.removeOnChangeListener(this);
|
||||
}
|
||||
|
||||
@@ -421,17 +428,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
updateTaskStackListenerState();
|
||||
}
|
||||
|
||||
public void setNextPageSwitchRunnable(Runnable r) {
|
||||
mNextPageSwitchRunnable = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPageEndTransition() {
|
||||
super.onPageEndTransition();
|
||||
if (mNextPageSwitchRunnable != null) {
|
||||
mNextPageSwitchRunnable.run();
|
||||
mNextPageSwitchRunnable = null;
|
||||
}
|
||||
if (getNextPage() > 0) {
|
||||
setSwipeDownShouldLaunchApp(true);
|
||||
}
|
||||
@@ -774,7 +773,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
setCurrentTask(runningTaskId);
|
||||
}
|
||||
|
||||
public TaskView getRunningTaskView() {
|
||||
public @Nullable TaskView getRunningTaskView() {
|
||||
return getTaskView(mRunningTaskId);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.quickstep.views;
|
||||
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static com.android.launcher3.BaseActivity.fromContext;
|
||||
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
|
||||
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
|
||||
@@ -28,7 +29,6 @@ import android.animation.ObjectAnimator;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -91,7 +91,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
|
||||
public static final long SCALE_ICON_DURATION = 120;
|
||||
private static final long DIM_ANIM_DURATION = 700;
|
||||
private static final long TASK_LAUNCH_ANIM_DURATION = 200;
|
||||
|
||||
public static final Property<TaskView, Float> ZOOM_SCALE =
|
||||
new FloatProperty<TaskView>("zoomScale") {
|
||||
@@ -214,6 +213,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
* Updates this task view to the given {@param task}.
|
||||
*/
|
||||
public void bind(Task task) {
|
||||
cancelPendingLoadTasks();
|
||||
mTask = task;
|
||||
mSnapshotView.bind(task);
|
||||
}
|
||||
@@ -236,10 +236,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
|
||||
public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
|
||||
final PendingAnimation pendingAnimation =
|
||||
getRecentsView().createTaskLauncherAnimation(this, TASK_LAUNCH_ANIM_DURATION);
|
||||
pendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
|
||||
getRecentsView().createTaskLauncherAnimation(this, RECENTS_LAUNCH_DURATION);
|
||||
pendingAnimation.anim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
|
||||
AnimatorPlaybackController currentAnimation = AnimatorPlaybackController
|
||||
.wrap(pendingAnimation.anim, TASK_LAUNCH_ANIM_DURATION, null);
|
||||
.wrap(pendingAnimation.anim, RECENTS_LAUNCH_DURATION, null);
|
||||
currentAnimation.setEndAction(() -> {
|
||||
pendingAnimation.finish(true, Touch.SWIPE);
|
||||
launchTask(false);
|
||||
@@ -304,15 +304,15 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
if (mTask == null) {
|
||||
return;
|
||||
}
|
||||
cancelPendingLoadTasks();
|
||||
if (visible) {
|
||||
// These calls are no-ops if the data is already loaded, try and load the high
|
||||
// resolution thumbnail if the state permits
|
||||
RecentsModel model = RecentsModel.INSTANCE.get(getContext());
|
||||
TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
|
||||
TaskIconCache iconCache = model.getIconCache();
|
||||
mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(mTask,
|
||||
!thumbnailCache.getHighResLoadingState().isEnabled() /* reducedResolution */,
|
||||
(task) -> mSnapshotView.setThumbnail(task, task.thumbnail));
|
||||
mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
|
||||
mTask, thumbnail -> mSnapshotView.setThumbnail(mTask, thumbnail));
|
||||
mIconLoadRequest = iconCache.updateIconInBackground(mTask,
|
||||
(task) -> {
|
||||
setIcon(task.icon);
|
||||
@@ -324,17 +324,22 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
if (mThumbnailLoadRequest != null) {
|
||||
mThumbnailLoadRequest.cancel();
|
||||
}
|
||||
if (mIconLoadRequest != null) {
|
||||
mIconLoadRequest.cancel();
|
||||
}
|
||||
mSnapshotView.setThumbnail(null, null);
|
||||
setIcon(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelPendingLoadTasks() {
|
||||
if (mThumbnailLoadRequest != null) {
|
||||
mThumbnailLoadRequest.cancel();
|
||||
mThumbnailLoadRequest = null;
|
||||
}
|
||||
if (mIconLoadRequest != null) {
|
||||
mIconLoadRequest.cancel();
|
||||
mIconLoadRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean showTaskMenu() {
|
||||
getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
|
||||
mMenuView = TaskMenuView.showForTask(this);
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
<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_min_displacement">48dp</dimen>
|
||||
<dimen name="motion_pause_detector_max_orthogonal_displacement">48dp</dimen>
|
||||
|
||||
<!-- Launcher app transition -->
|
||||
<dimen name="content_trans_y">50dp</dimen>
|
||||
@@ -65,4 +64,8 @@
|
||||
<dimen name="shelf_surface_radius">16dp</dimen>
|
||||
<!-- same as vertical_drag_handle_size -->
|
||||
<dimen name="shelf_surface_offset">24dp</dimen>
|
||||
|
||||
<!-- Assistant Gestures -->
|
||||
<dimen name="gestures_assistant_width">70dp</dimen>
|
||||
<dimen name="gestures_assistant_threshold">200dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -403,9 +403,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
|
||||
private void playIconAnimators(AnimatorSet appOpenAnimator, View v, Rect windowTargetBounds,
|
||||
boolean toggleVisibility) {
|
||||
final boolean isBubbleTextView = v instanceof BubbleTextView;
|
||||
if (mFloatingView == null) {
|
||||
mFloatingView = new FloatingIconView(mLauncher);
|
||||
} else {
|
||||
if (mFloatingView != null) {
|
||||
mFloatingView.setTranslationX(0);
|
||||
mFloatingView.setTranslationY(0);
|
||||
mFloatingView.setScaleX(1);
|
||||
@@ -414,7 +412,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
|
||||
mFloatingView.setBackground(null);
|
||||
}
|
||||
Rect rect = new Rect();
|
||||
mFloatingView.matchPositionOf(mLauncher, v, toggleVisibility, rect);
|
||||
mFloatingView = FloatingIconView.getFloatingIconView(mLauncher, v, toggleVisibility,
|
||||
true /* useDrawableAsIs */, -1 /* aspectRatio */, rect, mFloatingView);
|
||||
|
||||
int viewLocationStart = mIsRtl ? windowTargetBounds.width() - rect.right : rect.left;
|
||||
LayoutParams lp = (LayoutParams) mFloatingView.getLayoutParams();
|
||||
@@ -615,11 +614,6 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
|
||||
WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
|
||||
new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(false /* fromUnlock */),
|
||||
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
|
||||
|
||||
definition.addRemoteAnimation(
|
||||
WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
|
||||
new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(true /* fromUnlock */),
|
||||
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
|
||||
new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ public class OverviewInteractionState {
|
||||
}
|
||||
}
|
||||
|
||||
public void notifySwipeUpSettingChanged(boolean swipeUpEnabled) {
|
||||
private void notifySwipeUpSettingChanged(boolean swipeUpEnabled) {
|
||||
mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
|
||||
mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, swipeUpEnabled ? 1 : 0, 0).
|
||||
sendToTarget();
|
||||
|
||||
@@ -60,6 +60,7 @@ public class RecentTasksList extends TaskStackChangeListener {
|
||||
mBgThreadExecutor = BackgroundExecutor.get();
|
||||
mKeyguardManager = new KeyguardManagerCompat(context);
|
||||
mChangeId = 1;
|
||||
ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,6 +98,7 @@ public class RecentTasksList extends TaskStackChangeListener {
|
||||
if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
|
||||
// The list is up to date, callback with the same list
|
||||
mMainThreadExecutor.execute(resultCallback);
|
||||
return requestLoadId;
|
||||
}
|
||||
|
||||
// Kick off task loading in the background
|
||||
|
||||
@@ -15,33 +15,29 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
|
||||
|
||||
/**
|
||||
* Singleton class to load and manage recents model.
|
||||
*/
|
||||
@@ -54,8 +50,8 @@ public class RecentsModel extends TaskStackChangeListener {
|
||||
public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
|
||||
new MainThreadInitializedObject<>(c -> new RecentsModel(c));
|
||||
|
||||
private final List<TaskThumbnailChangeListener> mThumbnailChangeListeners = new ArrayList<>();
|
||||
private final Context mContext;
|
||||
private final MainThreadExecutor mMainThreadExecutor;
|
||||
|
||||
private ISystemUiProxy mSystemUiProxy;
|
||||
|
||||
@@ -68,9 +64,6 @@ public class RecentsModel extends TaskStackChangeListener {
|
||||
|
||||
private RecentsModel(Context context) {
|
||||
mContext = context;
|
||||
|
||||
mMainThreadExecutor = new MainThreadExecutor();
|
||||
|
||||
HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache",
|
||||
Process.THREAD_PRIORITY_BACKGROUND);
|
||||
loaderThread.start();
|
||||
@@ -168,6 +161,18 @@ public class RecentsModel extends TaskStackChangeListener {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
|
||||
mThumbnailCache.updateTaskSnapShot(taskId, snapshot);
|
||||
|
||||
for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {
|
||||
Task task = mThumbnailChangeListeners.get(i).onTaskThumbnailChanged(taskId, snapshot);
|
||||
if (task != null) {
|
||||
task.thumbnail = snapshot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
|
||||
mSystemUiProxy = systemUiProxy;
|
||||
}
|
||||
@@ -239,4 +244,17 @@ public class RecentsModel extends TaskStackChangeListener {
|
||||
+ ": ", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) {
|
||||
mThumbnailChangeListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeThumbnailChangeListener(TaskThumbnailChangeListener listener) {
|
||||
mThumbnailChangeListeners.remove(listener);
|
||||
}
|
||||
|
||||
public interface TaskThumbnailChangeListener {
|
||||
|
||||
Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.launcher3.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
@@ -24,14 +26,17 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.LruCache;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.icons.cache.HandlerRunnable;
|
||||
import com.android.launcher3.uioverrides.RecentsUiFactory;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -125,8 +130,9 @@ public class TaskIconCache {
|
||||
return label;
|
||||
}
|
||||
|
||||
// Skip loading content descriptions if accessibility is not enabled
|
||||
if (!mAccessibilityManager.isEnabled()) {
|
||||
// Skip loading content descriptions if accessibility is disabled unless low RAM recents
|
||||
// is enabled.
|
||||
if (!GO_LOW_RAM_RECENTS_ENABLED && !mAccessibilityManager.isEnabled()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.icons.cache.HandlerRunnable;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.Task.TaskKey;
|
||||
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
@@ -38,7 +39,7 @@ public class TaskThumbnailCache {
|
||||
private final MainThreadExecutor mMainThreadExecutor;
|
||||
|
||||
private final int mCacheSize;
|
||||
private final TaskKeyLruCache<ThumbnailData> mCache;
|
||||
private final ThumbnailCache mCache;
|
||||
private final HighResLoadingState mHighResLoadingState;
|
||||
|
||||
public static class HighResLoadingState {
|
||||
@@ -98,7 +99,7 @@ public class TaskThumbnailCache {
|
||||
|
||||
Resources res = context.getResources();
|
||||
mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize);
|
||||
mCache = new TaskKeyLruCache<>(mCacheSize);
|
||||
mCache = new ThumbnailCache(mCacheSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,13 +107,20 @@ public class TaskThumbnailCache {
|
||||
*/
|
||||
public void updateThumbnailInCache(Task task) {
|
||||
Preconditions.assertUIThread();
|
||||
|
||||
// Fetch the thumbnail for this task and put it in the cache
|
||||
updateThumbnailInBackground(task, true /* reducedResolution */, (t) -> {
|
||||
mCache.put(task.key, t.thumbnail);
|
||||
});
|
||||
if (task.thumbnail == null) {
|
||||
updateThumbnailInBackground(task.key, true /* reducedResolution */,
|
||||
t -> task.thumbnail = t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously updates the thumbnail in the cache if it is already there.
|
||||
*/
|
||||
public void updateTaskSnapShot(int taskId, ThumbnailData thumbnail) {
|
||||
Preconditions.assertUIThread();
|
||||
mCache.updateIfAlreadyInCache(taskId, thumbnail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously fetches the icon and other task data for the given {@param task}.
|
||||
@@ -120,22 +128,33 @@ public class TaskThumbnailCache {
|
||||
* @param callback The callback to receive the task after its data has been populated.
|
||||
* @return A cancelable handle to the request
|
||||
*/
|
||||
public ThumbnailLoadRequest updateThumbnailInBackground(Task task, boolean reducedResolution,
|
||||
Consumer<Task> callback) {
|
||||
public ThumbnailLoadRequest updateThumbnailInBackground(
|
||||
Task task, Consumer<ThumbnailData> callback) {
|
||||
Preconditions.assertUIThread();
|
||||
|
||||
boolean reducedResolution = !mHighResLoadingState.isEnabled();
|
||||
if (task.thumbnail != null && (!task.thumbnail.reducedResolution || reducedResolution)) {
|
||||
// Nothing to load, the thumbnail is already high-resolution or matches what the
|
||||
// request, so just callback
|
||||
callback.accept(task);
|
||||
callback.accept(task.thumbnail);
|
||||
return null;
|
||||
}
|
||||
|
||||
ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(task.key);
|
||||
|
||||
return updateThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), t -> {
|
||||
task.thumbnail = t;
|
||||
callback.accept(t);
|
||||
});
|
||||
}
|
||||
|
||||
private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean reducedResolution,
|
||||
Consumer<ThumbnailData> callback) {
|
||||
Preconditions.assertUIThread();
|
||||
|
||||
ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
|
||||
if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || reducedResolution)) {
|
||||
// Already cached, lets use that thumbnail
|
||||
task.thumbnail = cachedThumbnail;
|
||||
callback.accept(task);
|
||||
callback.accept(cachedThumbnail);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -144,14 +163,14 @@ public class TaskThumbnailCache {
|
||||
@Override
|
||||
public void run() {
|
||||
ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(
|
||||
task.key.id, reducedResolution);
|
||||
key.id, reducedResolution);
|
||||
if (isCanceled()) {
|
||||
// We don't call back to the provided callback in this case
|
||||
return;
|
||||
}
|
||||
mMainThreadExecutor.execute(() -> {
|
||||
task.thumbnail = thumbnail;
|
||||
callback.accept(task);
|
||||
mCache.put(key, thumbnail);
|
||||
callback.accept(thumbnail);
|
||||
onEnd();
|
||||
});
|
||||
}
|
||||
@@ -196,4 +215,21 @@ public class TaskThumbnailCache {
|
||||
this.reducedResolution = reducedResolution;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ThumbnailCache extends TaskKeyLruCache<ThumbnailData> {
|
||||
|
||||
public ThumbnailCache(int cacheSize) {
|
||||
super(cacheSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cache entry if it is already present in the cache
|
||||
*/
|
||||
public void updateIfAlreadyInCache(int taskId, ThumbnailData thumbnailData) {
|
||||
ThumbnailData oldData = getCacheEntry(taskId);
|
||||
if (oldData != null) {
|
||||
putCacheEntry(taskId, thumbnailData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ public class MotionPauseDetector {
|
||||
private final float mSpeedSomewhatFast;
|
||||
private final float mSpeedFast;
|
||||
private final float mMinDisplacementForPause;
|
||||
private final float mMaxOrthogonalDisplacementForPause;
|
||||
private final Alarm mForcePauseTimeout;
|
||||
|
||||
private Long mPreviousTime = null;
|
||||
@@ -62,8 +61,6 @@ public class MotionPauseDetector {
|
||||
mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
|
||||
mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
|
||||
mMinDisplacementForPause = res.getDimension(R.dimen.motion_pause_detector_min_displacement);
|
||||
mMaxOrthogonalDisplacementForPause = res.getDimension(
|
||||
R.dimen.motion_pause_detector_max_orthogonal_displacement);
|
||||
mForcePauseTimeout = new Alarm();
|
||||
mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
|
||||
}
|
||||
@@ -77,7 +74,6 @@ public class MotionPauseDetector {
|
||||
if (mOnMotionPauseListener != null) {
|
||||
mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
|
||||
}
|
||||
mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,6 +90,7 @@ public class MotionPauseDetector {
|
||||
if (mFirstOrthogonalPosition == null) {
|
||||
mFirstOrthogonalPosition = orthogonalPosition;
|
||||
}
|
||||
mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
|
||||
long time = SystemClock.uptimeMillis();
|
||||
if (mPreviousTime != null && mPreviousPosition != null) {
|
||||
long changeInTime = Math.max(1, time - mPreviousTime);
|
||||
@@ -108,7 +105,6 @@ public class MotionPauseDetector {
|
||||
}
|
||||
mPreviousTime = time;
|
||||
mPreviousPosition = position;
|
||||
mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
|
||||
}
|
||||
|
||||
private void checkMotionPaused(float velocity, float prevVelocity,
|
||||
@@ -135,9 +131,11 @@ public class MotionPauseDetector {
|
||||
}
|
||||
}
|
||||
boolean passedMinDisplacement = totalDisplacement.primary >= mMinDisplacementForPause;
|
||||
boolean passedMaxOrthogonalDisplacement =
|
||||
totalDisplacement.orthogonal >= mMaxOrthogonalDisplacementForPause;
|
||||
isPaused &= passedMinDisplacement && !passedMaxOrthogonalDisplacement;
|
||||
boolean isDisplacementOrthogonal = totalDisplacement.orthogonal > totalDisplacement.primary;
|
||||
if (!passedMinDisplacement || isDisplacementOrthogonal) {
|
||||
mForcePauseTimeout.cancelAlarm();
|
||||
isPaused = false;
|
||||
}
|
||||
updatePaused(isPaused);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ package com.android.quickstep;
|
||||
import static com.android.quickstep.QuickStepOnOffRule.Mode.BOTH;
|
||||
import static com.android.quickstep.QuickStepOnOffRule.Mode.OFF;
|
||||
import static com.android.quickstep.QuickStepOnOffRule.Mode.ON;
|
||||
import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
@@ -69,38 +74,52 @@ public class QuickStepOnOffRule implements TestRule {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
try {
|
||||
if (mode == ON || mode == BOTH) {
|
||||
evaluateWithQuickstepOn();
|
||||
}
|
||||
if (mode == OFF || mode == BOTH) {
|
||||
evaluateWithQuickstepOff();
|
||||
if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
|
||||
if (mode == ON || mode == BOTH) {
|
||||
evaluateWithQuickstepOn();
|
||||
}
|
||||
if (mode == OFF || mode == BOTH) {
|
||||
evaluateWithQuickstepOff();
|
||||
}
|
||||
} else {
|
||||
// Execute without changing the setting, if the requested mode is
|
||||
// compatible.
|
||||
final boolean swipeUpEnabledDefaultValue =
|
||||
SwipeUpSetting.isSwipeUpEnabledDefaultValue();
|
||||
if (mode == BOTH ||
|
||||
mode == ON && swipeUpEnabledDefaultValue ||
|
||||
mode == OFF && !swipeUpEnabledDefaultValue) {
|
||||
evaluateWithoutChangingSetting(base);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
overrideSwipeUpEnabled(null);
|
||||
setSwipeUpSetting(null);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void evaluateWithQuickstepOff() throws Throwable {
|
||||
overrideSwipeUpEnabled(false);
|
||||
public void setSwipeUpSetting(String value) {
|
||||
assertTrue("Couldn't change Quickstep mode",
|
||||
Settings.Secure.putString(
|
||||
InstrumentationRegistry.getInstrumentation().getTargetContext().
|
||||
getContentResolver(),
|
||||
SWIPE_UP_SETTING_NAME,
|
||||
value));
|
||||
}
|
||||
|
||||
public void evaluateWithoutChangingSetting(Statement base) throws Throwable {
|
||||
base.evaluate();
|
||||
}
|
||||
|
||||
private void evaluateWithQuickstepOff() throws Throwable {
|
||||
setSwipeUpSetting("0");
|
||||
evaluateWithoutChangingSetting(base);
|
||||
}
|
||||
|
||||
private void evaluateWithQuickstepOn() throws Throwable {
|
||||
overrideSwipeUpEnabled(true);
|
||||
setSwipeUpSetting("1");
|
||||
base.evaluate();
|
||||
}
|
||||
|
||||
private void overrideSwipeUpEnabled(Boolean swipeUpEnabledOverride)
|
||||
throws Throwable {
|
||||
mLauncher.overrideSwipeUpEnabled(swipeUpEnabledOverride);
|
||||
mMainThreadExecutor.execute(() -> OverviewInteractionState.INSTANCE.get(
|
||||
InstrumentationRegistry.getInstrumentation().getTargetContext()).
|
||||
notifySwipeUpSettingChanged(mLauncher.isSwipeUpEnabled()));
|
||||
// TODO(b/124236673): avoid using sleep().
|
||||
mLauncher.getDevice().waitForIdle();
|
||||
Thread.sleep(2000);
|
||||
mLauncher.getDevice().waitForIdle();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return base;
|
||||
|
||||
@@ -38,6 +38,15 @@
|
||||
android:theme="@style/HomeScreenElementTheme"
|
||||
launcher:pageIndicator="@+id/page_indicator" />
|
||||
|
||||
<!-- DO NOT CHANGE THE ID -->
|
||||
<com.android.launcher3.Hotseat
|
||||
android:id="@+id/hotseat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:theme="@style/HomeScreenElementTheme"
|
||||
android:importantForAccessibility="no"
|
||||
launcher:containerType="hotseat" />
|
||||
|
||||
<include
|
||||
android:id="@+id/overview_panel"
|
||||
layout="@layout/overview_panel"
|
||||
@@ -71,15 +80,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<!-- DO NOT CHANGE THE ID -->
|
||||
<com.android.launcher3.Hotseat
|
||||
android:id="@+id/hotseat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:theme="@style/HomeScreenElementTheme"
|
||||
android:importantForAccessibility="no"
|
||||
launcher:containerType="hotseat" />
|
||||
|
||||
</com.android.launcher3.dragndrop.DragLayer>
|
||||
|
||||
</com.android.launcher3.LauncherRootView>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
android:ellipsize="end"
|
||||
android:textSize="@dimen/snackbar_max_text_size"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:theme="@style/TextTitle"/>
|
||||
style="@style/TextTitle"/>
|
||||
<TextView
|
||||
android:id="@+id/action"
|
||||
android:layout_height="@dimen/snackbar_content_height"
|
||||
@@ -42,6 +42,6 @@
|
||||
android:textStyle="bold"
|
||||
android:textSize="@dimen/snackbar_max_text_size"
|
||||
android:textColor="?android:attr/colorAccent"
|
||||
android:theme="@style/TextTitle"
|
||||
style="@style/TextTitle"
|
||||
android:capitalize="sentences"/>
|
||||
</merge>
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
<style name="WidgetContainerTheme.Dark" />
|
||||
|
||||
<style name="FastScrollerPopup" >
|
||||
<style name="FastScrollerPopup" parent="@android:style/TextAppearance.DeviceDefault">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:minWidth">@dimen/fastscroll_popup_width</item>
|
||||
<item name="android:layout_height">@dimen/fastscroll_popup_height</item>
|
||||
@@ -142,7 +142,7 @@
|
||||
</style>
|
||||
|
||||
<!-- Base theme for BubbleTextView and sub classes -->
|
||||
<style name="BaseIcon">
|
||||
<style name="BaseIcon" parent="@android:style/TextAppearance.DeviceDefault">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
@@ -150,7 +150,6 @@
|
||||
<item name="android:gravity">center_horizontal</item>
|
||||
<item name="android:lines">1</item>
|
||||
<item name="android:textColor">?android:attr/textColorSecondary</item>
|
||||
<item name="android:fontFamily">sans-serif-condensed</item>
|
||||
<item name="android:defaultFocusHighlightEnabled">false</item>
|
||||
|
||||
<!-- No shadows in the base theme -->
|
||||
@@ -158,7 +157,7 @@
|
||||
</style>
|
||||
|
||||
<!-- Icon displayed on the worksapce -->
|
||||
<style name="BaseIcon.Workspace">
|
||||
<style name="BaseIcon.Workspace" >
|
||||
<item name="android:shadowRadius">2.0</item>
|
||||
<item name="android:shadowColor">?attr/workspaceShadowColor</item>
|
||||
<item name="ambientShadowColor">?attr/workspaceAmbientShadowColor</item>
|
||||
@@ -190,9 +189,7 @@
|
||||
|
||||
<style name="DropTargetButton" parent="DropTargetButtonBase" />
|
||||
|
||||
<style name="TextTitle">
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
</style>
|
||||
<style name="TextTitle" parent="@android:style/TextAppearance.DeviceDefault" />
|
||||
|
||||
<style name="AllAppsEmptySearchBackground">
|
||||
<item name="android:colorPrimary">#E0E0E0</item>
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
@@ -33,9 +32,11 @@ import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.Patterns;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.launcher3.LauncherProvider.SqlArguments;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
@@ -149,9 +150,6 @@ public class AutoInstallsLayout {
|
||||
private static final String HOTSEAT_CONTAINER_NAME =
|
||||
Favorites.containerToString(Favorites.CONTAINER_HOTSEAT);
|
||||
|
||||
private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
|
||||
"com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
|
||||
|
||||
@Thunk final Context mContext;
|
||||
@Thunk final AppWidgetHost mAppWidgetHost;
|
||||
protected final LayoutParserCallback mCallback;
|
||||
@@ -207,7 +205,7 @@ public class AutoInstallsLayout {
|
||||
*/
|
||||
protected int parseLayout(int layoutId, IntArray screenIds)
|
||||
throws XmlPullParserException, IOException {
|
||||
XmlResourceParser parser = mSourceRes.getXml(layoutId);
|
||||
XmlPullParser parser = mSourceRes.getXml(layoutId);
|
||||
beginDocument(parser, mRootTag);
|
||||
final int depth = parser.getDepth();
|
||||
int type;
|
||||
@@ -228,7 +226,7 @@ public class AutoInstallsLayout {
|
||||
* Parses container and screenId attribute from the current tag, and puts it in the out.
|
||||
* @param out array of size 2.
|
||||
*/
|
||||
protected void parseContainerAndScreen(XmlResourceParser parser, int[] out) {
|
||||
protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
|
||||
if (HOTSEAT_CONTAINER_NAME.equals(getAttributeValue(parser, ATTR_CONTAINER))) {
|
||||
out[0] = Favorites.CONTAINER_HOTSEAT;
|
||||
// Hack: hotseat items are stored using screen ids
|
||||
@@ -243,7 +241,7 @@ public class AutoInstallsLayout {
|
||||
* Parses the current node and returns the number of elements added.
|
||||
*/
|
||||
protected int parseAndAddNode(
|
||||
XmlResourceParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds)
|
||||
XmlPullParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds)
|
||||
throws XmlPullParserException, IOException {
|
||||
|
||||
if (TAG_INCLUDE.equals(parser.getName())) {
|
||||
@@ -324,7 +322,7 @@ public class AutoInstallsLayout {
|
||||
* Parses the tag and adds to the db
|
||||
* @return the id of the row added or -1;
|
||||
*/
|
||||
int parseAndAdd(XmlResourceParser parser)
|
||||
int parseAndAdd(XmlPullParser parser)
|
||||
throws XmlPullParserException, IOException;
|
||||
}
|
||||
|
||||
@@ -334,7 +332,7 @@ public class AutoInstallsLayout {
|
||||
protected class AppShortcutParser implements TagParser {
|
||||
|
||||
@Override
|
||||
public int parseAndAdd(XmlResourceParser parser) {
|
||||
public int parseAndAdd(XmlPullParser parser) {
|
||||
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
|
||||
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
|
||||
|
||||
@@ -371,7 +369,7 @@ public class AutoInstallsLayout {
|
||||
/**
|
||||
* Helper method to allow extending the parser capabilities
|
||||
*/
|
||||
protected int invalidPackageOrClass(XmlResourceParser parser) {
|
||||
protected int invalidPackageOrClass(XmlPullParser parser) {
|
||||
Log.w(TAG, "Skipping invalid <favorite> with no component");
|
||||
return -1;
|
||||
}
|
||||
@@ -383,7 +381,7 @@ public class AutoInstallsLayout {
|
||||
protected class AutoInstallParser implements TagParser {
|
||||
|
||||
@Override
|
||||
public int parseAndAdd(XmlResourceParser parser) {
|
||||
public int parseAndAdd(XmlPullParser parser) {
|
||||
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
|
||||
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
|
||||
if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
|
||||
@@ -414,7 +412,7 @@ public class AutoInstallsLayout {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parseAndAdd(XmlResourceParser parser) {
|
||||
public int parseAndAdd(XmlPullParser parser) {
|
||||
final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
|
||||
final int iconId = getAttributeResourceValue(parser, ATTR_ICON, 0);
|
||||
|
||||
@@ -449,7 +447,7 @@ public class AutoInstallsLayout {
|
||||
intent, Favorites.ITEM_TYPE_SHORTCUT);
|
||||
}
|
||||
|
||||
protected Intent parseIntent(XmlResourceParser parser) {
|
||||
protected Intent parseIntent(XmlPullParser parser) {
|
||||
final String url = getAttributeValue(parser, ATTR_URL);
|
||||
if (TextUtils.isEmpty(url) || !Patterns.WEB_URL.matcher(url).matches()) {
|
||||
if (LOGD) Log.d(TAG, "Ignoring shortcut, invalid url: " + url);
|
||||
@@ -470,7 +468,7 @@ public class AutoInstallsLayout {
|
||||
protected class PendingWidgetParser implements TagParser {
|
||||
|
||||
@Override
|
||||
public int parseAndAdd(XmlResourceParser parser)
|
||||
public int parseAndAdd(XmlPullParser parser)
|
||||
throws XmlPullParserException, IOException {
|
||||
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
|
||||
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
|
||||
@@ -541,7 +539,7 @@ public class AutoInstallsLayout {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parseAndAdd(XmlResourceParser parser)
|
||||
public int parseAndAdd(XmlPullParser parser)
|
||||
throws XmlPullParserException, IOException {
|
||||
final String title;
|
||||
final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
|
||||
@@ -649,7 +647,7 @@ public class AutoInstallsLayout {
|
||||
* Return attribute value, attempting launcher-specific namespace first
|
||||
* before falling back to anonymous attribute.
|
||||
*/
|
||||
protected static String getAttributeValue(XmlResourceParser parser, String attribute) {
|
||||
protected static String getAttributeValue(XmlPullParser parser, String attribute) {
|
||||
String value = parser.getAttributeValue(
|
||||
"http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute);
|
||||
if (value == null) {
|
||||
@@ -662,13 +660,14 @@ public class AutoInstallsLayout {
|
||||
* Return attribute resource value, attempting launcher-specific namespace
|
||||
* first before falling back to anonymous attribute.
|
||||
*/
|
||||
protected static int getAttributeResourceValue(XmlResourceParser parser, String attribute,
|
||||
protected static int getAttributeResourceValue(XmlPullParser parser, String attribute,
|
||||
int defaultValue) {
|
||||
int value = parser.getAttributeResourceValue(
|
||||
AttributeSet attrs = Xml.asAttributeSet(parser);
|
||||
int value = attrs.getAttributeResourceValue(
|
||||
"http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute,
|
||||
defaultValue);
|
||||
if (value == defaultValue) {
|
||||
value = parser.getAttributeResourceValue(null, attribute, defaultValue);
|
||||
value = attrs.getAttributeResourceValue(null, attribute, defaultValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
@@ -76,7 +75,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseContainerAndScreen(XmlResourceParser parser, int[] out) {
|
||||
protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
|
||||
out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
|
||||
if (strContainer != null) {
|
||||
@@ -91,7 +90,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
|
||||
public class AppShortcutWithUriParser extends AppShortcutParser {
|
||||
|
||||
@Override
|
||||
protected int invalidPackageOrClass(XmlResourceParser parser) {
|
||||
protected int invalidPackageOrClass(XmlPullParser parser) {
|
||||
final String uri = getAttributeValue(parser, ATTR_URI);
|
||||
if (TextUtils.isEmpty(uri)) {
|
||||
Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
|
||||
@@ -185,7 +184,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent parseIntent(XmlResourceParser parser) {
|
||||
protected Intent parseIntent(XmlPullParser parser) {
|
||||
String uri = null;
|
||||
try {
|
||||
uri = getAttributeValue(parser, ATTR_URI);
|
||||
@@ -205,7 +204,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
|
||||
private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
|
||||
|
||||
@Override
|
||||
public int parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
|
||||
public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
|
||||
IOException {
|
||||
final int groupDepth = parser.getDepth();
|
||||
int type;
|
||||
@@ -233,7 +232,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
|
||||
@Thunk class PartnerFolderParser implements TagParser {
|
||||
|
||||
@Override
|
||||
public int parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
|
||||
public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
|
||||
IOException {
|
||||
// Folder contents come from an external XML resource
|
||||
final Partner partner = Partner.get(mPackageManager);
|
||||
@@ -242,7 +241,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
|
||||
final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
|
||||
"xml", partner.getPackageName());
|
||||
if (resId != 0) {
|
||||
final XmlResourceParser partnerParser = partnerRes.getXml(resId);
|
||||
final XmlPullParser partnerParser = partnerRes.getXml(resId);
|
||||
beginDocument(partnerParser, TAG_FOLDER);
|
||||
|
||||
FolderParser folderParser = new FolderParser(getFolderElementsMap(partnerRes));
|
||||
@@ -259,7 +258,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
|
||||
@Thunk class MyFolderParser extends FolderParser {
|
||||
|
||||
@Override
|
||||
public int parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
|
||||
public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
|
||||
IOException {
|
||||
final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0);
|
||||
if (resId != 0) {
|
||||
|
||||
@@ -611,6 +611,12 @@ public class DeviceProfile {
|
||||
outBounds.right = outBounds.left + (getCellSize().x * spanX);
|
||||
}
|
||||
|
||||
public float getAspectRatioWithInsets() {
|
||||
int w = widthPx - mInsets.left - mInsets.right;
|
||||
int h = heightPx - mInsets.top - mInsets.bottom;
|
||||
return ((float) Math.max(w, h)) / Math.min(w, h);
|
||||
}
|
||||
|
||||
private static Context getContext(Context c, int orientation) {
|
||||
Configuration context = new Configuration(c.getResources().getConfiguration());
|
||||
context.orientation = orientation;
|
||||
|
||||
@@ -257,7 +257,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
||||
|
||||
private RotationHelper mRotationHelper;
|
||||
|
||||
private final Handler mHandler = new Handler();
|
||||
final Handler mHandler = new Handler();
|
||||
private final Runnable mHandleDeferredResume = this::handleDeferredResume;
|
||||
|
||||
@Override
|
||||
@@ -319,6 +319,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
||||
}
|
||||
}
|
||||
restoreState(savedInstanceState);
|
||||
mStateManager.reapplyState();
|
||||
|
||||
// We only load the page synchronously if the user rotates (or triggers a
|
||||
// configuration change) while launcher is in the foreground
|
||||
@@ -1747,6 +1748,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
||||
setWorkspaceLoading(true);
|
||||
|
||||
// Clear the workspace because it's going to be rebound
|
||||
mDragController.cancelDrag();
|
||||
|
||||
mWorkspace.clearDropTargets();
|
||||
mWorkspace.removeAllWorkspaceScreens();
|
||||
mAppWidgetHost.clearViews();
|
||||
|
||||
@@ -19,12 +19,14 @@ package com.android.launcher3;
|
||||
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
|
||||
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Handler;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.compat.LauncherAppsCompat;
|
||||
@@ -66,6 +68,9 @@ public class LauncherAppState {
|
||||
}
|
||||
|
||||
private LauncherAppState(Context context) {
|
||||
if (!UserManagerCompat.getInstance(context).isUserUnlocked(Process.myUserHandle())) {
|
||||
throw new RuntimeException("LauncherAppState should not start in direct boot mode");
|
||||
}
|
||||
if (getLocalProvider(context) == null) {
|
||||
throw new RuntimeException(
|
||||
"Initializing LauncherAppState in the absence of LauncherProvider");
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.util.Pair;
|
||||
import com.android.launcher3.compat.LauncherAppsCompat;
|
||||
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
|
||||
import com.android.launcher3.compat.UserManagerCompat;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
import com.android.launcher3.model.AddWorkspaceItemsTask;
|
||||
@@ -210,20 +211,20 @@ public class LauncherModel extends BroadcastReceiver
|
||||
final int itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
|
||||
ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
|
||||
if (modelItem != null && item != modelItem) {
|
||||
// check all the data is consistent
|
||||
if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
|
||||
ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
|
||||
ShortcutInfo shortcut = (ShortcutInfo) item;
|
||||
if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
|
||||
modelShortcut.intent.filterEquals(shortcut.intent) &&
|
||||
modelShortcut.id == shortcut.id &&
|
||||
modelShortcut.itemType == shortcut.itemType &&
|
||||
modelShortcut.container == shortcut.container &&
|
||||
modelShortcut.screenId == shortcut.screenId &&
|
||||
modelShortcut.cellX == shortcut.cellX &&
|
||||
modelShortcut.cellY == shortcut.cellY &&
|
||||
modelShortcut.spanX == shortcut.spanX &&
|
||||
modelShortcut.spanY == shortcut.spanY) {
|
||||
// If it is a release build on a release device, check all the data is consistent as
|
||||
// we don't want to crash non-dev users.
|
||||
if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
|
||||
modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
|
||||
if (modelItem.title.toString().equals(item.title.toString()) &&
|
||||
modelItem.getIntent().filterEquals(item.getIntent()) &&
|
||||
modelItem.id == item.id &&
|
||||
modelItem.itemType == item.itemType &&
|
||||
modelItem.container == item.container &&
|
||||
modelItem.screenId == item.screenId &&
|
||||
modelItem.cellX == item.cellX &&
|
||||
modelItem.cellY == item.cellY &&
|
||||
modelItem.spanX == item.spanX &&
|
||||
modelItem.spanY == item.spanY) {
|
||||
// For all intents and purposes, this is the same object
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,6 +187,11 @@ public class LauncherState {
|
||||
return new float[] {1, 0, 0};
|
||||
}
|
||||
|
||||
public float[] getHotseatScaleAndTranslation(Launcher launcher) {
|
||||
// For most states, treat the hotseat as if it were part of the workspace.
|
||||
return getWorkspaceScaleAndTranslation(launcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 2 floats designating how to transition overview:
|
||||
* scale for the current and adjacent pages
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.launcher3;
|
||||
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
|
||||
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
|
||||
@@ -249,6 +248,22 @@ public class LauncherStateManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (delay > 0) {
|
||||
// Create the animation after the delay as some properties can change between preparing
|
||||
// the animation and running the animation.
|
||||
int startChangeId = mConfig.mChangeId;
|
||||
mUiHandler.postDelayed(() -> {
|
||||
if (mConfig.mChangeId == startChangeId) {
|
||||
goToStateAnimated(state, fromState, onCompleteRunnable);
|
||||
}
|
||||
}, delay);
|
||||
} else {
|
||||
goToStateAnimated(state, fromState, onCompleteRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
private void goToStateAnimated(LauncherState state, LauncherState fromState,
|
||||
Runnable onCompleteRunnable) {
|
||||
// Since state NORMAL can be reached from multiple states, just assume that the
|
||||
// transition plays in reverse and use the same duration as previous state.
|
||||
mConfig.duration = state == NORMAL ? fromState.transitionDuration : state.transitionDuration;
|
||||
@@ -257,12 +272,7 @@ public class LauncherStateManager {
|
||||
prepareForAtomicAnimation(fromState, state, builder);
|
||||
AnimatorSet animation = createAnimationToNewWorkspaceInternal(
|
||||
state, builder, onCompleteRunnable);
|
||||
Runnable runnable = new StartAnimRunnable(animation);
|
||||
if (delay > 0) {
|
||||
mUiHandler.postDelayed(runnable, delay);
|
||||
} else {
|
||||
mUiHandler.post(runnable);
|
||||
}
|
||||
mUiHandler.post(new StartAnimRunnable(animation));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,6 +308,8 @@ public class LauncherStateManager {
|
||||
if (!isWorkspaceVisible) {
|
||||
workspace.setScaleX(0.92f);
|
||||
workspace.setScaleY(0.92f);
|
||||
workspace.getHotseat().setScaleX(0.92f);
|
||||
workspace.getHotseat().setScaleY(0.92f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,6 +544,8 @@ public class LauncherStateManager {
|
||||
|
||||
private AnimatorSet mCurrentAnimation;
|
||||
private LauncherState mTargetState;
|
||||
// Id to keep track of config changes, to tie an animation with the corresponding request
|
||||
private int mChangeId = 0;
|
||||
|
||||
/**
|
||||
* Cancels the current animation and resets config variables.
|
||||
@@ -553,6 +567,7 @@ public class LauncherStateManager {
|
||||
|
||||
mCurrentAnimation = null;
|
||||
playbackController = null;
|
||||
mChangeId ++;
|
||||
}
|
||||
|
||||
public PropertySetter getPropertySetter(AnimatorSetBuilder builder) {
|
||||
|
||||
@@ -1027,7 +1027,8 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
|
||||
if (overScrollAmount < 0) {
|
||||
super.scrollTo(overScrollAmount, getScrollY());
|
||||
} else {
|
||||
super.scrollTo(mMaxScrollX + overScrollAmount, getScrollY());
|
||||
int x = Math.max(0, Math.min(getScrollX(), mMaxScrollX));
|
||||
super.scrollTo(x + overScrollAmount, getScrollY());
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@@ -16,13 +16,18 @@
|
||||
|
||||
package com.android.launcher3;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.WallpaperManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.LauncherActivityInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
@@ -33,6 +38,7 @@ import android.graphics.Paint;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.DeadObjectException;
|
||||
@@ -51,14 +57,24 @@ import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.android.launcher3.compat.LauncherAppsCompat;
|
||||
import com.android.launcher3.compat.ShortcutConfigActivityInfo;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutView;
|
||||
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.widget.PendingAddShortcutInfo;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -67,9 +83,6 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
|
||||
/**
|
||||
* Various utilities shared amongst the Launcher's classes.
|
||||
*/
|
||||
@@ -555,6 +568,7 @@ public final class Utilities {
|
||||
public static void getLocationBoundsForView(Launcher launcher, View v, Rect outRect) {
|
||||
final DragLayer dragLayer = launcher.getDragLayer();
|
||||
final boolean isBubbleTextView = v instanceof BubbleTextView;
|
||||
final boolean isFolderIcon = v instanceof FolderIcon;
|
||||
final Rect rect = new Rect();
|
||||
|
||||
final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
|
||||
@@ -562,15 +576,14 @@ public final class Utilities {
|
||||
// Deep shortcut views have their icon drawn in a separate view.
|
||||
DeepShortcutView view = (DeepShortcutView) v.getParent();
|
||||
dragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
|
||||
} else if (isBubbleTextView && v.getTag() instanceof ItemInfo
|
||||
} else if ((isBubbleTextView || isFolderIcon) && v.getTag() instanceof ItemInfo
|
||||
&& (((ItemInfo) v.getTag()).container == CONTAINER_DESKTOP
|
||||
|| ((ItemInfo) v.getTag()).container == CONTAINER_HOTSEAT)) {
|
||||
BubbleTextView btv = (BubbleTextView) v;
|
||||
CellLayout pageViewIsOn = ((CellLayout) btv.getParent().getParent());
|
||||
CellLayout pageViewIsOn = ((CellLayout) v.getParent().getParent());
|
||||
int pageNum = launcher.getWorkspace().indexOfChild(pageViewIsOn);
|
||||
|
||||
DeviceProfile dp = launcher.getDeviceProfile();
|
||||
ItemInfo info = ((ItemInfo) btv.getTag());
|
||||
ItemInfo info = ((ItemInfo) v.getTag());
|
||||
dp.getItemLocation(info.cellX, info.cellY, info.spanX, info.spanY,
|
||||
info.container, pageNum - launcher.getCurrentWorkspaceScreen(), rect);
|
||||
} else {
|
||||
@@ -580,8 +593,9 @@ public final class Utilities {
|
||||
int viewLocationTop = rect.top;
|
||||
|
||||
if (isBubbleTextView && !fromDeepShortcutView) {
|
||||
BubbleTextView btv = (BubbleTextView) v;
|
||||
btv.getIconBounds(rect);
|
||||
((BubbleTextView) v).getIconBounds(rect);
|
||||
} else if (isFolderIcon) {
|
||||
((FolderIcon) v).getPreviewBounds(rect);
|
||||
} else {
|
||||
rect.set(0, 0, rect.width(), rect.height());
|
||||
}
|
||||
@@ -590,4 +604,55 @@ public final class Utilities {
|
||||
outRect.set(viewLocationLeft, viewLocationTop, viewLocationLeft + rect.width(),
|
||||
viewLocationTop + rect.height());
|
||||
}
|
||||
|
||||
public static void unregisterReceiverSafely(Context context, BroadcastReceiver receiver) {
|
||||
try {
|
||||
context.unregisterReceiver(receiver);
|
||||
} catch (IllegalArgumentException e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full drawable for {@param info}.
|
||||
* @param outObj this is set to the internal data associated with {@param info},
|
||||
* eg {@link LauncherActivityInfo} or {@link ShortcutInfoCompat}.
|
||||
*/
|
||||
public static Drawable getFullDrawable(Launcher launcher, ItemInfo info, int width, int height,
|
||||
boolean flattenDrawable, Object[] outObj) {
|
||||
LauncherAppState appState = LauncherAppState.getInstance(launcher);
|
||||
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
|
||||
LauncherActivityInfo activityInfo = LauncherAppsCompat.getInstance(launcher)
|
||||
.resolveActivity(info.getIntent(), info.user);
|
||||
outObj[0] = activityInfo;
|
||||
return (activityInfo != null) ? appState.getIconCache()
|
||||
.getFullResIcon(activityInfo, flattenDrawable) : null;
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
|
||||
if (info instanceof PendingAddShortcutInfo) {
|
||||
ShortcutConfigActivityInfo activityInfo =
|
||||
((PendingAddShortcutInfo) info).activityInfo;
|
||||
outObj[0] = activityInfo;
|
||||
return activityInfo.getFullResIcon(appState.getIconCache());
|
||||
}
|
||||
ShortcutKey key = ShortcutKey.fromItemInfo(info);
|
||||
DeepShortcutManager sm = DeepShortcutManager.getInstance(launcher);
|
||||
List<ShortcutInfoCompat> si = sm.queryForFullDetails(
|
||||
key.componentName.getPackageName(), Arrays.asList(key.getId()), key.user);
|
||||
if (si.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
outObj[0] = si.get(0);
|
||||
return sm.getShortcutIconDrawable(si.get(0),
|
||||
appState.getInvariantDeviceProfile().fillResIconDpi);
|
||||
}
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
|
||||
FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon(
|
||||
launcher, info.id, new Point(width, height));
|
||||
if (icon == null) {
|
||||
return null;
|
||||
}
|
||||
outObj[0] = icon;
|
||||
return icon;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Parcelable;
|
||||
import android.os.UserHandle;
|
||||
import android.util.AttributeSet;
|
||||
@@ -521,6 +522,9 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
|
||||
mScreenOrder.clear();
|
||||
mWorkspaceScreens.clear();
|
||||
|
||||
// Remove any deferred refresh callbacks
|
||||
mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
|
||||
|
||||
// Ensure that the first page is always present
|
||||
bindAndInitFirstWorkspaceScreen(qsb);
|
||||
|
||||
@@ -3348,13 +3352,15 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
|
||||
LauncherAppWidgetHost host) {
|
||||
mInfos = infos;
|
||||
mHost = host;
|
||||
mHandler = new Handler();
|
||||
mHandler = mLauncher.mHandler;
|
||||
mRefreshPending = true;
|
||||
|
||||
mHost.addProviderChangeListener(this);
|
||||
// Force refresh after 10 seconds, if we don't get the provider changed event.
|
||||
// This could happen when the provider is no longer available in the app.
|
||||
mHandler.postDelayed(this, 10000);
|
||||
Message msg = Message.obtain(mHandler, this);
|
||||
msg.obj = DeferredWidgetRefresh.class;
|
||||
mHandler.sendMessageDelayed(msg, 10000);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.android.launcher3;
|
||||
import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
|
||||
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
|
||||
import static com.android.launcher3.LauncherState.HOTSEAT_SEARCH_BOX;
|
||||
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
|
||||
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
@@ -35,6 +34,7 @@ import com.android.launcher3.LauncherState.PageAlphaProvider;
|
||||
import com.android.launcher3.LauncherStateManager.AnimationConfig;
|
||||
import com.android.launcher3.anim.AnimatorSetBuilder;
|
||||
import com.android.launcher3.anim.PropertySetter;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
|
||||
|
||||
/**
|
||||
@@ -72,6 +72,7 @@ public class WorkspaceStateTransitionAnimation {
|
||||
private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter,
|
||||
AnimatorSetBuilder builder, AnimationConfig config) {
|
||||
float[] scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
|
||||
float[] hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation(mLauncher);
|
||||
mNewScale = scaleAndTranslation[0];
|
||||
PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
|
||||
final int childCount = mWorkspace.getChildCount();
|
||||
@@ -84,12 +85,24 @@ public class WorkspaceStateTransitionAnimation {
|
||||
Interpolator fadeInterpolator = builder.getInterpolator(ANIM_WORKSPACE_FADE,
|
||||
pageAlphaProvider.interpolator);
|
||||
boolean playAtomicComponent = config.playAtomicComponent();
|
||||
Hotseat hotseat = mWorkspace.getHotseat();
|
||||
if (playAtomicComponent) {
|
||||
Interpolator scaleInterpolator = builder.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
|
||||
propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
|
||||
|
||||
// Set the hotseat's pivot point to match the workspace's, so that it scales together.
|
||||
DragLayer dragLayer = mLauncher.getDragLayer();
|
||||
int[] workspacePivot = new int[]{(int) mWorkspace.getPivotX(),
|
||||
(int) mWorkspace.getPivotY()};
|
||||
dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
|
||||
dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
|
||||
hotseat.setPivotX(workspacePivot[0]);
|
||||
hotseat.setPivotY(workspacePivot[1]);
|
||||
float hotseatScale = hotseatScaleAndTranslation[0];
|
||||
propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale, scaleInterpolator);
|
||||
|
||||
float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
|
||||
propertySetter.setViewAlpha(mLauncher.getHotseat(), hotseatIconsAlpha,
|
||||
fadeInterpolator);
|
||||
propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
|
||||
propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
|
||||
hotseatIconsAlpha, fadeInterpolator);
|
||||
}
|
||||
@@ -105,6 +118,11 @@ public class WorkspaceStateTransitionAnimation {
|
||||
propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
|
||||
scaleAndTranslation[2], translationInterpolator);
|
||||
|
||||
propertySetter.setFloat(hotseat, View.TRANSLATION_Y,
|
||||
hotseatScaleAndTranslation[2], translationInterpolator);
|
||||
propertySetter.setFloat(mWorkspace.getPageIndicator(), View.TRANSLATION_Y,
|
||||
hotseatScaleAndTranslation[2], translationInterpolator);
|
||||
|
||||
// Set scrim
|
||||
WorkspaceAndHotseatScrim scrim = mLauncher.getDragLayer().getScrim();
|
||||
propertySetter.setFloat(scrim, SCRIM_PROGRESS, state.getWorkspaceScrimAlpha(mLauncher),
|
||||
|
||||
@@ -124,12 +124,6 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil
|
||||
float shiftCurrent = progress * mShiftRange;
|
||||
|
||||
mAppsView.setTranslationY(shiftCurrent);
|
||||
float hotseatTranslation = -mShiftRange + shiftCurrent;
|
||||
|
||||
if (!mIsVerticalLayout) {
|
||||
mLauncher.getHotseat().setTranslationY(hotseatTranslation);
|
||||
mLauncher.getWorkspace().getPageIndicator().setTranslationY(hotseatTranslation);
|
||||
}
|
||||
|
||||
// Use a light system UI (dark icons) if all apps is behind at least half of the
|
||||
// status bar.
|
||||
|
||||
@@ -101,6 +101,10 @@ abstract class BaseFlags {
|
||||
public static final TogglableFlag QUICKSTEP_SPRINGS = new TogglableFlag("QUICKSTEP_SPRINGS",
|
||||
false, "Enable springs for quickstep animations");
|
||||
|
||||
public static final TogglableFlag ADAPTIVE_ICON_WINDOW_ANIM = new TogglableFlag(
|
||||
"ADAPTIVE_ICON_WINDOW_ANIM", false,
|
||||
"Use adaptive icons for window animations.");
|
||||
|
||||
public static final TogglableFlag ENABLE_QUICKSTEP_LIVE_TILE = new TogglableFlag(
|
||||
"ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
|
||||
|
||||
@@ -112,6 +116,10 @@ abstract class BaseFlags {
|
||||
"ENABLE_HINTS_IN_OVERVIEW", false,
|
||||
"Show chip hints and gleams on the overview screen");
|
||||
|
||||
public static final TogglableFlag ENABLE_ASSISTANT_GESTURE = new ToggleableGlobalSettingsFlag(
|
||||
"ENABLE_ASSISTANT_GESTURE", false,
|
||||
"Enable swipe up from the bottom right corner to start assistant");
|
||||
|
||||
public static void initialize(Context context) {
|
||||
// Avoid the disk read for user builds
|
||||
if (Utilities.IS_DEBUG_DEVICE) {
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.animation.FloatArrayEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.pm.LauncherActivityInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
@@ -54,18 +53,12 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.FirstFrameAnimatorHelper;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.compat.LauncherAppsCompat;
|
||||
import com.android.launcher3.compat.ShortcutConfigActivityInfo;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
import com.android.launcher3.widget.PendingAddShortcutInfo;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
@@ -204,11 +197,12 @@ public class DragView extends View {
|
||||
public void run() {
|
||||
LauncherAppState appState = LauncherAppState.getInstance(mLauncher);
|
||||
Object[] outObj = new Object[1];
|
||||
Drawable dr = getFullDrawable(info, appState, outObj);
|
||||
int w = mBitmap.getWidth();
|
||||
int h = mBitmap.getHeight();
|
||||
Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h,
|
||||
false /* flattenDrawable */, outObj);
|
||||
|
||||
if (dr instanceof AdaptiveIconDrawable) {
|
||||
int w = mBitmap.getWidth();
|
||||
int h = mBitmap.getHeight();
|
||||
int blurMargin = (int) mLauncher.getResources()
|
||||
.getDimension(R.dimen.blur_size_medium_outline) / 2;
|
||||
|
||||
@@ -313,49 +307,6 @@ public class DragView extends View {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full drawable for {@param info}.
|
||||
* @param outObj this is set to the internal data associated with {@param info},
|
||||
* eg {@link LauncherActivityInfo} or {@link ShortcutInfoCompat}.
|
||||
*/
|
||||
private Drawable getFullDrawable(ItemInfo info, LauncherAppState appState, Object[] outObj) {
|
||||
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
|
||||
LauncherActivityInfo activityInfo = LauncherAppsCompat.getInstance(mLauncher)
|
||||
.resolveActivity(info.getIntent(), info.user);
|
||||
outObj[0] = activityInfo;
|
||||
return (activityInfo != null) ? appState.getIconCache()
|
||||
.getFullResIcon(activityInfo, false) : null;
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
|
||||
if (info instanceof PendingAddShortcutInfo) {
|
||||
ShortcutConfigActivityInfo activityInfo =
|
||||
((PendingAddShortcutInfo) info).activityInfo;
|
||||
outObj[0] = activityInfo;
|
||||
return activityInfo.getFullResIcon(appState.getIconCache());
|
||||
}
|
||||
ShortcutKey key = ShortcutKey.fromItemInfo(info);
|
||||
DeepShortcutManager sm = DeepShortcutManager.getInstance(mLauncher);
|
||||
List<ShortcutInfoCompat> si = sm.queryForFullDetails(
|
||||
key.componentName.getPackageName(), Arrays.asList(key.getId()), key.user);
|
||||
if (si.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
outObj[0] = si.get(0);
|
||||
return sm.getShortcutIconDrawable(si.get(0),
|
||||
appState.getInvariantDeviceProfile().fillResIconDpi);
|
||||
}
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
|
||||
FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon(
|
||||
mLauncher, info.id, new Point(mBitmap.getWidth(), mBitmap.getHeight()));
|
||||
if (icon == null) {
|
||||
return null;
|
||||
}
|
||||
outObj[0] = icon;
|
||||
return icon;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For apps icons and shortcut icons that have badges, this method creates a drawable that can
|
||||
* later on be rendered on top of the layers for the badges. For app icons, work profile badges
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.folder.PreviewBackground;
|
||||
import com.android.launcher3.graphics.ShiftedBitmapDrawable;
|
||||
import com.android.launcher3.icons.BitmapRenderer;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
|
||||
@@ -133,39 +134,4 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
|
||||
|
||||
return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple drawable which draws a bitmap at a fixed position irrespective of the bounds
|
||||
*/
|
||||
private static class ShiftedBitmapDrawable extends Drawable {
|
||||
|
||||
private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
private final Bitmap mBitmap;
|
||||
private final float mShiftX;
|
||||
private final float mShiftY;
|
||||
|
||||
ShiftedBitmapDrawable(Bitmap bitmap, float shiftX, float shiftY) {
|
||||
mBitmap = bitmap;
|
||||
mShiftX = shiftX;
|
||||
mShiftY = shiftY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
canvas.drawBitmap(mBitmap, mShiftX, mShiftY, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int i) { }
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ import com.android.launcher3.pageindicators.PageIndicatorDots;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
import com.android.launcher3.views.ClipPathView;
|
||||
import com.android.launcher3.widget.PendingAddShortcutInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -84,7 +85,7 @@ import java.util.List;
|
||||
/**
|
||||
* Represents a set of icons chosen by the user or generated by the system.
|
||||
*/
|
||||
public class Folder extends AbstractFloatingView implements DragSource,
|
||||
public class Folder extends AbstractFloatingView implements ClipPathView, DragSource,
|
||||
View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
|
||||
View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
|
||||
private static final String TAG = "Launcher.Folder";
|
||||
@@ -1460,6 +1461,7 @@ public class Folder extends AbstractFloatingView implements DragSource,
|
||||
* Alternative to using {@link #getClipToOutline()} as it only works with derivatives of
|
||||
* rounded rect.
|
||||
*/
|
||||
@Override
|
||||
public void setClipPath(Path clipPath) {
|
||||
mClipPath = clipPath;
|
||||
invalidate();
|
||||
|
||||
@@ -189,6 +189,10 @@ public class FolderIcon extends FrameLayout implements FolderListener {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void getPreviewBounds(Rect outBounds) {
|
||||
mBackground.getBounds(outBounds);
|
||||
}
|
||||
|
||||
public Folder getFolder() {
|
||||
return mFolder;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import android.util.AttributeSet;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TypedValue;
|
||||
import android.util.Xml;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
@@ -46,6 +47,7 @@ import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.views.ClipPathView;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
@@ -74,8 +76,8 @@ public abstract class FolderShape {
|
||||
|
||||
public abstract void addShape(Path path, float offsetX, float offsetY, float radius);
|
||||
|
||||
public abstract Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
|
||||
float endRadius, boolean isReversed);
|
||||
public abstract <T extends View & ClipPathView> Animator createRevealAnimator(T target,
|
||||
Rect startRect, Rect endRect, float endRadius, boolean isReversed);
|
||||
|
||||
@Nullable
|
||||
public TypedValue getAttrValue(int attr) {
|
||||
@@ -88,8 +90,8 @@ public abstract class FolderShape {
|
||||
private static abstract class SimpleRectShape extends FolderShape {
|
||||
|
||||
@Override
|
||||
public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
|
||||
float endRadius, boolean isReversed) {
|
||||
public final <T extends View & ClipPathView> Animator createRevealAnimator(T target,
|
||||
Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
|
||||
return new RoundedRectRevealOutlineProvider(
|
||||
getStartRadius(startRect), endRadius, startRect, endRect) {
|
||||
@Override
|
||||
@@ -121,8 +123,8 @@ public abstract class FolderShape {
|
||||
Rect startRect, Rect endRect, float endRadius, Path outPath);
|
||||
|
||||
@Override
|
||||
public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
|
||||
float endRadius, boolean isReversed) {
|
||||
public final <T extends View & ClipPathView> Animator createRevealAnimator(T target,
|
||||
Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
|
||||
Path path = new Path();
|
||||
AnimatorUpdateListener listener =
|
||||
newUpdateListener(startRect, endRect, endRadius, path);
|
||||
|
||||
@@ -33,6 +33,7 @@ import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.Shader;
|
||||
import android.util.Property;
|
||||
@@ -154,6 +155,14 @@ public class PreviewBackground {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void getBounds(Rect outBounds) {
|
||||
int top = basePreviewOffsetY;
|
||||
int left = basePreviewOffsetX;
|
||||
int right = left + previewSize;
|
||||
int bottom = top + previewSize;
|
||||
outBounds.set(left, top, right, bottom);
|
||||
}
|
||||
|
||||
int getRadius() {
|
||||
return previewSize / 2;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.graphics;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
/**
|
||||
* A simple drawable which draws a bitmap at a fixed position irrespective of the bounds
|
||||
*/
|
||||
public class ShiftedBitmapDrawable extends Drawable {
|
||||
|
||||
private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
private final Bitmap mBitmap;
|
||||
private float mShiftX;
|
||||
private float mShiftY;
|
||||
|
||||
public ShiftedBitmapDrawable(Bitmap bitmap, float shiftX, float shiftY) {
|
||||
mBitmap = bitmap;
|
||||
mShiftX = shiftX;
|
||||
mShiftY = shiftY;
|
||||
}
|
||||
|
||||
public float getShiftX() {
|
||||
return mShiftX;
|
||||
}
|
||||
|
||||
public float getShiftY() {
|
||||
return mShiftY;
|
||||
}
|
||||
|
||||
public void setShiftX(float shiftX) {
|
||||
mShiftX = shiftX;
|
||||
}
|
||||
|
||||
public void setShiftY(float shiftY) {
|
||||
mShiftY = shiftY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
canvas.drawBitmap(mBitmap, mShiftX, mShiftY, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int i) { }
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,16 @@
|
||||
|
||||
package com.android.launcher3.graphics;
|
||||
|
||||
import static android.content.Intent.ACTION_SCREEN_OFF;
|
||||
import static android.content.Intent.ACTION_USER_PRESENT;
|
||||
|
||||
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
@@ -89,6 +96,20 @@ public class WorkspaceAndHotseatScrim implements
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
if (ACTION_SCREEN_OFF.equals(action)) {
|
||||
mAnimateScrimOnNextDraw = true;
|
||||
} else if (ACTION_USER_PRESENT.equals(action)) {
|
||||
// ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where
|
||||
// the user unlocked and the Launcher is not in the foreground.
|
||||
mAnimateScrimOnNextDraw = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static final int DARK_SCRIM_COLOR = 0x55000000;
|
||||
private static final int MAX_HOTSEAT_SCRIM_ALPHA = 100;
|
||||
private static final int ALPHA_MASK_HEIGHT_DP = 500;
|
||||
@@ -204,11 +225,20 @@ public class WorkspaceAndHotseatScrim implements
|
||||
public void onViewAttachedToWindow(View view) {
|
||||
mWallpaperColorInfo.addOnChangeListener(this);
|
||||
onExtractedColorsChanged(mWallpaperColorInfo);
|
||||
|
||||
if (mTopScrim != null) {
|
||||
IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
|
||||
filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
|
||||
mRoot.getContext().registerReceiver(mReceiver, filter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View view) {
|
||||
mWallpaperColorInfo.removeOnChangeListener(this);
|
||||
if (mTopScrim != null) {
|
||||
mRoot.getContext().unregisterReceiver(mReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.logging;
|
||||
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A utility class to record and log events. Events are stored in a fixed size array and old logs
|
||||
* are purged as new events come.
|
||||
*/
|
||||
public class EventLogArray {
|
||||
|
||||
private static final int TYPE_ONE_OFF = 0;
|
||||
private static final int TYPE_FLOAT = 1;
|
||||
private static final int TYPE_INTEGER = 2;
|
||||
private static final int TYPE_BOOL_TRUE = 3;
|
||||
private static final int TYPE_BOOL_FALSE = 4;
|
||||
|
||||
private final String name;
|
||||
private final EventEntry[] logs;
|
||||
private int nextIndex;
|
||||
|
||||
public EventLogArray(String name, int size) {
|
||||
this.name = name;
|
||||
logs = new EventEntry[size];
|
||||
nextIndex = 0;
|
||||
}
|
||||
|
||||
public void addLog(String event) {
|
||||
addLog(TYPE_ONE_OFF, event, 0);
|
||||
}
|
||||
|
||||
public void addLog(String event, int extras) {
|
||||
addLog(TYPE_INTEGER, event, extras);
|
||||
}
|
||||
|
||||
public void addLog(String event, float extras) {
|
||||
addLog(TYPE_FLOAT, event, extras);
|
||||
}
|
||||
|
||||
public void addLog(String event, boolean extras) {
|
||||
addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0);
|
||||
}
|
||||
|
||||
private void addLog(int type, String event, float extras) {
|
||||
// Merge the logs if its a duplicate
|
||||
int last = (nextIndex + logs.length - 1) % logs.length;
|
||||
int secondLast = (nextIndex + logs.length - 2) % logs.length;
|
||||
if (isEntrySame(logs[last], type, event) && isEntrySame(logs[secondLast], type, event)) {
|
||||
logs[last].update(type, event, extras);
|
||||
logs[secondLast].duplicateCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (logs[nextIndex] == null) {
|
||||
logs[nextIndex] = new EventEntry();
|
||||
}
|
||||
logs[nextIndex].update(type, event, extras);
|
||||
nextIndex = (nextIndex + 1) % logs.length;
|
||||
}
|
||||
|
||||
public void dump(String prefix, PrintWriter writer) {
|
||||
writer.println(prefix + name + " event history:");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(" HH:mm:ss.SSSZ ", Locale.US);
|
||||
Date date = new Date();
|
||||
|
||||
for (int i = 0; i < logs.length; i++) {
|
||||
EventEntry log = logs[(nextIndex + logs.length - i - 1) % logs.length];
|
||||
if (log == null) {
|
||||
continue;
|
||||
}
|
||||
date.setTime(log.time);
|
||||
|
||||
StringBuilder msg = new StringBuilder(prefix).append(sdf.format(date))
|
||||
.append(log.event);
|
||||
switch (log.type) {
|
||||
case TYPE_BOOL_FALSE:
|
||||
msg.append(": false");
|
||||
break;
|
||||
case TYPE_BOOL_TRUE:
|
||||
msg.append(": true");
|
||||
break;
|
||||
case TYPE_FLOAT:
|
||||
msg.append(": ").append(log.extras);
|
||||
break;
|
||||
case TYPE_INTEGER:
|
||||
msg.append(": ").append((int) log.extras);
|
||||
break;
|
||||
default: // fall out
|
||||
}
|
||||
if (log.duplicateCount > 0) {
|
||||
msg.append(" & ").append(log.duplicateCount).append(" similar events");
|
||||
}
|
||||
writer.println(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEntrySame(EventEntry entry, int type, String event) {
|
||||
return entry != null && entry.type == type && entry.event.equals(event);
|
||||
}
|
||||
|
||||
/** A single event entry. */
|
||||
private static class EventEntry {
|
||||
|
||||
private int type;
|
||||
private String event;
|
||||
private float extras;
|
||||
private long time;
|
||||
private int duplicateCount;
|
||||
|
||||
public void update(int type, String event, float extras) {
|
||||
this.type = type;
|
||||
this.event = event;
|
||||
this.extras = extras;
|
||||
time = System.currentTimeMillis();
|
||||
duplicateCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,6 @@ import java.lang.ref.WeakReference;
|
||||
public abstract class InternalStateHandler extends Binder {
|
||||
|
||||
public static final String EXTRA_STATE_HANDLER = "launcher.state_handler";
|
||||
public static final String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY";
|
||||
|
||||
private static final Scheduler sScheduler = new Scheduler();
|
||||
|
||||
@@ -77,10 +76,6 @@ public abstract class InternalStateHandler extends Binder {
|
||||
Launcher launcher, Intent intent, boolean alreadyOnHome, boolean explicitIntent) {
|
||||
boolean result = false;
|
||||
if (intent != null && intent.getExtras() != null) {
|
||||
// If we know that this the intent comes from pressing Home, defer to the default
|
||||
// processing.
|
||||
if (intent.hasExtra(EXTRA_FROM_HOME_KEY)) return false;
|
||||
|
||||
IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER);
|
||||
if (stateBinder instanceof InternalStateHandler) {
|
||||
InternalStateHandler handler = (InternalStateHandler) stateBinder;
|
||||
|
||||
@@ -72,6 +72,11 @@ public class SpringLoadedState extends LauncherState {
|
||||
return new float[] { scale, 0, (desiredCellTop - actualCellTop) / scale};
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getHotseatScaleAndTranslation(Launcher launcher) {
|
||||
return new float[] {1, 0, 0};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateEnabled(Launcher launcher) {
|
||||
Workspace ws = launcher.getWorkspace();
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.views;
|
||||
|
||||
import android.graphics.Path;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Alternative to using {@link View#getClipToOutline()} as it only works with derivatives of
|
||||
* rounded rect.
|
||||
*/
|
||||
public interface ClipPathView {
|
||||
void setClipPath(Path clipPath);
|
||||
}
|
||||
@@ -16,41 +16,90 @@
|
||||
package com.android.launcher3.views;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.InsettableFrameLayout.LayoutParams;
|
||||
import com.android.launcher3.ItemInfoWithIcon;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.graphics.DrawableFactory;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.folder.FolderShape;
|
||||
import com.android.launcher3.graphics.ShiftedBitmapDrawable;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
|
||||
|
||||
/**
|
||||
* A view that is created to look like another view with the purpose of creating fluid animations.
|
||||
*/
|
||||
public class FloatingIconView extends View implements Animator.AnimatorListener {
|
||||
|
||||
public class FloatingIconView extends View implements Animator.AnimatorListener, ClipPathView {
|
||||
|
||||
private static final Rect sTmpRect = new Rect();
|
||||
|
||||
private Runnable mStartRunnable;
|
||||
private Runnable mEndRunnable;
|
||||
|
||||
public FloatingIconView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
private Drawable mDrawable;
|
||||
private int mOriginalHeight;
|
||||
private final int mBlurSizeOutline;
|
||||
|
||||
public void setRunnables(Runnable startRunnable, Runnable endRunnable) {
|
||||
mStartRunnable = startRunnable;
|
||||
mEndRunnable = endRunnable;
|
||||
private boolean mIsAdaptiveIcon = false;
|
||||
|
||||
private @Nullable Drawable mForeground;
|
||||
private @Nullable Drawable mBackground;
|
||||
private ValueAnimator mRevealAnimator;
|
||||
private final Rect mStartRevealRect = new Rect();
|
||||
private final Rect mEndRevealRect = new Rect();
|
||||
private Path mClipPath;
|
||||
protected final Rect mOutline = new Rect();
|
||||
private final float mTaskCornerRadius;
|
||||
|
||||
private final Rect mFinalDrawableBounds = new Rect();
|
||||
private final Rect mBgDrawableBounds = new Rect();
|
||||
private final float mBgDrawableStartScale = 5f; // Magic number that can be tuned later.
|
||||
|
||||
private FloatingIconView(Context context) {
|
||||
super(context);
|
||||
|
||||
mBlurSizeOutline = context.getResources().getDimensionPixelSize(
|
||||
R.dimen.blur_size_medium_outline);
|
||||
|
||||
mTaskCornerRadius = 0; // TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions this view to match the size and location of {@param rect}.
|
||||
*
|
||||
* @param alpha The alpha to set this view.
|
||||
* @param progress A value from [0, 1] that represents the animation progress.
|
||||
* @param windowAlphaThreshold The value at which the window alpha is 0.
|
||||
*/
|
||||
public void update(RectF rect, float alpha) {
|
||||
public void update(RectF rect, float alpha, float progress, float windowAlphaThreshold) {
|
||||
setAlpha(alpha);
|
||||
|
||||
LayoutParams lp = (LayoutParams) getLayoutParams();
|
||||
@@ -59,13 +108,44 @@ public class FloatingIconView extends View implements Animator.AnimatorListener
|
||||
setTranslationX(dX);
|
||||
setTranslationY(dY);
|
||||
|
||||
float scaleX = rect.width() / (float) getWidth();
|
||||
float scaleY = rect.height() / (float) getHeight();
|
||||
float scale = Math.min(scaleX, scaleY);
|
||||
float scaleX = rect.width() / (float) lp.width;
|
||||
float scaleY = rect.height() / (float) lp.height;
|
||||
float scale = mIsAdaptiveIcon ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
|
||||
setPivotX(0);
|
||||
setPivotY(0);
|
||||
setScaleX(scale);
|
||||
setScaleY(scale);
|
||||
|
||||
// Wait until the window is no longer visible before morphing the icon into its final shape.
|
||||
float shapeRevealProgress = Utilities.mapToRange(Math.max(windowAlphaThreshold, progress),
|
||||
windowAlphaThreshold, 1f, 0f, 1, Interpolators.LINEAR);
|
||||
if (mIsAdaptiveIcon && shapeRevealProgress > 0) {
|
||||
if (mRevealAnimator == null) {
|
||||
mEndRevealRect.set(mOutline);
|
||||
// We play the reveal animation in reverse so that we end with the icon shape.
|
||||
mRevealAnimator = (ValueAnimator) FolderShape.getShape().createRevealAnimator(this,
|
||||
mStartRevealRect, mEndRevealRect, mTaskCornerRadius / scale, true);
|
||||
mRevealAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mRevealAnimator = null;
|
||||
}
|
||||
});
|
||||
mRevealAnimator.start();
|
||||
// We pause here so we can set the current fraction ourselves.
|
||||
mRevealAnimator.pause();
|
||||
}
|
||||
|
||||
float bgScale = shapeRevealProgress + mBgDrawableStartScale * (1 - shapeRevealProgress);
|
||||
setBackgroundDrawableBounds(bgScale);
|
||||
|
||||
mRevealAnimator.setCurrentFraction(shapeRevealProgress);
|
||||
if (Float.compare(shapeRevealProgress, 1f) >= 0f) {
|
||||
mRevealAnimator.end();
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
invalidateOutline();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,25 +162,17 @@ public class FloatingIconView extends View implements Animator.AnimatorListener
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animator) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size and position of this view to match {@param v}.
|
||||
*
|
||||
* @param v The view to copy
|
||||
* @param hideOriginal If true, it will hide {@param v} while this view is visible.
|
||||
* @param positionOut Rect that will hold the size and position of v.
|
||||
*/
|
||||
public void matchPositionOf(Launcher launcher, View v, boolean hideOriginal, Rect positionOut) {
|
||||
private void matchPositionOf(Launcher launcher, View v, Rect positionOut) {
|
||||
Utilities.getLocationBoundsForView(launcher, v, positionOut);
|
||||
final LayoutParams lp = new LayoutParams(positionOut.width(), positionOut.height());
|
||||
lp.ignoreInsets = true;
|
||||
mOriginalHeight = lp.height;
|
||||
|
||||
// Position the floating view exactly on top of the original
|
||||
lp.leftMargin = positionOut.left;
|
||||
@@ -110,29 +182,180 @@ public class FloatingIconView extends View implements Animator.AnimatorListener
|
||||
// animation frame.
|
||||
layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
|
||||
+ lp.height);
|
||||
}
|
||||
|
||||
if (v instanceof BubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) {
|
||||
// Create a copy of the app icon
|
||||
setBackground(DrawableFactory.INSTANCE.get(launcher)
|
||||
.newIcon(v.getContext(), (ItemInfoWithIcon) v.getTag()));
|
||||
@WorkerThread
|
||||
private void getIcon(Launcher launcher, View v, ItemInfo info, boolean useDrawableAsIs,
|
||||
float aspectRatio) {
|
||||
final LayoutParams lp = (LayoutParams) getLayoutParams();
|
||||
mDrawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height, useDrawableAsIs,
|
||||
new Object[1]);
|
||||
|
||||
if (ADAPTIVE_ICON_WINDOW_ANIM.get() && !useDrawableAsIs
|
||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
&& mDrawable instanceof AdaptiveIconDrawable) {
|
||||
mIsAdaptiveIcon = true;
|
||||
|
||||
AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) mDrawable;
|
||||
Drawable background = adaptiveIcon.getBackground();
|
||||
if (background == null) {
|
||||
background = new ColorDrawable(Color.TRANSPARENT);
|
||||
}
|
||||
mBackground = background;
|
||||
Drawable foreground = adaptiveIcon.getForeground();
|
||||
if (foreground == null) {
|
||||
foreground = new ColorDrawable(Color.TRANSPARENT);
|
||||
}
|
||||
mForeground = foreground;
|
||||
|
||||
int offset = getOffsetForAdaptiveIconBounds();
|
||||
mFinalDrawableBounds.set(offset, offset, lp.width - offset, mOriginalHeight - offset);
|
||||
if (mForeground instanceof ShiftedBitmapDrawable && v instanceof FolderIcon) {
|
||||
ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground;
|
||||
((FolderIcon) v).getPreviewBounds(sTmpRect);
|
||||
sbd.setShiftX(sbd.getShiftX() - sTmpRect.left);
|
||||
sbd.setShiftY(sbd.getShiftY() - sTmpRect.top);
|
||||
}
|
||||
mForeground.setBounds(mFinalDrawableBounds);
|
||||
mBackground.setBounds(mFinalDrawableBounds);
|
||||
|
||||
int blurMargin = mBlurSizeOutline / 2;
|
||||
mStartRevealRect.set(blurMargin, blurMargin , lp.width - blurMargin,
|
||||
mOriginalHeight - blurMargin);
|
||||
|
||||
if (aspectRatio > 0) {
|
||||
lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
|
||||
layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
|
||||
+ lp.height);
|
||||
setBackgroundDrawableBounds(mBgDrawableStartScale);
|
||||
}
|
||||
|
||||
// Set up outline
|
||||
mOutline.set(0, 0, lp.width, lp.height);
|
||||
setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
outline.setRoundRect(mOutline, mTaskCornerRadius);
|
||||
}
|
||||
});
|
||||
setClipToOutline(true);
|
||||
} else {
|
||||
setBackground(mDrawable);
|
||||
}
|
||||
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
invalidate();
|
||||
invalidateOutline();
|
||||
});
|
||||
}
|
||||
|
||||
private void setBackgroundDrawableBounds(float scale) {
|
||||
mBgDrawableBounds.set(mFinalDrawableBounds);
|
||||
Utilities.scaleRectAboutCenter(mBgDrawableBounds, scale);
|
||||
mBackground.setBounds(mBgDrawableBounds);
|
||||
}
|
||||
|
||||
private int getOffsetForAdaptiveIconBounds() {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O ||
|
||||
!(mDrawable instanceof AdaptiveIconDrawable)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final LayoutParams lp = (LayoutParams) getLayoutParams();
|
||||
Rect bounds = new Rect(0, 0, lp.width + mBlurSizeOutline, lp.height + mBlurSizeOutline);
|
||||
bounds.inset(mBlurSizeOutline / 2, mBlurSizeOutline / 2);
|
||||
|
||||
try (LauncherIcons li = LauncherIcons.obtain(Launcher.fromContext(getContext()))) {
|
||||
Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(mDrawable, null));
|
||||
}
|
||||
|
||||
bounds.inset(
|
||||
(int) (-bounds.width() * AdaptiveIconDrawable.getExtraInsetFraction()),
|
||||
(int) (-bounds.height() * AdaptiveIconDrawable.getExtraInsetFraction())
|
||||
);
|
||||
|
||||
return bounds.left;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClipPath(Path clipPath) {
|
||||
mClipPath = clipPath;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void drawAdaptiveIconIfExists(Canvas canvas) {
|
||||
if (mBackground != null) {
|
||||
mBackground.draw(canvas);
|
||||
}
|
||||
if (mForeground != null) {
|
||||
mForeground.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (mClipPath == null) {
|
||||
super.draw(canvas);
|
||||
drawAdaptiveIconIfExists(canvas);
|
||||
} else {
|
||||
int count = canvas.save();
|
||||
canvas.clipPath(mClipPath);
|
||||
super.draw(canvas);
|
||||
drawAdaptiveIconIfExists(canvas);
|
||||
canvas.restoreToCount(count);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animator) {}
|
||||
|
||||
/**
|
||||
* Creates a floating icon view for {@param originalView}.
|
||||
*
|
||||
* @param originalView The view to copy
|
||||
* @param hideOriginal If true, it will hide {@param originalView} while this view is visible.
|
||||
* @param useDrawableAsIs If true, we do not separate the foreground/background of adaptive
|
||||
* icons. TODO(b/122843905): We can remove this once app opening uses new animation.
|
||||
* @param aspectRatio If >= 0, we will use this aspect ratio for the initial adaptive icon size.
|
||||
* @param positionOut Rect that will hold the size and position of v.
|
||||
*/
|
||||
public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView,
|
||||
boolean hideOriginal, boolean useDrawableAsIs, float aspectRatio, Rect positionOut,
|
||||
FloatingIconView recycle) {
|
||||
FloatingIconView view = recycle != null ? recycle : new FloatingIconView(launcher);
|
||||
|
||||
// Match the position of the original view.
|
||||
view.matchPositionOf(launcher, originalView, positionOut);
|
||||
|
||||
// Get the drawable on the background thread
|
||||
// Must be called after matchPositionOf so that we know what size to load.
|
||||
if (originalView.getTag() instanceof ItemInfo) {
|
||||
new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
|
||||
view.getIcon(launcher, originalView, (ItemInfo) originalView.getTag(),
|
||||
useDrawableAsIs, aspectRatio);
|
||||
});
|
||||
}
|
||||
|
||||
// We need to add it to the overlay, but keep it invisible until animation starts..
|
||||
final DragLayer dragLayer = launcher.getDragLayer();
|
||||
setVisibility(INVISIBLE);
|
||||
((ViewGroup) dragLayer.getParent()).getOverlay().add(this);
|
||||
view.setVisibility(INVISIBLE);
|
||||
((ViewGroup) dragLayer.getParent()).getOverlay().add(view);
|
||||
|
||||
setRunnables(() -> {
|
||||
setVisibility(VISIBLE);
|
||||
if (hideOriginal) {
|
||||
v.setVisibility(INVISIBLE);
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
((ViewGroup) dragLayer.getParent()).getOverlay().remove(this);
|
||||
if (hideOriginal) {
|
||||
v.setVisibility(VISIBLE);
|
||||
}
|
||||
});
|
||||
view.mStartRunnable = () -> {
|
||||
view.setVisibility(VISIBLE);
|
||||
if (hideOriginal) {
|
||||
originalView.setVisibility(INVISIBLE);
|
||||
}
|
||||
};
|
||||
view.mEndRunnable = () -> {
|
||||
((ViewGroup) dragLayer.getParent()).getOverlay().remove(view);
|
||||
if (hideOriginal) {
|
||||
originalView.setVisibility(VISIBLE);
|
||||
}
|
||||
};
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,8 +93,6 @@ public final class LauncherInstrumentation {
|
||||
private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
|
||||
|
||||
private final UiDevice mDevice;
|
||||
private final boolean mSwipeUpEnabled;
|
||||
private Boolean mSwipeUpEnabledOverride = null;
|
||||
private final Instrumentation mInstrumentation;
|
||||
private int mExpectedRotation = Surface.ROTATION_0;
|
||||
|
||||
@@ -104,13 +102,6 @@ public final class LauncherInstrumentation {
|
||||
public LauncherInstrumentation(Instrumentation instrumentation) {
|
||||
mInstrumentation = instrumentation;
|
||||
mDevice = UiDevice.getInstance(instrumentation);
|
||||
final boolean swipeUpEnabledDefaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
|
||||
mSwipeUpEnabled = SwipeUpSetting.isSwipeUpSettingAvailable() ?
|
||||
Settings.Secure.getInt(
|
||||
instrumentation.getTargetContext().getContentResolver(),
|
||||
SWIPE_UP_SETTING_NAME,
|
||||
swipeUpEnabledDefaultValue ? 1 : 0) == 1 :
|
||||
swipeUpEnabledDefaultValue;
|
||||
|
||||
// Launcher should run in test harness so that custom accessibility protocol between
|
||||
// Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call
|
||||
@@ -119,17 +110,18 @@ public final class LauncherInstrumentation {
|
||||
TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness());
|
||||
}
|
||||
|
||||
// Used only by TaplTests.
|
||||
public void overrideSwipeUpEnabled(Boolean swipeUpEnabledOverride) {
|
||||
mSwipeUpEnabledOverride = swipeUpEnabledOverride;
|
||||
}
|
||||
|
||||
void setActiveContainer(VisibleContainer container) {
|
||||
sActiveContainer = new WeakReference<>(container);
|
||||
}
|
||||
|
||||
public boolean isSwipeUpEnabled() {
|
||||
return mSwipeUpEnabledOverride != null ? mSwipeUpEnabledOverride : mSwipeUpEnabled;
|
||||
final boolean swipeUpEnabledDefaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
|
||||
return SwipeUpSetting.isSwipeUpSettingAvailable() ?
|
||||
Settings.Secure.getInt(
|
||||
mInstrumentation.getTargetContext().getContentResolver(),
|
||||
SWIPE_UP_SETTING_NAME,
|
||||
swipeUpEnabledDefaultValue ? 1 : 0) == 1 :
|
||||
swipeUpEnabledDefaultValue;
|
||||
}
|
||||
|
||||
static void log(String message) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.test.uiautomator.Until;
|
||||
* A recent task in the overview panel carousel.
|
||||
*/
|
||||
public final class OverviewTask {
|
||||
static final int FLING_SPEED = 3000;
|
||||
private final LauncherInstrumentation mLauncher;
|
||||
private final UiObject2 mTask;
|
||||
private final BaseOverview mOverview;
|
||||
@@ -45,7 +46,7 @@ public final class OverviewTask {
|
||||
public void dismiss() {
|
||||
verifyActiveContainer();
|
||||
// Dismiss the task via flinging it up.
|
||||
mTask.fling(Direction.DOWN);
|
||||
mTask.fling(Direction.DOWN, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
|
||||
mLauncher.waitForIdle();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user