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:
Tony Wickham
2019-03-06 10:26:28 -08:00
70 changed files with 1769 additions and 784 deletions
+1 -1
View File
@@ -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();
+2 -1
View File
@@ -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"/>
@@ -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);
}
}
@@ -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;
@@ -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
@@ -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();
}
}
@@ -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);
}
}
@@ -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);
@@ -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() {
@@ -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);
+4 -1
View File
@@ -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;
+9 -9
View File
@@ -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>
+2 -2
View File
@@ -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>
+4 -7
View File
@@ -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;
+4 -1
View File
@@ -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");
+15 -14
View File
@@ -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) {
+2 -1
View File
@@ -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();
}
+74 -9
View File
@@ -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;
}
}
}
+8 -2
View File
@@ -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;
}
}
}
+3 -1
View File
@@ -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();
}