Merge branch 'ub-launcher3-master' into pi-dev

am: 6303a794df

Change-Id: I66f6a79666134e1d8398ea5d726ba45ae184ce49
This commit is contained in:
Jon
2018-03-27 14:46:18 +00:00
committed by android-build-merger
329 changed files with 9058 additions and 9358 deletions
+5 -3
View File
@@ -87,9 +87,11 @@
android:process=":wallpaper_chooser"
android:permission="android.permission.BIND_JOB_SERVICE" />
<service android:name="com.android.launcher3.notification.NotificationListener"
android:enabled="@bool/notification_badging_enabled"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<service
android:name="com.android.launcher3.notification.NotificationListener"
android:label="@string/icon_badging_service_title"
android:enabled="@bool/notification_badging_enabled"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
+1 -1
View File
@@ -71,7 +71,7 @@
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="nosensor"
android:screenOrientation="unspecified"
android:configChanges="keyboard|keyboardHidden|navigation"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
+6 -1
View File
@@ -39,7 +39,7 @@
public int getY();
}
-keep class com.android.launcher3.dragndrop.DragLayer$LayoutParams {
-keep class com.android.launcher3.views.BaseDragLayer$LayoutParams {
public void setWidth(int);
public int getWidth();
public void setHeight(int);
@@ -102,6 +102,11 @@
public <init>(...);
}
# InstantAppResolver
-keep class com.android.quickstep.InstantAppResolverImpl {
public <init>(...);
}
-keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** {
*;
}
+22 -3
View File
@@ -36,13 +36,32 @@
android:restoreAnyVersion="true"
android:supportsRtl="true" >
<service android:name="com.android.quickstep.TouchInteractionService"
android:exported="true" />
<service
android:name="com.android.quickstep.TouchInteractionService"
android:permission="android.permission.STATUS_BAR_SERVICE" >
<intent-filter>
<action android:name="android.intent.action.QUICKSTEP_SERVICE" />
</intent-filter>
</service>
<!-- STOPSHIP: Change exported to false once all the integration is complete.
It is set to true so that the activity can be started from command line -->
<activity android:name="com.android.quickstep.RecentsActivity"
android:exported="true" />
android:exported="true"
android:excludeFromRecents="true" />
<!-- Content provider to settings search -->
<provider
android:name="com.android.quickstep.LauncherSearchIndexablesProvider"
android:authorities="com.android.launcher3"
android:grantUriPermissions="true"
android:multiprocess="true"
android:permission="android.permission.READ_SEARCH_INDEXABLES"
android:exported="true">
<intent-filter>
<action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" />
</intent-filter>
</provider>
</application>
</manifest>
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 B

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.quickstep.RecentsRootView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.android.quickstep.FallbackRecentsView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/overview_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:theme="@style/HomeScreenElementTheme" />
</com.android.quickstep.RecentsRootView>
+4 -8
View File
@@ -14,18 +14,14 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.quickstep.RecentsView
<com.android.quickstep.views.LauncherRecentsView
xmlns:android="http://schemas.android.com/apk/res/android"
android:theme="@style/HomeScreenElementTheme"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:alpha="0.0"
android:visibility="invisible" >
android:visibility="invisible"
android:focusableInTouchMode="true" >
<com.android.launcher3.uioverrides.WorkspaceCard
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.quickstep.RecentsView>
</com.android.quickstep.views.LauncherRecentsView>
+3 -3
View File
@@ -13,12 +13,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.quickstep.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
<com.android.quickstep.views.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="4dp">
<com.android.quickstep.TaskThumbnailView
<com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -29,4 +29,4 @@
android:layout_width="@dimen/task_thumbnail_icon_size"
android:layout_height="@dimen/task_thumbnail_icon_size"
android:layout_gravity="top|center_horizontal" />
</com.android.quickstep.TaskView>
</com.android.quickstep.views.TaskView>
+2 -2
View File
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.quickstep.TaskMenuView
<com.android.quickstep.views.TaskMenuView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="wrap_content"
@@ -33,4 +33,4 @@
android:paddingTop="18dp"
android:drawablePadding="8dp"
android:gravity="center_horizontal"/>
</com.android.quickstep.TaskMenuView>
</com.android.quickstep.views.TaskMenuView>
+4 -8
View File
@@ -16,8 +16,6 @@
<resources>
<dimen name="options_menu_icon_size">24dp</dimen>
<dimen name="task_thumbnail_top_margin">24dp</dimen>
<dimen name="task_thumbnail_icon_size">48dp</dimen>
<dimen name="task_menu_background_radius">12dp</dimen>
@@ -25,19 +23,17 @@
<dimen name="task_fade_length">20dp</dimen>
<dimen name="recents_page_spacing">10dp</dimen>
<!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
loading full resolution screenshots. -->
<dimen name="recents_fast_fling_velocity">600dp</dimen>
<dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
<dimen name="quickstep_fling_min_velocity">250dp</dimen>
<dimen name="workspace_overview_offset_x">-24dp</dimen>
<!-- TODO: This can be calculated using other resource values -->
<dimen name="all_apps_search_box_full_height">90dp</dimen>
<!-- Launcher app transition -->
<dimen name="content_trans_y">25dp</dimen>
<dimen name="workspace_trans_y">80dp</dimen>
<dimen name="recents_adjacent_trans_x">120dp</dimen>
<dimen name="recents_adjacent_trans_x">140dp</dimen>
<dimen name="recents_adjacent_trans_y">80dp</dimen>
<fraction name="recents_adjacent_scale">150%</fraction>
</resources>
+2
View File
@@ -16,5 +16,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
<string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
</resources>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 Google Inc.
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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreference
android:key="pref_add_icon_to_home"
android:title="@string/auto_add_shortcuts_label"
android:summary="@string/auto_add_shortcuts_description"
android:defaultValue="true"
/>
<ListPreference
android:key="pref_override_icon_shape"
android:title="@string/icon_shape_override_label"
android:summary="@string/icon_shape_override_label_location"
android:entries="@array/icon_shape_override_paths_names"
android:entryValues="@array/icon_shape_override_paths_values"
android:defaultValue=""
android:persistent="false" />
</PreferenceScreen>
@@ -15,27 +15,92 @@
*/
package com.android.launcher3;
import static com.android.systemui.shared.recents.utilities.Utilities
.postAtFrontOfQueueAsynchronously;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.BinderThread;
import android.support.annotation.UiThread;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
@TargetApi(Build.VERSION_CODES.P)
public abstract class LauncherAnimationRunner extends AnimatorListenerAdapter
implements RemoteAnimationRunnerCompat {
public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
private static final int REFRESH_RATE_MS = 16;
AnimatorSet mAnimator;
private Launcher mLauncher;
private final Handler mHandler;
LauncherAnimationRunner(Launcher launcher) {
mLauncher = launcher;
private Runnable mSysFinishRunnable;
private AnimatorSet mAnimator;
public LauncherAnimationRunner(Handler handler) {
mHandler = handler;
}
@BinderThread
@Override
public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats, Runnable runnable) {
postAtFrontOfQueueAsynchronously(mHandler, () -> {
// Finish any previous animation
finishSystemAnimation();
mSysFinishRunnable = runnable;
mAnimator = getAnimator(targetCompats);
if (mAnimator == null) {
finishSystemAnimation();
return;
}
mAnimator.addListener(this);
mAnimator.start();
// Because t=0 has the app icon in its original spot, we can skip the
// first frame and have the same movement one frame earlier.
mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
});
}
@UiThread
public abstract AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats);
@UiThread
@Override
public void onAnimationEnd(Animator animation) {
if (animation == mAnimator) {
mAnimator = null;
finishSystemAnimation();
}
}
/**
* Called by the system
*/
@BinderThread
@Override
public void onAnimationCancelled() {
postAtFrontOfQueueAsynchronously(mLauncher.getWindow().getDecorView().getHandler(), () -> {
postAtFrontOfQueueAsynchronously(mHandler, () -> {
if (mAnimator != null) {
mAnimator.cancel();
mAnimator.removeListener(this);
mAnimator.end();
mAnimator = null;
}
});
}
@UiThread
private void finishSystemAnimation() {
if (mSysFinishRunnable != null) {
mSysFinishRunnable.run();
mSysFinishRunnable = null;
}
}
}
@@ -16,11 +16,11 @@
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.NORMAL;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -31,13 +31,16 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Surface;
import android.view.View;
@@ -47,14 +50,18 @@ import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.InsettableFrameLayout.LayoutParams;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.shortcuts.DeepShortcutTextView;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.quickstep.RecentsAnimationInterpolator;
import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
import com.android.quickstep.RecentsView;
import com.android.quickstep.TaskView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.Task;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -73,13 +80,16 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
implements OnDeviceProfileChangeListener {
private static final String TAG = "LauncherTransition";
private static final int REFRESH_RATE_MS = 16;
private static final int STATUS_BAR_TRANSITION_DURATION = 120;
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
private static final int APP_LAUNCH_DURATION = 500;
// Use a shorter duration for x or y translation to create a curve effect
private static final int APP_LAUNCH_CURVED_DURATION = 233;
private static final int RECENTS_LAUNCH_DURATION = 336;
private static final int LAUNCHER_RESUME_START_DELAY = 150;
private static final int LAUNCHER_RESUME_START_DELAY = 100;
private static final int CLOSING_TRANSITION_DURATION_MS = 350;
// Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
@@ -88,7 +98,9 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
private final DragLayer mDragLayer;
private final Launcher mLauncher;
private DeviceProfile mDeviceProfile;
private final Handler mHandler;
private final boolean mIsRtl;
private final float mContentTransY;
private final float mWorkspaceTransY;
@@ -96,17 +108,23 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
private final float mRecentsTransY;
private final float mRecentsScale;
private DeviceProfile mDeviceProfile;
private View mFloatingView;
private boolean mIsRtl;
private LauncherTransitionAnimator mCurrentAnimator;
private final AnimatorListenerAdapter mReapplyStateListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLauncher.getStateManager().reapplyState();
}
};
public LauncherAppTransitionManagerImpl(Context context) {
mLauncher = Launcher.getLauncher(context);
mDragLayer = mLauncher.getDragLayer();
mHandler = new Handler(Looper.getMainLooper());
mIsRtl = Utilities.isRtl(mLauncher.getResources());
mDeviceProfile = mLauncher.getDeviceProfile();
mIsRtl = Utilities.isRtl(mLauncher.getResources());
Resources res = mLauncher.getResources();
mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
@@ -124,26 +142,6 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
mDeviceProfile = dp;
}
private void setCurrentAnimator(LauncherTransitionAnimator animator) {
if (isAnimating()) {
mCurrentAnimator.cancel();
}
mCurrentAnimator = animator;
}
@Override
public void finishLauncherAnimation() {
if (isAnimating()) {
mCurrentAnimator.finishLauncherAnimation();
}
mCurrentAnimator = null;
}
@Override
public boolean isAnimating() {
return mCurrentAnimator != null && mCurrentAnimator.isRunning();
}
/**
* @return ActivityOptions with remote animations that controls how the window of the opening
* targets are displayed.
@@ -152,57 +150,28 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
if (hasControlRemoteAppTransitionPermission()) {
try {
RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler) {
@Override
public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
Runnable finishedCallback) {
// Post at front of queue ignoring sync barriers to make sure it gets
// processed before the next frame.
postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
final boolean removeTrackingView;
LauncherTransitionAnimator animator =
composeRecentsLaunchAnimator(v, targets);
if (animator != null) {
// We are animating the task view directly, do not remove it after
removeTrackingView = false;
} else {
animator = composeAppLaunchAnimator(v, targets);
// A new floating view is created for the animation, remove it after
removeTrackingView = true;
}
setCurrentAnimator(animator);
mAnimator = animator.getAnimatorSet();
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Reset launcher to normal state
v.setVisibility(View.VISIBLE);
if (removeTrackingView) {
((ViewGroup) mDragLayer.getParent()).removeView(
mFloatingView);
}
mDragLayer.setAlpha(1f);
mDragLayer.setTranslationY(0f);
View appsView = mLauncher.getAppsView();
appsView.setAlpha(1f);
appsView.setTranslationY(0f);
finishedCallback.run();
}
});
mAnimator.start();
// Because t=0 has the app icon in its original spot, we can skip the
// first frame and have the same movement one frame earlier.
mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
});
public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
Animator[] anims = composeRecentsLaunchAnimator(v, targetCompats);
AnimatorSet anim = new AnimatorSet();
if (anims != null) {
anim.playTogether(anims);
} else {
anim.play(getLauncherAnimators(v, targetCompats));
anim.play(getWindowAnimators(v, targetCompats));
}
mLauncher.getStateManager().setCurrentAnimation(anim);
return anim;
}
};
return ActivityOptionsCompat.makeRemoteAnimation(
new RemoteAnimationAdapterCompat(runner, 500, 380));
int duration = findTaskViewToLaunch(launcher, v, null) != null
? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION;
int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION;
return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
runner, duration, statusBarTransitionDelay));
} catch (NoClassDefFoundError e) {
// Gracefully fall back to default launch options if the user's platform doesn't
// have the latest changes.
@@ -212,19 +181,44 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
}
/**
* Composes the animations for a launch from the recents list if possible.
* Try to find a TaskView that corresponds with the component of the launched view.
*
* If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
* Otherwise, we will assume we are using a normal app transition, but it's possible that the
* opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
*/
private LauncherTransitionAnimator composeRecentsLaunchAnimator(View v,
RemoteAnimationTargetCompat[] targets) {
// Ensure recents is actually visible
if (!mLauncher.isInState(LauncherState.OVERVIEW)) {
return null;
private TaskView findTaskViewToLaunch(
BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
if (v instanceof TaskView) {
return (TaskView) v;
}
RecentsView recentsView = activity.getOverviewPanel();
// It's possible that the launched view can still be resolved to a visible task view, check
// the task id of the opening task and see if we can find a match.
if (v.getTag() instanceof ItemInfo) {
ItemInfo itemInfo = (ItemInfo) v.getTag();
ComponentName componentName = itemInfo.getTargetComponent();
if (componentName != null) {
for (int i = 0; i < recentsView.getChildCount(); i++) {
TaskView taskView = (TaskView) recentsView.getPageAt(i);
if (recentsView.isTaskViewVisible(taskView)) {
Task task = taskView.getTask();
if (componentName.equals(task.key.getComponent())) {
return taskView;
}
}
}
}
}
if (targets == null) {
return null;
}
// Resolve the opening task id
int openingTaskId = -1;
for (RemoteAnimationTargetCompat target : targets) {
if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
if (target.mode == MODE_OPENING) {
openingTaskId = target.taskId;
break;
}
@@ -237,15 +231,56 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
// If the opening task id is not currently visible in overview, then fall back to normal app
// icon launch animation
RecentsView recentsView = mLauncher.getOverviewPanel();
TaskView taskView = recentsView.getTaskView(openingTaskId);
if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
return null;
}
return taskView;
}
/**
* Composes the animations for a launch from the recents list if possible.
*/
private Animator[] composeRecentsLaunchAnimator(View v,
RemoteAnimationTargetCompat[] targets) {
// Ensure recents is actually visible
if (!mLauncher.getStateManager().getState().overviewUi) {
return null;
}
RecentsView recentsView = mLauncher.getOverviewPanel();
boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
boolean skipLauncherChanges = !launcherClosing;
TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets);
if (taskView == null) {
return null;
}
// Found a visible recents task that matches the opening app, lets launch the app from there
return new LauncherTransitionAnimator(getRecentsLauncherAnimator(recentsView, taskView),
getRecentsWindowAnimator(taskView, targets));
Animator launcherAnim;
final AnimatorListenerAdapter windowAnimEndListener;
if (launcherClosing) {
launcherAnim = getRecentsLauncherAnimator(recentsView, taskView);
// Make sure recents gets fixed up by resetting task alphas and scales, etc.
windowAnimEndListener = mReapplyStateListener;
} else {
AnimatorPlaybackController controller =
mLauncher.getStateManager()
.createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION);
controller.dispatchOnStart();
launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
windowAnimEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLauncher.getStateManager().goToState(NORMAL, false);
}
};
}
Animator windowAnim = getRecentsWindowAnimator(taskView, skipLauncherChanges, targets);
windowAnim.addListener(windowAnimEndListener);
return new Animator[] {launcherAnim, windowAnim};
}
/**
@@ -260,15 +295,16 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
int launchedTaskIndex = recentsView.indexOfChild(v);
int centerTaskIndex = recentsView.getCurrentPage();
boolean launchingCenterTask = launchedTaskIndex == centerTaskIndex;
boolean isRtl = recentsView.isRtl();
if (launchingCenterTask) {
if (launchedTaskIndex - 1 >= recentsView.getFirstTaskIndex()) {
if (launchedTaskIndex - 1 >= 0) {
TaskView adjacentPage1 = (TaskView) recentsView.getPageAt(launchedTaskIndex - 1);
ObjectAnimator adjacentTask1ScaleAndTranslate =
LauncherAnimUtils.ofPropertyValuesHolder(adjacentPage1,
new PropertyListBuilder()
.scale(adjacentPage1.getScaleX() * mRecentsScale)
.translationY(mRecentsTransY)
.translationX(mIsRtl ? mRecentsTransX : -mRecentsTransX)
.translationX(isRtl ? mRecentsTransX : -mRecentsTransX)
.build());
launcherAnimator.play(adjacentTask1ScaleAndTranslate);
}
@@ -279,33 +315,31 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
new PropertyListBuilder()
.scale(adjacentTask2.getScaleX() * mRecentsScale)
.translationY(mRecentsTransY)
.translationX(mIsRtl ? -mRecentsTransX : mRecentsTransX)
.translationX(isRtl ? -mRecentsTransX : mRecentsTransX)
.build());
launcherAnimator.play(adjacentTask2ScaleAndTranslate);
}
} else if (centerTaskIndex >= recentsView.getFirstTaskIndex()) {
} else {
// We are launching an adjacent task, so parallax the center and other adjacent task.
TaskView centerTask = (TaskView) recentsView.getPageAt(centerTaskIndex);
float translationX = Math.abs(v.getTranslationX());
ObjectAnimator centerTaskParallaxToRight =
float translationX = mRecentsTransX / 2;
ObjectAnimator centerTaskParallaxOffscreen =
LauncherAnimUtils.ofPropertyValuesHolder(centerTask,
new PropertyListBuilder()
.scale(v.getScaleX())
.translationX(mIsRtl ? -translationX : translationX)
.translationX(isRtl ? -translationX : translationX)
.build());
launcherAnimator.play(centerTaskParallaxToRight);
launcherAnimator.play(centerTaskParallaxOffscreen);
int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - launchedTaskIndex);
if (otherAdjacentTaskIndex >= recentsView.getFirstTaskIndex()
if (otherAdjacentTaskIndex >= 0
&& otherAdjacentTaskIndex < recentsView.getPageCount()) {
TaskView otherAdjacentTask = (TaskView) recentsView.getPageAt(
otherAdjacentTaskIndex);
ObjectAnimator otherAdjacentTaskParallaxToRight =
ObjectAnimator otherAdjacentTaskParallaxOffscreen =
LauncherAnimUtils.ofPropertyValuesHolder(otherAdjacentTask,
new PropertyListBuilder()
.translationX(otherAdjacentTask.getTranslationX()
+ (mIsRtl ? -translationX : translationX))
.translationX(isRtl ? -translationX : translationX)
.build());
launcherAnimator.play(otherAdjacentTaskParallaxToRight);
launcherAnimator.play(otherAdjacentTaskParallaxOffscreen);
}
}
@@ -314,7 +348,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
launcherAnimator.play(allAppsSlideOut);
Workspace workspace = mLauncher.getWorkspace();
float[] workspaceScaleAndTranslation = LauncherState.NORMAL
float[] workspaceScaleAndTranslation = NORMAL
.getWorkspaceScaleAndTranslation(mLauncher);
Animator recenterWorkspace = LauncherAnimUtils.ofPropertyValuesHolder(
workspace, new PropertyListBuilder()
@@ -324,9 +358,6 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
launcherAnimator.play(recenterWorkspace);
CellLayout currentWorkspacePage = (CellLayout) workspace.getPageAt(
workspace.getCurrentPage());
Animator hideWorkspaceScrim = ObjectAnimator.ofInt(
currentWorkspacePage.getScrimBackground(), DRAWABLE_ALPHA, 0);
launcherAnimator.play(hideWorkspaceScrim);
launcherAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
launcherAnimator.setDuration(RECENTS_LAUNCH_DURATION);
@@ -337,7 +368,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
* @return Animator that controls the window of the opening targets for the recents launch
* animation.
*/
private ValueAnimator getRecentsWindowAnimator(TaskView v,
private ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipLauncherChanges,
RemoteAnimationTargetCompat[] targets) {
Rect taskViewBounds = new Rect();
mDragLayer.getDescendantRectRelativeToSelf(v, taskViewBounds);
@@ -373,22 +404,23 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
final float percent = animation.getAnimatedFraction();
TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
v.setScaleX(tw.taskScale);
v.setScaleY(tw.taskScale);
v.setTranslationX(tw.taskX);
v.setTranslationY(tw.taskY);
// Defer fading out the view until after the app window gets faded in
v.setAlpha(getValue(1f, 0f, 75, 75,
appAnimator.getDuration() * percent, Interpolators.LINEAR));
if (!skipLauncherChanges) {
v.setScaleX(tw.taskScale);
v.setScaleY(tw.taskScale);
v.setTranslationX(tw.taskX);
v.setTranslationY(tw.taskY);
// Defer fading out the view until after the app window gets faded in
v.setAlpha(getValue(1f, 0f, 75, 75,
appAnimator.getDuration() * percent, Interpolators.LINEAR));
}
matrix.setScale(tw.winScale, tw.winScale);
matrix.postTranslate(tw.winX, tw.winY);
crop.set(tw.winCrop);
// Fade in the app window.
float alphaDelay = 0;
float alphaDuration = 75;
float alpha = getValue(0f, 1f, alphaDelay, alphaDuration,
float alpha = getValue(0f, 1f, 0, alphaDuration,
appAnimator.getDuration() * percent, Interpolators.LINEAR);
TransactionCompat t = new TransactionCompat();
@@ -401,7 +433,10 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
matrix.postTranslate(target.position.x, target.position.y);
t.setMatrix(target.leash, matrix);
t.setWindowCrop(target.leash, crop);
t.deferTransactionUntil(target.leash, surface, getNextFrameNumber(surface));
if (!skipLauncherChanges) {
t.deferTransactionUntil(target.leash, surface, frameNumber);
}
}
if (isFirstFrame) {
t.show(target.leash);
@@ -413,25 +448,9 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
isFirstFrame = false;
}
});
appAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Make sure recents gets fixed up by resetting task alphas and scales, etc.
mLauncher.getStateManager().reapplyState();
}
});
return appAnimator;
}
/**
* Composes the animations for a launch from an app icon.
*/
private LauncherTransitionAnimator composeAppLaunchAnimator(View v,
RemoteAnimationTargetCompat[] targets) {
return new LauncherTransitionAnimator(getLauncherAnimators(v, targets),
getWindowAnimators(v, targets));
}
/**
* @return Animators that control the movements of the Launcher and icon of the opening target.
*/
@@ -462,7 +481,9 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
if (mLauncher.isInState(LauncherState.ALL_APPS) && !mDeviceProfile.isVerticalBarLayout()) {
// All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
View appsView = mLauncher.getAppsView();
final View appsView = mLauncher.getAppsView();
final float startAlpha = appsView.getAlpha();
final float startY = appsView.getTranslationY();
appsView.setAlpha(alphas[0]);
appsView.setTranslationY(trans[0]);
@@ -475,6 +496,14 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
launcherAnimator.play(alpha);
launcherAnimator.play(transY);
launcherAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
appsView.setAlpha(startAlpha);
appsView.setTranslationY(startY);
}
});
} else {
mDragLayer.setAlpha(alphas[0]);
mDragLayer.setTranslationY(trans[0]);
@@ -489,6 +518,13 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
launcherAnimator.play(dragLayerAlpha);
launcherAnimator.play(dragLayerTransY);
launcherAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mDragLayer.setAlpha(1);
mDragLayer.setTranslationY(0);
}
});
}
return launcherAnimator;
}
@@ -497,33 +533,56 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
* @return Animator that controls the icon used to launch the target.
*/
private AnimatorSet getIconAnimator(View v) {
boolean isBubbleTextView = v instanceof BubbleTextView;
final boolean isBubbleTextView = v instanceof BubbleTextView;
mFloatingView = new View(mLauncher);
if (isBubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) {
// Create a copy of the app icon
ItemInfoWithIcon info = (ItemInfoWithIcon) v.getTag();
FastBitmapDrawable d = DrawableFactory.get(mLauncher).newIcon(info);
d.setIsDisabled(info.isDisabled());
mFloatingView.setBackground(d);
mFloatingView.setBackground(
DrawableFactory.get(mLauncher).newIcon((ItemInfoWithIcon) v.getTag()));
}
// Position the floating view exactly on top of the original
Rect rect = new Rect();
mDragLayer.getDescendantRectRelativeToSelf(v, rect);
int viewLocationStart = mIsRtl
? mDeviceProfile.widthPx - rect.right
: rect.left;
final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
if (fromDeepShortcutView) {
// Deep shortcut views have their icon drawn in a separate view.
DeepShortcutView view = (DeepShortcutView) v.getParent();
mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
} else {
mDragLayer.getDescendantRectRelativeToSelf(v, rect);
}
int viewLocationLeft = rect.left;
int viewLocationTop = rect.top;
if (isBubbleTextView) {
((BubbleTextView) v).getIconBounds(rect);
float startScale = 1f;
if (isBubbleTextView && !fromDeepShortcutView) {
BubbleTextView btv = (BubbleTextView) v;
btv.getIconBounds(rect);
Drawable dr = btv.getIcon();
if (dr instanceof FastBitmapDrawable) {
startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
}
} else {
rect.set(0, 0, rect.width(), rect.height());
}
viewLocationLeft += rect.left;
viewLocationTop += rect.top;
int viewLocationStart = mIsRtl
? mDeviceProfile.widthPx - rect.right
: viewLocationLeft;
LayoutParams lp = new LayoutParams(rect.width(), rect.height());
lp.ignoreInsets = true;
lp.setMarginStart(viewLocationStart + rect.left);
lp.topMargin = viewLocationTop + rect.top;
lp.setMarginStart(viewLocationStart);
lp.topMargin = viewLocationTop;
mFloatingView.setLayoutParams(lp);
// Set the properties here already to make sure they'are available when running the first
// animation frame.
mFloatingView.setLeft(viewLocationLeft);
mFloatingView.setTop(viewLocationTop);
mFloatingView.setRight(viewLocationLeft + rect.width());
mFloatingView.setBottom(viewLocationTop + rect.height());
// Swap the two views in place.
((ViewGroup) mDragLayer.getParent()).addView(mFloatingView);
v.setVisibility(View.INVISIBLE);
@@ -544,8 +603,8 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
// Adjust the duration to change the "curve" of the app icon to the center.
boolean isBelowCenterY = lp.topMargin < centerY;
x.setDuration(isBelowCenterY ? 500 : 233);
y.setDuration(isBelowCenterY ? 233 : 500);
x.setDuration(isBelowCenterY ? APP_LAUNCH_DURATION : APP_LAUNCH_CURVED_DURATION);
y.setDuration(isBelowCenterY ? APP_LAUNCH_CURVED_DURATION : APP_LAUNCH_DURATION);
x.setInterpolator(Interpolators.AGGRESSIVE_EASE);
y.setInterpolator(Interpolators.AGGRESSIVE_EASE);
appIconAnimatorSet.play(x);
@@ -556,14 +615,10 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
float maxScaleX = mDeviceProfile.widthPx / (float) rect.width();
float maxScaleY = mDeviceProfile.heightPx / (float) rect.height();
float scale = Math.max(maxScaleX, maxScaleY);
ObjectAnimator sX = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_X, 1f, scale);
ObjectAnimator sY = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_Y, 1f, scale);
sX.setDuration(500);
sY.setDuration(500);
sX.setInterpolator(Interpolators.EXAGGERATED_EASE);
sY.setInterpolator(Interpolators.EXAGGERATED_EASE);
appIconAnimatorSet.play(sX);
appIconAnimatorSet.play(sY);
ObjectAnimator scaleAnim = ObjectAnimator
.ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
scaleAnim.setDuration(APP_LAUNCH_DURATION).setInterpolator(Interpolators.EXAGGERATED_EASE);
appIconAnimatorSet.play(scaleAnim);
// Fade out the app icon.
ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
@@ -572,6 +627,14 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
alpha.setInterpolator(Interpolators.LINEAR);
appIconAnimatorSet.play(alpha);
appIconAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Reset launcher to normal state
v.setVisibility(View.VISIBLE);
((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
}
});
return appIconAnimatorSet;
}
@@ -580,7 +643,11 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
*/
private ValueAnimator getWindowAnimators(View v, RemoteAnimationTargetCompat[] targets) {
Rect bounds = new Rect();
if (v instanceof BubbleTextView) {
if (v.getParent() instanceof DeepShortcutView) {
// Deep shortcut views have their icon drawn in a separate view.
DeepShortcutView view = (DeepShortcutView) v.getParent();
mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), bounds);
} else if (v instanceof BubbleTextView) {
((BubbleTextView) v).getIconBounds(bounds);
} else {
mDragLayer.getDescendantRectRelativeToSelf(v, bounds);
@@ -591,7 +658,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
Matrix matrix = new Matrix();
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
appAnimator.setDuration(500);
appAnimator.setDuration(APP_LAUNCH_DURATION);
appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
boolean isFirstFrame = true;
@@ -631,9 +698,8 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
matrix.postTranslate(transX0, transY0);
// Fade in the app window.
float alphaDelay = 0;
float alphaDuration = 60;
float alpha = getValue(0f, 1f, alphaDelay, alphaDuration,
float alpha = getValue(0f, 1f, 0, alphaDuration,
appAnimator.getDuration() * percent, Interpolators.LINEAR);
// Animate the window crop so that it starts off as a square, and then reveals
@@ -678,6 +744,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
try {
RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(),
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
@@ -705,40 +772,25 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
* ie. pressing home, swiping up from nav bar.
*/
private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
return new LauncherAnimationRunner(mLauncher) {
return new LauncherAnimationRunner(mHandler) {
@Override
public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
Runnable finishedCallback) {
Handler handler = mLauncher.getWindow().getDecorView().getHandler();
postAtFrontOfQueueAsynchronously(handler, () -> {
if ((Utilities.getPrefs(mLauncher)
.getBoolean("pref_use_screenshot_for_swipe_up", false)
&& mLauncher.isInState(LauncherState.OVERVIEW))
|| !launcherIsATargetWithMode(targets, MODE_OPENING)) {
// We use a separate transition for Overview mode. And we can skip the
// animation in cases where Launcher is not in the set of opening targets.
// This can happen when Launcher is already visible. ie. Closing a dialog.
setCurrentAnimator(null);
finishedCallback.run();
return;
}
public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
if (mLauncher.getStateManager().getState().overviewUi) {
// We use a separate transition for Overview mode.
return null;
}
LauncherTransitionAnimator animator = new LauncherTransitionAnimator(
getLauncherResumeAnimation(), getClosingWindowAnimators(targets));
setCurrentAnimator(animator);
mAnimator = animator.getAnimatorSet();
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finishedCallback.run();
}
});
mAnimator.start();
AnimatorSet anim = new AnimatorSet();
anim.play(getClosingWindowAnimators(targetCompats));
// Because t=0 has the app icon in its original spot, we can skip the
// first frame and have the same movement one frame earlier.
mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
});
if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)) {
AnimatorSet contentAnimation = getLauncherResumeAnimation();
anim.play(contentAnimation);
// Only register the content animation for cancellation when state changes
mLauncher.getStateManager().setCurrentAnimation(contentAnimation);
}
return anim;
}
};
}
@@ -750,7 +802,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
Matrix matrix = new Matrix();
float height = mLauncher.getDeviceProfile().heightPx;
float width = mLauncher.getDeviceProfile().widthPx;
float endX = (Utilities.isRtl(mLauncher.getResources()) ? -width : width) * 1.16f;
float endX = (mLauncher.<RecentsView>getOverviewPanel().isRtl() ? -width : width) * 1.16f;
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
closingAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
@@ -809,12 +861,17 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
return contentAnimator;
} else {
AnimatorSet workspaceAnimator = new AnimatorSet();
mLauncher.getWorkspace().setTranslationY(mWorkspaceTransY);
mLauncher.getWorkspace().setAlpha(0f);
workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(),
View.TRANSLATION_Y, mWorkspaceTransY, 0));
workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(), View.ALPHA,
0, 1f));
View currentPage = ((CellLayout) mLauncher.getWorkspace()
.getChildAt(mLauncher.getWorkspace().getCurrentPage()))
.getShortcutsAndWidgets();
currentPage.setAlpha(0f);
workspaceAnimator.play(ObjectAnimator.ofFloat(currentPage, View.ALPHA, 0, 1f));
workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
workspaceAnimator.setDuration(333);
workspaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -842,6 +899,8 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
AnimatorSet resumeLauncherAnimation = new AnimatorSet();
resumeLauncherAnimation.play(workspaceAnimator);
resumeLauncherAnimation.playSequentially(allAppsSlideIn, allAppsOvershoot);
resumeLauncherAnimation.addListener(mReapplyStateListener);
return resumeLauncherAnimation;
}
}
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3;
import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import android.annotation.TargetApi;
import android.os.Build;
import com.android.launcher3.states.InternalStateHandler;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
import java.util.function.BiPredicate;
@TargetApi(Build.VERSION_CODES.P)
public class LauncherInitListener extends InternalStateHandler implements ActivityInitListener {
private final BiPredicate<Launcher, Boolean> mOnInitListener;
public LauncherInitListener(BiPredicate<Launcher, Boolean> onInitListener) {
mOnInitListener = onInitListener;
}
@Override
protected boolean init(Launcher launcher, boolean alreadyOnHome) {
// For the duration of the gesture, lock the screen orientation to ensure that we do not
// rotate mid-quickscrub
launcher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
return mOnInitListener.test(launcher, alreadyOnHome);
}
@Override
public void register() {
initWhenReady();
}
@Override
public void unregister() {
clearReference();
}
}
@@ -1,64 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3;
import android.animation.Animator;
import android.animation.AnimatorSet;
/**
* Creates an AnimatorSet consisting on one Animator for Launcher transition, and one Animator for
* the Window transitions.
*
* Allows for ending the Launcher animator without ending the Window animator.
*/
public class LauncherTransitionAnimator {
private AnimatorSet mAnimatorSet;
private Animator mLauncherAnimator;
private Animator mWindowAnimator;
LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator) {
if (launcherAnimator != null) {
mLauncherAnimator = launcherAnimator;
}
mWindowAnimator = windowAnimator;
mAnimatorSet = new AnimatorSet();
if (launcherAnimator != null) {
mAnimatorSet.play(launcherAnimator);
}
mAnimatorSet.play(windowAnimator);
}
public AnimatorSet getAnimatorSet() {
return mAnimatorSet;
}
public void cancel() {
mAnimatorSet.cancel();
}
public boolean isRunning() {
return mAnimatorSet.isRunning();
}
public void finishLauncherAnimation() {
if (mLauncherAnimator != null) {
mLauncherAnimator.end();
}
}
}
@@ -24,7 +24,7 @@ import android.view.View;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
/**
@@ -32,7 +32,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
*/
public class AllAppsState extends LauncherState {
private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY | FLAG_ALL_APPS_SCRIM;
private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
@Override
@@ -57,7 +57,8 @@ public class AllAppsState extends LauncherState {
@Override
public String getDescription(Launcher launcher) {
return launcher.getString(R.string.all_apps_button_label);
AllAppsContainerView appsView = launcher.getAppsView();
return appsView.getDescription();
}
@Override
@@ -82,8 +83,15 @@ public class AllAppsState extends LauncherState {
}
@Override
public float getHoseatAlpha(Launcher launcher) {
return 0;
public int getVisibleElements(Launcher launcher) {
return ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
}
@Override
public float[] getOverviewTranslationFactor(Launcher launcher) {
// Keep the same translation as in overview, so that we don't slide around when
// transitioning to All Apps.
return LauncherState.OVERVIEW.getOverviewTranslationFactor(launcher);
}
@Override
@@ -1,92 +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.launcher3.uioverrides;
import com.android.launcher3.Alarm;
import com.android.launcher3.OnAlarmListener;
/**
* Utility class to detect a pause during a drag.
*/
public class DragPauseDetector implements OnAlarmListener {
private static final float MAX_VELOCITY_TO_PAUSE = 0.2f;
private static final long PAUSE_DURATION = 100;
private final Alarm mAlarm;
private final Runnable mOnPauseCallback;
private boolean mTriggered = false;
private int mDisabledFlags = 0;
public DragPauseDetector(Runnable onPauseCallback) {
mOnPauseCallback = onPauseCallback;
mAlarm = new Alarm();
mAlarm.setOnAlarmListener(this);
mAlarm.setAlarm(PAUSE_DURATION);
}
public void onDrag(float velocity) {
if (mTriggered || !isEnabled()) {
return;
}
if (Math.abs(velocity) > MAX_VELOCITY_TO_PAUSE) {
// Cancel any previous alarm and set a new alarm
mAlarm.setAlarm(PAUSE_DURATION);
}
}
@Override
public void onAlarm(Alarm alarm) {
if (!mTriggered && isEnabled()) {
mTriggered = true;
mOnPauseCallback.run();
}
}
public boolean isTriggered () {
return mTriggered;
}
public boolean isEnabled() {
return mDisabledFlags == 0;
}
public void addDisabledFlags(int flags) {
boolean wasEnabled = isEnabled();
mDisabledFlags |= flags;
resetAlarm(wasEnabled);
}
public void clearDisabledFlags(int flags) {
boolean wasEnabled = isEnabled();
mDisabledFlags &= ~flags;
resetAlarm(wasEnabled);
}
private void resetAlarm(boolean wasEnabled) {
boolean isEnabled = isEnabled();
if (wasEnabled == isEnabled) {
// Nothing has changed
} if (isEnabled && !mTriggered) {
mAlarm.setAlarm(PAUSE_DURATION);
} else if (!isEnabled) {
mAlarm.cancelAlarm();
}
}
}
@@ -1,171 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.touch.SwipeDetector.DIRECTION_NEGATIVE;
import static com.android.launcher3.touch.SwipeDetector.DIRECTION_POSITIVE;
import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
import static com.android.launcher3.touch.SwipeDetector.VERTICAL;
import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import android.graphics.Rect;
import android.metrics.LogMaker;
import android.view.MotionEvent;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.VerticalSwipeController;
import com.android.quickstep.RecentsView;
class EventLogTags {
private EventLogTags() {
} // don't instantiate
/** 524292 sysui_multi_action (content|4) */
public static final int SYSUI_MULTI_ACTION = 524292;
public static void writeSysuiMultiAction(Object[] content) {
android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
}
}
class MetricsLogger {
private static MetricsLogger sMetricsLogger;
private static MetricsLogger getLogger() {
if (sMetricsLogger == null) {
sMetricsLogger = new MetricsLogger();
}
return sMetricsLogger;
}
protected void saveLog(Object[] rep) {
EventLogTags.writeSysuiMultiAction(rep);
}
public void write(LogMaker content) {
if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
content.setType(4/*MetricsEvent.TYPE_ACTION*/);
}
saveLog(content.serialize());
}
}
/**
* Extension of {@link VerticalSwipeController} to go from NORMAL to OVERVIEW.
*/
public class EdgeSwipeController extends VerticalSwipeController implements
OnDeviceProfileChangeListener {
private static final Rect sTempRect = new Rect();
private final MetricsLogger mMetricsLogger = new MetricsLogger();
public EdgeSwipeController(Launcher l) {
super(l, NORMAL, OVERVIEW, l.getDeviceProfile().isVerticalBarLayout()
? HORIZONTAL : VERTICAL);
l.addOnDeviceProfileChangeListener(this);
}
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
mDetector.updateDirection(dp.isVerticalBarLayout() ? HORIZONTAL : VERTICAL);
}
@Override
protected boolean shouldInterceptTouch(MotionEvent ev) {
return mLauncher.isInState(NORMAL) && (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
}
@Override
protected int getSwipeDirection(MotionEvent ev) {
return isTransitionFlipped() ? DIRECTION_NEGATIVE : DIRECTION_POSITIVE;
}
public EdgeSwipeController(Launcher l, LauncherState baseState) {
super(l, baseState);
}
@Override
protected boolean isTransitionFlipped() {
return mLauncher.getDeviceProfile().isSeascape();
}
@Override
protected void onTransitionComplete(boolean wasFling, boolean stateChanged) {
if (stateChanged && mToState instanceof OverviewState) {
// Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
// "Recents" activity for app transition tests.
final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
builder.setPackageName("com.android.systemui");
builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
"com.android.systemui.recents.RecentsActivity");
builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
0/* zero time */);
mMetricsLogger.write(builder);
// Add user event logging for launcher pipeline
int direction = Direction.UP;
if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
direction = Direction.LEFT;
if (mLauncher.getDeviceProfile().isSeascape()) {
direction = Direction.RIGHT;
}
}
mLauncher.getUserEventDispatcher().logStateChangeAction(
wasFling ? Touch.FLING : Touch.SWIPE, direction,
ContainerType.NAVBAR, ContainerType.WORKSPACE, // src target
ContainerType.TASKSWITCHER, // dst target
mLauncher.getWorkspace().getCurrentPage());
}
}
@Override
protected void initSprings() {
mSpringHandlers = new SpringAnimationHandler[0];
}
@Override
protected float getShiftRange() {
return getShiftRange(mLauncher);
}
public static float getShiftRange(Launcher launcher) {
RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, sTempRect);
DragLayer dl = launcher.getDragLayer();
Rect insets = dl.getInsets();
DeviceProfile dp = launcher.getDeviceProfile();
if (dp.isVerticalBarLayout()) {
if (dp.isSeascape()) {
return insets.left + sTempRect.left;
} else {
return dl.getWidth() - sTempRect.right + insets.right;
}
} else {
return dl.getHeight() - sTempRect.bottom + insets.bottom;
}
}
}
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.uioverrides;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.quickstep.QuickScrubController;
import com.android.quickstep.views.RecentsView;
/**
* Extension of overview state used for QuickScrub
*/
public class FastOverviewState extends OverviewState {
private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_DISABLE_RESTORE
| FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI | FLAG_HIDE_BACK_BUTTON;
public FastOverviewState(int id) {
super(id, QuickScrubController.QUICK_SCRUB_START_DURATION, STATE_FLAGS);
}
@Override
public void onStateTransitionEnd(Launcher launcher) {
super.onStateTransitionEnd(launcher);
RecentsView recentsView = launcher.getOverviewPanel();
recentsView.getQuickScrubController().onFinishedTransitionToQuickScrub();
}
public void onStateEnabled(Launcher launcher) {
super.onStateEnabled(launcher);
AbstractFloatingView.closeAllOpenViews(launcher);
}
@Override
public int getVisibleElements(Launcher launcher) {
return NONE;
}
@Override
public float[] getOverviewTranslationFactor(Launcher launcher) {
return new float[] {0f, 0.5f};
}
}
@@ -0,0 +1,71 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.quickstep.util.SysuiEventLogger;
/**
* Touch controller for handling edge swipes in landscape/seascape UI
*/
public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchController {
public LandscapeEdgeSwipeController(Launcher l) {
super(l, SwipeDetector.HORIZONTAL);
}
@Override
protected boolean canInterceptTouch(MotionEvent ev) {
if (mCurrentAnimation != null) {
// If we are already animating from a previous state, we can intercept.
return true;
}
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return false;
}
return mLauncher.isInState(NORMAL) && (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
}
@Override
protected int getSwipeDirection(MotionEvent ev) {
mFromState = NORMAL;
mToState = OVERVIEW;
return SwipeDetector.DIRECTION_BOTH;
}
@Override
protected float getShiftRange() {
return mLauncher.getDragLayer().getWidth();
}
@Override
protected float initCurrentAnimation() {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
mCurrentAnimation = mLauncher.getStateManager()
.createAnimationToNewWorkspace(mToState, maxAccuracy);
return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
}
@Override
protected int getDirectionForLog() {
return mLauncher.getDeviceProfile().isSeascape() ? Direction.RIGHT : Direction.LEFT;
}
@Override
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
if (mFromState == NORMAL && targetState == OVERVIEW) {
SysuiEventLogger.writeDummyRecentsTransition(0);
}
}
}
@@ -0,0 +1,70 @@
/*
* 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.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.views.RecentsView;
/**
* Touch controller from going from OVERVIEW to ALL_APPS
*/
public class LandscapeStatesTouchController extends PortraitStatesTouchController {
public LandscapeStatesTouchController(Launcher l) {
super(l);
}
@Override
protected boolean canInterceptTouch(MotionEvent ev) {
if (mCurrentAnimation != null) {
// If we are already animating from a previous state, we can intercept.
return true;
}
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return false;
}
if (mLauncher.isInState(ALL_APPS)) {
// In all-apps only listen if the container cannot scroll itself
return mLauncher.getAppsView().shouldContainerScroll(ev);
} else if (mLauncher.isInState(NORMAL)) {
return true;
} else if (mLauncher.isInState(OVERVIEW)) {
RecentsView rv = mLauncher.getOverviewPanel();
return ev.getY() > (rv.getBottom() - rv.getPaddingBottom());
} else {
return false;
}
}
protected LauncherState getTargetState() {
if (mLauncher.isInState(ALL_APPS)) {
// Should swipe down go to OVERVIEW instead?
return TouchInteractionService.isConnected() ?
mLauncher.getStateManager().getLastState() : NORMAL;
} else {
return ALL_APPS;
}
}
}
@@ -16,20 +16,18 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
import android.content.Context;
import android.graphics.Rect;
import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.RecentsView;
import com.android.quickstep.views.RecentsView;
/**
* Definition for overview state
@@ -37,27 +35,31 @@ import com.android.quickstep.RecentsView;
public class OverviewState extends LauncherState {
private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
| FLAG_DISABLE_RESTORE;
| FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI;
public OverviewState(int id) {
super(id, ContainerType.TASKSWITCHER, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
}
protected OverviewState(int id, int transitionDuration, int stateFlags) {
super(id, ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
}
@Override
public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
Rect pageRect = new Rect();
RecentsView.getScaledDownPageRect(launcher.getDeviceProfile(), launcher, pageRect);
RecentsView rv = launcher.getOverviewPanel();
RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, pageRect);
if (launcher.getWorkspace().getNormalChildWidth() <= 0 || pageRect.isEmpty()) {
return super.getWorkspaceScaleAndTranslation(launcher);
}
float overlap = 0;
if (rv.getCurrentPage() >= rv.getFirstTaskIndex()) {
overlap = launcher.getResources().getDimension(R.dimen.workspace_overview_offset_x);
}
return getScaleAndTranslationForPageRect(launcher, overlap, pageRect);
return getScaleAndTranslationForPageRect(launcher, pageRect);
}
@Override
public float[] getOverviewTranslationFactor(Launcher launcher) {
return new float[] {0f, 0f};
}
@Override
@@ -73,8 +75,8 @@ public class OverviewState extends LauncherState {
}
@Override
public float getVerticalProgress(Launcher launcher) {
return getVerticalProgress(launcher.getDeviceProfile(), launcher);
public void onStateTransitionEnd(Launcher launcher) {
launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
}
@Override
@@ -83,51 +85,50 @@ public class OverviewState extends LauncherState {
}
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
final int centerPage = launcher.getWorkspace().getNextPage();
return new PageAlphaProvider(ACCEL_2) {
return new PageAlphaProvider(DEACCEL_2) {
@Override
public float getPageAlpha(int pageIndex) {
return pageIndex != centerPage ? 0 : 1f;
return 0;
}
};
}
public static float[] getScaleAndTranslationForPageRect(Launcher launcher, float offsetX,
Rect pageRect) {
public static float[] getScaleAndTranslationForPageRect(Launcher launcher, Rect pageRect) {
Workspace ws = launcher.getWorkspace();
float childWidth = ws.getNormalChildWidth();
float childHeight = ws.getNormalChildHeight();
float scale = pageRect.height() / childHeight;
float scale = pageRect.width() / childWidth;
Rect insets = launcher.getDragLayer().getInsets();
float halfHeight = ws.getExpectedHeight() / 2;
float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
float translationY = pageRect.top - childTop;
// Align the workspace horizontally centered with the task rect
float halfWidth = ws.getExpectedWidth() / 2;
float childCenter = halfWidth -
scale * (halfWidth - ws.getPaddingLeft() - insets.left - childWidth / 2);
float translationX = pageRect.centerX() - childCenter;
if (Utilities.isRtl(launcher.getResources())) {
translationX -= offsetX / scale;
} else {
translationX += offsetX / scale;
}
return new float[] {scale, translationX, translationY};
return new float[] {scale, 0, translationY};
}
public static float getVerticalProgress(DeviceProfile grid, Context context) {
if (!grid.isVerticalBarLayout()) {
return 1f;
@Override
public int getVisibleElements(Launcher launcher) {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return NONE;
} else {
return launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
? HOTSEAT_EXTRA | ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS | HOTSEAT_EXTRA;
}
}
float total = grid.heightPx;
float searchHeight = total - grid.availableHeightPx +
context.getResources().getDimension(R.dimen.all_apps_search_box_full_height);
return 1 - (searchHeight / total);
@Override
public float getVerticalProgress(Launcher launcher) {
if ((getVisibleElements(launcher) & ALL_APPS_HEADER_EXTRA) == 0) {
// We have no all apps content, so we're still at the fully down progress.
return super.getVerticalProgress(launcher);
}
return 1 - (getDefaultSwipeHeight(launcher)
/ launcher.getAllAppsController().getShiftRange());
}
public static float getDefaultSwipeHeight(Launcher launcher) {
DeviceProfile dp = launcher.getDeviceProfile();
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
}
}
@@ -1,71 +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.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.OVERVIEW;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.VerticalSwipeController;
/**
* Extension of {@link VerticalSwipeController} which allows swipe up from OVERVIEW to ALL_APPS
* Note that the swipe down is handled by {@link TwoStepSwipeController}.
*/
public class OverviewSwipeUpController extends VerticalSwipeController {
public OverviewSwipeUpController(Launcher l) {
super(l, OVERVIEW);
}
@Override
protected boolean shouldInterceptTouch(MotionEvent ev) {
if (!mLauncher.isInState(OVERVIEW)) {
return false;
}
if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
return ev.getY() >
mLauncher.getDragLayer().getHeight() * OVERVIEW.getVerticalProgress(mLauncher);
} else {
return mLauncher.getDragLayer().isEventOverHotseat(ev);
}
}
@Override
protected int getSwipeDirection(MotionEvent ev) {
return SwipeDetector.DIRECTION_POSITIVE;
}
@Override
protected void onTransitionComplete(boolean wasFling, boolean stateChanged) {
if (stateChanged) {
// Transition complete. log the action
mLauncher.getUserEventDispatcher().logStateChangeAction(
wasFling ? Touch.FLING : Touch.SWIPE,
Direction.UP,
ContainerType.HOTSEAT,
ContainerType.TASKSWITCHER,
ContainerType.ALLAPPS,
mLauncher.getWorkspace().getCurrentPage());
}
}
}
@@ -0,0 +1,291 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.SysuiEventLogger;
/**
* Touch controller for handling various state transitions in portrait UI.
*/
public class PortraitStatesTouchController extends AbstractStateChangeTouchController {
private static final float TOTAL_DISTANCE_MULTIPLIER = 2f;
private static final float LINEAR_SCALE_LIMIT = 1 / TOTAL_DISTANCE_MULTIPLIER;
// Much be greater than LINEAR_SCALE_LIMIT;
private static final float MAXIMUM_DISTANCE_FACTOR = 0.9f;
// Maximum amount to overshoot.
private static final float MAX_OVERSHOOT = 0.3f;
private static final double PI_BY_2 = Math.PI / 2;
private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
// If > 0, the animation progress is clamped at that value as long as user is dragging.
private float mClampProgressUpdate = -1;
// If true, we will finish the current animation instantly on second touch.
private boolean mFinishFastOnSecondTouch;
private final Interpolator mAllAppsDampedInterpolator = new Interpolator() {
private final double mAngleMultiplier = Math.PI /
(2 * (MAXIMUM_DISTANCE_FACTOR - LINEAR_SCALE_LIMIT));
@Override
public float getInterpolation(float v) {
if (v <= LINEAR_SCALE_LIMIT) {
return v * TOTAL_DISTANCE_MULTIPLIER;
}
float overshoot = (v - LINEAR_SCALE_LIMIT);
return (float) (1 + MAX_OVERSHOOT * Math.sin(overshoot * mAngleMultiplier));
}
};
private final Interpolator mOverviewBoundInterpolator = (v) -> {
if (v >= MAXIMUM_DISTANCE_FACTOR) {
return 1;
}
return FAST_OUT_SLOW_IN.getInterpolation(v / MAXIMUM_DISTANCE_FACTOR);
};
public PortraitStatesTouchController(Launcher l) {
super(l, SwipeDetector.VERTICAL);
}
@Override
protected boolean canInterceptTouch(MotionEvent ev) {
if (mCurrentAnimation != null) {
if (mFinishFastOnSecondTouch) {
// TODO: Animate to finish instead.
mCurrentAnimation.getAnimationPlayer().end();
}
// If we are already animating from a previous state, we can intercept.
return true;
}
if (mLauncher.isInState(ALL_APPS)) {
// In all-apps only listen if the container cannot scroll itself
if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
return false;
}
} else {
// For all other states, only listen if the event originated below the hotseat height
DeviceProfile dp = mLauncher.getDeviceProfile();
int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
if (ev.getY() < (mLauncher.getDragLayer().getHeight() - hotseatHeight)) {
return false;
}
}
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return false;
}
return true;
}
@Override
protected int getSwipeDirection(MotionEvent ev) {
final int directionsToDetectScroll;
if (mLauncher.isInState(ALL_APPS)) {
directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
mStartContainerType = ContainerType.ALLAPPS;
} else if (mLauncher.isInState(NORMAL)) {
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
mStartContainerType = ContainerType.HOTSEAT;
} else if (mLauncher.isInState(OVERVIEW)) {
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
mStartContainerType = ContainerType.TASKSWITCHER;
} else {
return 0;
}
mFromState = mLauncher.getStateManager().getState();
mToState = getTargetState();
if (mFromState == mToState) {
return 0;
}
return directionsToDetectScroll;
}
protected LauncherState getTargetState() {
if (mLauncher.isInState(ALL_APPS)) {
// Should swipe down go to OVERVIEW instead?
return TouchInteractionService.isConnected() ?
mLauncher.getStateManager().getLastState() : NORMAL;
} else if (mLauncher.isInState(OVERVIEW)) {
return ALL_APPS;
} else {
return TouchInteractionService.isConnected() ? OVERVIEW : ALL_APPS;
}
}
private AnimatorSetBuilder getNormalToOverviewAnimation() {
mAllAppsInterpolatorWrapper.baseInterpolator = mAllAppsDampedInterpolator;
AnimatorSetBuilder builder = new AnimatorSetBuilder();
builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);
builder.setInterpolator(ANIM_OVERVIEW_TRANSLATION, mOverviewBoundInterpolator);
return builder;
}
@Override
protected void updateProgress(float fraction) {
if (mClampProgressUpdate > 0) {
mCurrentAnimation.setPlayFraction(Math.min(fraction, mClampProgressUpdate));
} else {
super.updateProgress(fraction);
}
}
@Override
protected float initCurrentAnimation() {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;
float totalShift = endVerticalShift - startVerticalShift;
final AnimatorSetBuilder builder;
if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
builder = getNormalToOverviewAnimation();
totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER;
mClampProgressUpdate = MAXIMUM_DISTANCE_FACTOR;
} else {
builder = new AnimatorSetBuilder();
mClampProgressUpdate = -1;
}
mCurrentAnimation = mLauncher.getStateManager()
.createAnimationToNewWorkspace(mToState, builder, maxAccuracy);
if (totalShift == 0) {
totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
* OverviewState.getDefaultSwipeHeight(mLauncher);
}
return 1 / totalShift;
}
@Override
protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
LauncherState targetState, float velocity, boolean isFling) {
if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
mFinishFastOnSecondTouch = true;
// Update all apps interpolator
float currentFraction = mCurrentAnimation.getProgressFraction();
float absVelocity = Math.abs(velocity);
float currentValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction);
if (isFling && absVelocity > 1 && currentFraction < LINEAR_SCALE_LIMIT) {
// TODO: Clean up these magic calculations
// Linearly interpolate the max value based on the velocity.
float maxValue = Math.max(absVelocity > 4 ? 1 + MAX_OVERSHOOT :
1 + (absVelocity - 1) * MAX_OVERSHOOT / 3,
currentValue);
double angleToPeak = PI_BY_2 - Math.asin(currentValue / maxValue);
if (expectedDuration != 0 && angleToPeak != 0) {
float distanceLeft = 1 - currentFraction;
mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
float scaledF = (f - currentFraction) / distanceLeft;
if (scaledF < 0.5f) {
double angle = PI_BY_2 - angleToPeak + scaledF * angleToPeak / 0.5f;
return (float) (maxValue * Math.sin(angle));
}
scaledF = ((scaledF - .5f) / .5f);
double angle = PI_BY_2 + 3 * scaledF * PI_BY_2;
float amplitude = (1 - scaledF) * (1 - scaledF) * (maxValue - 1);
return 1 + (float) (amplitude * Math.sin(angle));
};
animator.setDuration(expectedDuration).setInterpolator(LINEAR);
return;
}
}
if (currentFraction < LINEAR_SCALE_LIMIT) {
mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
velocity, isFling);
return;
}
float extraValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction) - 1;
float distanceLeft = 1 - currentFraction;
animator.setFloatValues(currentFraction, 1);
mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
float scaledF = (f - currentFraction) / distanceLeft;
double angle = scaledF * 1.5 * Math.PI;
float amplitude = (1 - scaledF) * (1 - scaledF) * extraValue;
return 1 + (float) (amplitude * Math.sin(angle));
};
animator.setDuration(200).setInterpolator(LINEAR);
return;
}
mFinishFastOnSecondTouch = false;
super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
velocity, isFling);
}
@Override
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
if (mFromState == NORMAL && targetState == OVERVIEW) {
SysuiEventLogger.writeDummyRecentsTransition(0);
}
}
private static class InterpolatorWrapper implements Interpolator {
public TimeInterpolator baseInterpolator = LINEAR;
@Override
public float getInterpolation(float v) {
return baseInterpolator.getInterpolation(v);
}
}
}
@@ -16,147 +16,84 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.AlphaUpdateListener.updateVisibility;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_X_FACTOR;
import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.view.View;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.os.Build;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.PagedView;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.RecentsView;
import com.android.quickstep.TaskView;
import com.android.launcher3.anim.PropertySetter;
import com.android.quickstep.views.LauncherRecentsView;
@TargetApi(Build.VERSION_CODES.O)
public class RecentsViewStateController implements StateHandler {
private final Launcher mLauncher;
private final RecentsView mRecentsView;
private final WorkspaceCard mWorkspaceCard;
private final AnimatedFloat mTransitionProgress = new AnimatedFloat(this::onTransitionProgress);
// The fraction representing the visibility of the RecentsView. This allows delaying the
// overall transition while the RecentsView is being shown or hidden.
private final AnimatedFloat mVisibilityMultiplier = new AnimatedFloat(this::onVisibilityProgress);
private boolean mIsRecentsScrollingToFirstTask;
private final LauncherRecentsView mRecentsView;
public RecentsViewStateController(Launcher launcher) {
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
mRecentsView.setStateController(this);
mWorkspaceCard = (WorkspaceCard) mRecentsView.getChildAt(0);
mWorkspaceCard.setup(launcher);
}
@Override
public void setState(LauncherState state) {
mWorkspaceCard.setWorkspaceScrollingEnabled(state == OVERVIEW);
setVisibility(state == OVERVIEW);
setTransitionProgress(state == OVERVIEW ? 1 : 0);
if (state == OVERVIEW) {
for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
((TaskView) mRecentsView.getPageAt(i)).resetVisualProperties();
}
mRecentsView.updateCurveProperties();
mRecentsView.setContentAlpha(state.overviewUi ? 1 : 0);
updateVisibility(mRecentsView, isAccessibilityEnabled(mLauncher));
float[] translationFactor = state.getOverviewTranslationFactor(mLauncher);
mRecentsView.setTranslationXFactor(translationFactor[0]);
mRecentsView.setTranslationYFactor(translationFactor[1]);
if (state.overviewUi) {
mRecentsView.resetTaskVisuals();
}
}
@Override
public void setStateWithAnimation(final LauncherState toState,
AnimatorSetBuilder builder, AnimationConfig config) {
boolean settingEnabled = Utilities.getPrefs(mLauncher)
.getBoolean("pref_scroll_to_first_task", false);
mIsRecentsScrollingToFirstTask = mLauncher.isInState(NORMAL) && toState == OVERVIEW
&& settingEnabled;
// TODO: Instead of animating the workspace translationX, move the contents
mWorkspaceCard.setWorkspaceScrollingEnabled(mIsRecentsScrollingToFirstTask);
// Scroll to the workspace card before changing to the NORMAL state.
LauncherState fromState = mLauncher.getStateManager().getState();
int currPage = mRecentsView.getCurrentPage();
if (toState == NORMAL && currPage != 0 && !config.userControlled) {
if (fromState.overviewUi && toState == NORMAL && currPage != 0 && !config.userControlled) {
int maxSnapDuration = PagedView.SLOW_PAGE_SNAP_ANIMATION_DURATION;
int durationPerPage = maxSnapDuration / 10;
int snapDuration = Math.min(maxSnapDuration, durationPerPage * currPage);
mRecentsView.snapToPage(0, snapDuration);
builder.setStartDelay(snapDuration);
// Let the snapping animation play for a bit before we translate off screen.
builder.setStartDelay(snapDuration / 4);
}
ObjectAnimator progressAnim =
mTransitionProgress.animateToValue(toState == OVERVIEW ? 1 : 0);
progressAnim.setDuration(config.duration);
progressAnim.setInterpolator(Interpolators.LINEAR);
progressAnim.addListener(new AnimationSuccessListener() {
PropertySetter setter = config.getProperSetter(builder);
float[] translationFactor = toState.getOverviewTranslationFactor(mLauncher);
setter.setFloat(mRecentsView, TRANSLATION_X_FACTOR,
translationFactor[0],
builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR,
translationFactor[1],
builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
setter.setFloat(mRecentsView, CONTENT_ALPHA, toState.overviewUi ? 1 : 0, LINEAR);
@Override
public void onAnimationSuccess(Animator animator) {
mWorkspaceCard.setWorkspaceScrollingEnabled(toState == OVERVIEW);
mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
}
});
builder.play(progressAnim);
ObjectAnimator visibilityAnim = animateVisibility(toState == OVERVIEW);
visibilityAnim.setDuration(config.duration);
visibilityAnim.setInterpolator(Interpolators.LINEAR);
builder.play(visibilityAnim);
}
public void setVisibility(boolean isVisible) {
mVisibilityMultiplier.cancelAnimation();
mRecentsView.setVisibility(isVisible ? View.VISIBLE : View.GONE);
mVisibilityMultiplier.updateValue(isVisible ? 1 : 0);
}
public ObjectAnimator animateVisibility(boolean isVisible) {
ObjectAnimator anim = mVisibilityMultiplier.animateToValue(isVisible ? 1 : 0);
if (isVisible) {
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mRecentsView.setVisibility(View.VISIBLE);
}
});
} else {
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
mRecentsView.setVisibility(View.GONE);
}
if (toState.overviewUi) {
ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
updateAnim.addUpdateListener(valueAnimator -> {
// While animating into recents, update the visible task data as needed
mRecentsView.loadVisibleTaskData();
});
updateAnim.setDuration(config.duration);
builder.play(updateAnim);
}
return anim;
}
public void setTransitionProgress(float progress) {
mTransitionProgress.cancelAnimation();
mTransitionProgress.updateValue(progress);
}
private void onTransitionProgress() {
applyProgress();
if (mIsRecentsScrollingToFirstTask) {
int scrollForFirstTask = mRecentsView.getScrollForPage(mRecentsView.getFirstTaskIndex());
int scrollForPage0 = mRecentsView.getScrollForPage(0);
mRecentsView.setScrollX((int) (mTransitionProgress.value * scrollForFirstTask
+ (1 - mTransitionProgress.value) * scrollForPage0));
}
}
private void onVisibilityProgress() {
applyProgress();
}
private void applyProgress() {
mRecentsView.setAlpha(mTransitionProgress.value * mVisibilityMultiplier.value);
}
}
@@ -1,51 +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.launcher3.uioverrides;
import android.animation.Animator;
import android.util.SparseArray;
import com.android.launcher3.anim.AnimatorSetBuilder;
import java.util.Collections;
import java.util.List;
public class TaggedAnimatorSetBuilder extends AnimatorSetBuilder {
/**
* Map of the index in {@link #mAnims} to tag. All the animations in {@link #mAnims} starting
* from this index correspond to the tag (until a new tag is specified for an index)
*/
private final SparseArray<Object> mTags = new SparseArray<>();
@Override
public void startTag(Object obj) {
mTags.put(mAnims.size(), obj);
}
public List<Animator> getAnimationsForTag(Object tag) {
int startIndex = mTags.indexOfValue(tag);
if (startIndex < 0) {
return Collections.emptyList();
}
int startPos = mTags.keyAt(startIndex);
int endIndex = startIndex + 1;
int endPos = endIndex >= mTags.size() ? mAnims.size() : mTags.keyAt(endIndex);
return mAnims.subList(startPos, endPos);
}
}
@@ -15,10 +15,12 @@
*/
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.util.Log;
import android.view.MotionEvent;
@@ -26,30 +28,21 @@ import android.view.View;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.RecentsView;
import com.android.quickstep.TaskView;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import com.android.quickstep.PendingAnimation;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
/**
* Touch controller for swipe interaction in Overview state
* Touch controller for handling task view card swipes
*/
public class OverviewSwipeController extends AnimatorListenerAdapter
public class TaskViewTouchController extends AnimatorListenerAdapter
implements TouchController, SwipeDetector.Listener {
private static final String TAG = "OverviewSwipeController";
@@ -65,20 +58,19 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
private final RecentsView mRecentsView;
private final int[] mTempCords = new int[2];
private PendingAnimation mPendingAnimation;
private AnimatorPlaybackController mCurrentAnimation;
private boolean mCurrentAnimationIsGoingUp;
private boolean mNoIntercept;
private boolean mSwipeDownEnabled;
private float mDisplacementShift;
private float mProgressMultiplier;
private float mEndDisplacement;
private int mStartingTarget;
private TaskView mTaskBeingDragged;
public OverviewSwipeController(Launcher launcher) {
public TaskViewTouchController(Launcher launcher) {
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
mDetector = new SwipeDetector(launcher, this, SwipeDetector.VERTICAL);
@@ -95,15 +87,6 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
return mLauncher.isInState(OVERVIEW);
}
private boolean isEventOverHotseat(MotionEvent ev) {
if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
return ev.getY() >
mLauncher.getDragLayer().getHeight() * OVERVIEW.getVerticalProgress(mLauncher);
} else {
return mLauncher.getDragLayer().isEventOverHotseat(ev);
}
}
@Override
public void onAnimationCancel(Animator animation) {
if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
@@ -130,30 +113,15 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
ignoreSlopWhenSettling = true;
} else {
mTaskBeingDragged = null;
mSwipeDownEnabled = true;
int currentPage = mRecentsView.getCurrentPage();
if (currentPage == 0) {
// User is on home tile
View view = mRecentsView.getChildAt(mRecentsView.getCurrentPage());
if (view instanceof TaskView && mLauncher.getDragLayer().isEventOverView(view, ev)) {
// The tile can be dragged down to open the task.
mTaskBeingDragged = (TaskView) view;
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
} else {
View view = mRecentsView.getChildAt(currentPage);
if (mLauncher.getDragLayer().isEventOverView(view, ev) &&
view instanceof TaskView) {
// The tile can be dragged down to open the task.
mTaskBeingDragged = (TaskView) view;
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
mStartingTarget = LauncherLogProto.ItemType.TASK;
} else if (isEventOverHotseat(ev)) {
// The hotseat is being dragged
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
mSwipeDownEnabled = false;
mStartingTarget = ContainerType.HOTSEAT;
} else {
mNoIntercept = true;
mStartingTarget = ContainerType.WORKSPACE;
return false;
}
mNoIntercept = true;
return false;
}
}
@@ -175,9 +143,6 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
}
private void reInitAnimationController(boolean goingUp) {
if (!goingUp && !mSwipeDownEnabled) {
goingUp = true;
}
if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) {
// No need to init
return;
@@ -185,44 +150,30 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
if (mCurrentAnimation != null) {
mCurrentAnimation.setPlayFraction(0);
}
if (mPendingAnimation != null) {
mPendingAnimation.finish(false);
mPendingAnimation = null;
}
mCurrentAnimationIsGoingUp = goingUp;
float range = mLauncher.getAllAppsController().getShiftRange();
long maxDuration = (long) (2 * range);
DragLayer dl = mLauncher.getDragLayer();
if (mTaskBeingDragged == null) {
// User is either going to all apps or home
mCurrentAnimation = mLauncher.getStateManager()
.createAnimationToNewWorkspace(goingUp ? ALL_APPS : NORMAL, maxDuration);
if (goingUp) {
mEndDisplacement = -range;
} else {
mEndDisplacement = EdgeSwipeController.getShiftRange(mLauncher);
}
if (goingUp) {
mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
true /* animateTaskView */, true /* removeTask */, maxDuration);
mCurrentAnimation = AnimatorPlaybackController
.wrap(mPendingAnimation.anim, maxDuration);
mEndDisplacement = -mTaskBeingDragged.getHeight();
} else {
if (goingUp) {
AnimatorSet anim = new AnimatorSet();
ObjectAnimator translate = ObjectAnimator.ofFloat(
mTaskBeingDragged, View.TRANSLATION_Y, -mTaskBeingDragged.getBottom());
translate.setInterpolator(LINEAR);
translate.setDuration(maxDuration);
anim.play(translate);
AnimatorSet anim = new AnimatorSet();
// TODO: Setup a zoom animation
mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
ObjectAnimator alpha = ObjectAnimator.ofFloat(mTaskBeingDragged, View.ALPHA, 0);
alpha.setInterpolator(DEACCEL_1_5);
alpha.setDuration(maxDuration);
anim.play(alpha);
mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
mEndDisplacement = -mTaskBeingDragged.getBottom();
} else {
AnimatorSet anim = new AnimatorSet();
// TODO: Setup a zoom animation
mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
mTempCords[1] = mTaskBeingDragged.getHeight();
dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
mEndDisplacement = dl.getHeight() - mTempCords[1];
}
mTempCords[1] = mTaskBeingDragged.getHeight();
dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
mEndDisplacement = dl.getHeight() - mTempCords[1];
}
mCurrentAnimation.getTarget().addListener(this);
@@ -260,9 +211,7 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
if (fling) {
logAction = Touch.FLING;
boolean goingUp = velocity < 0;
if (!goingUp && !mSwipeDownEnabled) {
goingToEnd = false;
} else if (goingUp != mCurrentAnimationIsGoingUp) {
if (goingUp != mCurrentAnimationIsGoingUp) {
// In case the fling is in opposite direction, make sure if is close enough
// from the start position
if (mCurrentAnimation.getProgressFraction()
@@ -288,7 +237,6 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
float nextFrameProgress = Utilities.boundToRange(
progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
@@ -299,27 +247,17 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
}
private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) {
if (mTaskBeingDragged == null) {
LauncherState state = wasSuccess ?
(mCurrentAnimationIsGoingUp ? ALL_APPS : NORMAL) : OVERVIEW;
mLauncher.getStateManager().goToState(state, false);
} else if (wasSuccess) {
if (mCurrentAnimationIsGoingUp) {
mRecentsView.onTaskDismissed(mTaskBeingDragged);
} else {
if (mPendingAnimation != null) {
mPendingAnimation.finish(wasSuccess);
mPendingAnimation = null;
}
if (wasSuccess) {
if (!mCurrentAnimationIsGoingUp) {
mTaskBeingDragged.launchTask(false);
mLauncher.getUserEventDispatcher().logTaskLaunch(logAction,
Direction.DOWN, mTaskBeingDragged.getTask().getTopComponent());
}
}
if (mTaskBeingDragged == null || (wasSuccess && mCurrentAnimationIsGoingUp)) {
mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
mCurrentAnimationIsGoingUp ? Direction.UP : Direction.DOWN,
mStartingTarget, ContainerType.TASKSWITCHER,
mLauncher.getStateManager().getState().containerType,
mRecentsView.getCurrentPage());
}
mDetector.finishedScrolling();
mTaskBeingDragged = null;
mCurrentAnimation = null;
@@ -1,496 +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.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.support.animation.SpringAnimation;
import android.util.Log;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.SpringAnimationHandler;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.FloatRange;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.TouchInteractionService;
import java.util.ArrayList;
/**
* Handles vertical touch gesture on the DragLayer
*/
public class TwoStepSwipeController extends AnimatorListenerAdapter
implements TouchController, SwipeDetector.Listener {
private static final String TAG = "TwoStepSwipeController";
private static final float RECATCH_REJECTION_FRACTION = .0875f;
private static final int SINGLE_FRAME_MS = 16;
private static final long QUICK_SNAP_TO_OVERVIEW_DURATION = 250;
// Progress after which the transition is assumed to be a success in case user does not fling
private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
/**
* Index of the vertical swipe handles in {@link LauncherStateManager#getStateHandlers()}.
*/
private static final int SWIPE_HANDLER_INDEX = 0;
/**
* Index of various UI handlers in {@link LauncherStateManager#getStateHandlers()} not related
* to vertical swipe.
*/
private static final int OTHER_HANDLERS_START_INDEX = SWIPE_HANDLER_INDEX + 1;
// Swipe progress range (when starting from NORMAL state) where OVERVIEW state is allowed
private static final float MIN_PROGRESS_TO_OVERVIEW = 0.1f;
private static final float MAX_PROGRESS_TO_OVERVIEW = 0.4f;
private static final int FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE = 1 << 0;
private static final int FLAG_OVERVIEW_DISABLED_FLING = 1 << 1;
private static final int FLAG_OVERVIEW_DISABLED_CANCEL_STATE = 1 << 2;
private static final int FLAG_OVERVIEW_DISABLED = 1 << 4;
private static final int FLAG_DISABLED_TWO_TARGETS = 1 << 5;
private static final int FLAG_DISABLED_BACK_TARGET = 1 << 6;
private final Launcher mLauncher;
private final SwipeDetector mDetector;
private boolean mNoIntercept;
private int mStartContainerType;
private DragPauseDetector mDragPauseDetector;
private FloatRange mOverviewProgressRange;
private TaggedAnimatorSetBuilder mTaggedAnimatorSetBuilder;
private AnimatorSet mQuickOverviewAnimation;
private boolean mAnimatingToOverview;
private CroppedAnimationController mCroppedAnimationController;
private AnimatorPlaybackController mCurrentAnimation;
private LauncherState mFromState;
private LauncherState mToState;
private float mStartProgress;
// Ratio of transition process [0, 1] to drag displacement (px)
private float mProgressMultiplier;
private SpringAnimationHandler[] mSpringHandlers;
public TwoStepSwipeController(Launcher l) {
mLauncher = l;
mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
}
private boolean canInterceptTouch(MotionEvent ev) {
if (mCurrentAnimation != null) {
// If we are already animating from a previous state, we can intercept.
return true;
}
if (mLauncher.isInState(NORMAL)) {
if ((ev.getEdgeFlags() & EDGE_NAV_BAR) != 0 &&
!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
// On normal swipes ignore edge swipes
return false;
}
} else if (mLauncher.isInState(ALL_APPS)) {
if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
return false;
}
} else {
// Don't listen for the swipe gesture if we are already in some other state.
return false;
}
if (mAnimatingToOverview) {
return false;
}
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return false;
}
return true;
}
@Override
public void onAnimationCancel(Animator animation) {
if (mCurrentAnimation != null && animation == mCurrentAnimation.getOriginalTarget()) {
Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
clearState();
}
}
private void initSprings() {
AllAppsContainerView appsView = mLauncher.getAppsView();
SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
if (handler == null) {
mSpringHandlers = new SpringAnimationHandler[0];
return;
}
ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
handlers.add(handler);
SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
if (searchSpring != null) {
SpringAnimationHandler searchHandler =
new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
searchHandler.add(searchSpring, true /* setDefaultValues */);
handlers.add(searchHandler);
}
mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
return false;
}
// Now figure out which direction scroll events the controller will start
// calling the callbacks.
final int directionsToDetectScroll;
boolean ignoreSlopWhenSettling = false;
if (mCurrentAnimation != null) {
if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
} else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
} else {
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
ignoreSlopWhenSettling = true;
}
} else {
if (mLauncher.isInState(ALL_APPS)) {
directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
mStartContainerType = ContainerType.ALLAPPS;
} else {
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
mStartContainerType = mLauncher.getDragLayer().isEventOverHotseat(ev) ?
ContainerType.HOTSEAT : ContainerType.WORKSPACE;
}
}
mDetector.setDetectableScrollConditions(
directionsToDetectScroll, ignoreSlopWhenSettling);
if (mSpringHandlers == null) {
initSprings();
}
}
if (mNoIntercept) {
return false;
}
onControllerTouchEvent(ev);
return mDetector.isDraggingOrSettling();
}
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
for (SpringAnimationHandler h : mSpringHandlers) {
h.addMovement(ev);
}
return mDetector.onTouchEvent(ev);
}
@Override
public void onDragStart(boolean start) {
if (mCurrentAnimation == null) {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
mDragPauseDetector = new DragPauseDetector(this::onDragPauseDetected);
mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE);
if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
mDragPauseDetector.addDisabledFlags(FLAG_DISABLED_TWO_TARGETS);
}
mOverviewProgressRange = new FloatRange();
mOverviewProgressRange.start = mLauncher.isInState(NORMAL)
? MIN_PROGRESS_TO_OVERVIEW
: 1 - MAX_PROGRESS_TO_OVERVIEW;
mOverviewProgressRange.end = mOverviewProgressRange.start
+ MAX_PROGRESS_TO_OVERVIEW - MIN_PROGRESS_TO_OVERVIEW;
// Build current animation
mFromState = mLauncher.getStateManager().getState();
mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS;
if (mToState == NORMAL && mLauncher.getStateManager().getLastState() == OVERVIEW) {
mToState = OVERVIEW;
mDragPauseDetector.addDisabledFlags(FLAG_DISABLED_BACK_TARGET);
}
mTaggedAnimatorSetBuilder = new TaggedAnimatorSetBuilder();
mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(
mToState, mTaggedAnimatorSetBuilder, maxAccuracy);
if (!TouchInteractionService.isConnected()) {
mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED);
}
mCurrentAnimation.getTarget().addListener(this);
mStartProgress = 0;
mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range;
mCurrentAnimation.dispatchOnStart();
} else {
mCurrentAnimation.pause();
mStartProgress = mCurrentAnimation.getProgressFraction();
mDragPauseDetector.clearDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
updatePauseDetectorRangeFlag();
}
for (SpringAnimationHandler h : mSpringHandlers) {
h.skipToEnd();
}
}
private float getShiftRange() {
return mLauncher.getAllAppsController().getShiftRange();
}
@Override
public boolean onDrag(float displacement, float velocity) {
float deltaProgress = mProgressMultiplier * displacement;
mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
updatePauseDetectorRangeFlag();
mDragPauseDetector.onDrag(velocity);
return true;
}
private void updatePauseDetectorRangeFlag() {
if (mOverviewProgressRange.contains(mCurrentAnimation.getProgressFraction())) {
mDragPauseDetector.clearDisabledFlags(FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE);
} else {
mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE);
}
}
@Override
public void onDragEnd(float velocity, boolean fling) {
mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
final int logAction;
LauncherState targetState;
final float progress = mCurrentAnimation.getProgressFraction();
if (fling) {
logAction = Touch.FLING;
targetState = velocity < 0 ? ALL_APPS : mLauncher.getStateManager().getLastState();
// snap to top or bottom using the release velocity
} else {
logAction = Touch.SWIPE;
targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
}
if (fling && targetState == ALL_APPS) {
for (SpringAnimationHandler h : mSpringHandlers) {
// The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
}
}
float endProgress;
if (mDragPauseDetector.isTriggered() && targetState == NORMAL) {
targetState = OVERVIEW;
endProgress = OVERVIEW.getVerticalProgress(mLauncher);
if (mFromState == NORMAL) {
endProgress = 1 - endProgress;
}
} else if (targetState == mToState) {
endProgress = 1;
} else {
endProgress = 0;
}
LauncherState targetStateFinal = targetState;
mCurrentAnimation.setEndAction(() ->
onSwipeInteractionCompleted(targetStateFinal, logAction));
float nextFrameProgress = Utilities.boundToRange(
progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
anim.setFloatValues(nextFrameProgress, endProgress);
anim.setDuration(
SwipeDetector.calculateDuration(velocity, Math.abs(endProgress - progress)));
anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
anim.start();
}
private void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
if (targetState != mFromState) {
// Transition complete. log the action
mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
mToState == ALL_APPS ? Direction.UP : Direction.DOWN,
mStartContainerType,
mFromState.containerType,
mToState.containerType,
mLauncher.getWorkspace().getCurrentPage());
}
clearState();
// TODO: mQuickOverviewAnimation might still be running in which changing a state instantly
// may cause a jump. Animate the state change with a short duration in this case?
mLauncher.getStateManager().goToState(targetState, false /* animated */);
}
private void onDragPauseDetected() {
final ValueAnimator twoStepAnimator = ValueAnimator.ofFloat(0, 1);
twoStepAnimator.setDuration(mCurrentAnimation.getDuration());
StateHandler[] handlers = mLauncher.getStateManager().getStateHandlers();
// Change the current animation to only play the vertical handle
AnimatorSet anim = new AnimatorSet();
anim.playTogether(mTaggedAnimatorSetBuilder.getAnimationsForTag(
handlers[SWIPE_HANDLER_INDEX]));
anim.play(twoStepAnimator);
mCurrentAnimation = mCurrentAnimation.cloneFor(anim);
AnimatorSetBuilder builder = new AnimatorSetBuilder();
AnimationConfig config = new AnimationConfig();
config.duration = QUICK_SNAP_TO_OVERVIEW_DURATION;
for (int i = OTHER_HANDLERS_START_INDEX; i < handlers.length; i++) {
handlers[i].setStateWithAnimation(OVERVIEW, builder, config);
}
mQuickOverviewAnimation = builder.build();
mQuickOverviewAnimation.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
onQuickOverviewAnimationComplete(twoStepAnimator);
}
});
mQuickOverviewAnimation.start();
}
private void onQuickOverviewAnimationComplete(ValueAnimator animator) {
if (mAnimatingToOverview) {
return;
}
// For the remainder to the interaction, the user can either go to the ALL_APPS state or
// the OVERVIEW state.
// The remaining state handlers are on the OVERVIEW state. Create one animation towards the
// ALL_APPS state and only call it when the user moved above the current range.
AnimationConfig config = new AnimationConfig();
config.duration = (long) (2 * getShiftRange());
config.userControlled = true;
AnimatorSetBuilder builderToAllAppsState = new AnimatorSetBuilder();
StateHandler[] handlers = mLauncher.getStateManager().getStateHandlers();
for (int i = OTHER_HANDLERS_START_INDEX; i < handlers.length; i++) {
handlers[i].setStateWithAnimation(ALL_APPS, builderToAllAppsState, config);
}
mCroppedAnimationController = new CroppedAnimationController(
AnimatorPlaybackController.wrap(builderToAllAppsState.build(), config.duration),
new FloatRange(animator.getAnimatedFraction(), mToState == ALL_APPS ? 1 : 0));
animator.addUpdateListener(mCroppedAnimationController);
}
private void clearState() {
mCurrentAnimation = null;
mTaggedAnimatorSetBuilder = null;
if (mDragPauseDetector != null) {
mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_CANCEL_STATE);
}
mDragPauseDetector = null;
if (mQuickOverviewAnimation != null) {
mQuickOverviewAnimation.cancel();
mQuickOverviewAnimation = null;
}
mCroppedAnimationController = null;
mAnimatingToOverview = false;
mDetector.finishedScrolling();
}
/**
* {@link AnimatorUpdateListener} which controls another animation for a fraction of range
*/
private static class CroppedAnimationController implements AnimatorUpdateListener {
private final AnimatorPlaybackController mTarget;
private final FloatRange mRange;
CroppedAnimationController(AnimatorPlaybackController target, FloatRange range) {
mTarget = target;
mRange = range;
}
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fraction = valueAnimator.getAnimatedFraction();
if (mRange.start < mRange.end) {
if (fraction <= mRange.start) {
mTarget.setPlayFraction(0);
} else if (fraction >= mRange.end) {
mTarget.setPlayFraction(1);
} else {
mTarget.setPlayFraction((fraction - mRange.start) / (mRange.end - mRange.start));
}
} else if (mRange.start > mRange.end) {
if (fraction >= mRange.start) {
mTarget.setPlayFraction(0);
} else if (fraction <= mRange.end) {
mTarget.setPlayFraction(1);
} else {
mTarget.setPlayFraction((fraction - mRange.start) / (mRange.end - mRange.start));
}
} else {
// mRange.start == mRange.end
mTarget.setPlayFraction(0);
}
}
}
}
@@ -16,41 +16,46 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.Utilities.getPrefs;
import static com.android.quickstep.OverviewInteractionState.KEY_SWIPE_UP_ENABLED;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.BitmapRenderer;
import com.android.launcher3.R;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.RecentsView;
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.views.RecentsView;
public class UiFactory {
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
public static final boolean USE_HARDWARE_BITMAP = false; // FeatureFlags.IS_DOGFOOD_BUILD;
public static TouchController[] createTouchControllers(Launcher launcher) {
if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
SharedPreferences prefs = getPrefs(launcher);
boolean swipeUpEnabled = prefs.getBoolean(KEY_SWIPE_UP_ENABLED, true);
if (!swipeUpEnabled) {
return new TouchController[] {
new IgnoreTouchesInQuickScrub(),
new EdgeSwipeController(launcher),
new TwoStepSwipeController(launcher),
new OverviewSwipeController(launcher)};
launcher.getDragController(),
new LandscapeStatesTouchController(launcher),
new TaskViewTouchController(launcher)};
}
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return new TouchController[] {
launcher.getDragController(),
new LandscapeStatesTouchController(launcher),
new LandscapeEdgeSwipeController(launcher),
new TaskViewTouchController(launcher)};
} else {
return new TouchController[] {
new IgnoreTouchesInQuickScrub(),
new TwoStepSwipeController(launcher),
new OverviewSwipeController(launcher)};
launcher.getDragController(),
new PortraitStatesTouchController(launcher),
new TaskViewTouchController(launcher)};
}
}
@@ -64,28 +69,41 @@ public class UiFactory {
new RecentsViewStateController(launcher)};
}
public static void onWorkspaceLongPress(Launcher launcher, PointF touchPoint) {
OptionsPopupView.show(launcher, touchPoint.x, touchPoint.y);
}
public static void onLauncherStateOrFocusChanged(Launcher launcher) {
OverviewInteractionState.setBackButtonVisible(launcher, launcher == null
|| !launcher.isInState(NORMAL) || !launcher.hasWindowFocus());
}
public static Bitmap createFromRenderer(int width, int height, boolean forceSoftwareRenderer,
BitmapRenderer renderer) {
if (USE_HARDWARE_BITMAP && !forceSoftwareRenderer) {
return RecentsTransition.createHardwareBitmap(width, height, renderer::render);
} else {
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
renderer.render(new Canvas(result));
return result;
boolean shouldBackButtonBeHidden = launcher != null
&& launcher.getStateManager().getState().hideBackButton
&& launcher.hasWindowFocus();
if (shouldBackButtonBeHidden) {
// Show the back button if there is a floating view visible.
shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenView(launcher) == null;
}
OverviewInteractionState.getInstance(launcher)
.setBackButtonVisible(!shouldBackButtonBeHidden);
}
public static void resetOverview(Launcher launcher) {
RecentsView recents = launcher.getOverviewPanel();
recents.reset();
}
public static void onStart(Context context) {
RecentsModel model = RecentsModel.getInstance(context);
if (model != null) {
model.onStart();
}
}
public static void onTrimMemory(Context context, int level) {
RecentsModel model = RecentsModel.getInstance(context);
if (model != null) {
model.onTrimMemory(level);
}
}
public static View[] getHotseatExtraContent(Hotseat hotseat) {
return new View[] {
hotseat.findViewById(R.id.drag_indicator),
hotseat.findViewById(R.id.search_container_hotseat),
};
}
}
@@ -1,127 +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.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.quickstep.RecentsView;
import com.android.quickstep.RecentsView.PageCallbacks;
import com.android.quickstep.RecentsView.ScrollState;
public class WorkspaceCard extends View implements PageCallbacks, OnClickListener {
private final Rect mTempRect = new Rect();
private Launcher mLauncher;
private Workspace mWorkspace;
private float mLinearInterpolationForPage2 = 1;
private float mTranslateXPage0, mTranslateXPage1;
private float mExtraScrollShift;
private boolean mIsWorkspaceScrollingEnabled;
public WorkspaceCard(Context context) {
this(context, null);
}
public WorkspaceCard(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WorkspaceCard(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnClickListener(this);
}
/**
* Draw nothing.
*/
@Override
public void draw(Canvas canvas) { }
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// Initiate data
mLinearInterpolationForPage2 = RecentsView.getScaledDownPageRect(
mLauncher.getDeviceProfile(), mLauncher, mTempRect);
float[] scale = OverviewState.getScaleAndTranslationForPageRect(mLauncher, 0, mTempRect);
mTranslateXPage0 = scale[1];
mTranslateXPage1 = OverviewState
.getScaleAndTranslationForPageRect(mLauncher,
getResources().getDimension(R.dimen.workspace_overview_offset_x) / scale[0],
mTempRect)[1];
mExtraScrollShift = 0;
if (mWorkspace != null && getWidth() > 0) {
float workspaceWidth = mWorkspace.getNormalChildWidth() * scale[0];
mExtraScrollShift = (workspaceWidth - getWidth()) / 2;
setScaleX(workspaceWidth / getWidth());
}
}
@Override
public void onClick(View view) {
mLauncher.getStateManager().goToState(NORMAL);
}
public void setup(Launcher launcher) {
mLauncher = launcher;
mWorkspace = mLauncher.getWorkspace();
}
public void setWorkspaceScrollingEnabled(boolean isEnabled) {
mIsWorkspaceScrollingEnabled = isEnabled;
}
@Override
public int onPageScroll(ScrollState scrollState) {
float factor = scrollState.linearInterpolation;
float translateX = scrollState.distanceFromScreenCenter;
if (mIsWorkspaceScrollingEnabled) {
float shift = factor * (mTranslateXPage1 - mTranslateXPage0);
mWorkspace.setTranslationX(shift + mTranslateXPage0);
translateX += shift;
}
setTranslationX(translateX);
// If the workspace card is still the first page, shift all the other pages.
if (scrollState.linearInterpolation > mLinearInterpolationForPage2) {
scrollState.prevPageExtraWidth = 0;
} else if (mLinearInterpolationForPage2 > 0) {
scrollState.prevPageExtraWidth = mExtraScrollShift *
(1 - scrollState.linearInterpolation / mLinearInterpolationForPage2);
} else {
scrollState.prevPageExtraWidth = mExtraScrollShift;
}
return SCROLL_TYPE_WORKSPACE;
}
}
@@ -0,0 +1,367 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.view.View;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.quickstep.views.LauncherLayoutListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.AssistDataReceiver;
import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import java.util.function.BiPredicate;
/**
* Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
*/
public interface ActivityControlHelper<T extends BaseDraggingActivity> {
LayoutListener createLayoutListener(T activity);
void onQuickstepGestureStarted(T activity, boolean activityVisible);
void onQuickInteractionStart(T activity, boolean activityVisible);
void executeOnNextDraw(T activity, TaskView targetView, Runnable action);
void onTransitionCancelled(T activity, boolean activityVisible);
int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect);
void onSwipeUpComplete(T activity);
void prepareRecentsUI(T activity, boolean activityVisible);
AnimatorPlaybackController createControllerForVisibleActivity(T activity);
AnimatorPlaybackController createControllerForHiddenActivity(T activity, int transitionLength);
ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
void startRecents(Context context, Intent intent, AssistDataReceiver assistDataReceiver,
RecentsAnimationListener remoteAnimationListener);
@UiThread
@Nullable
RecentsView getVisibleRecentsView();
@UiThread
boolean switchToRecentsIfVisible();
class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
@Override
public LayoutListener createLayoutListener(Launcher activity) {
return new LauncherLayoutListener(activity);
}
@Override
public void onQuickstepGestureStarted(Launcher activity, boolean activityVisible) {
activity.onQuickstepGestureStarted(activityVisible);
}
@Override
public void onQuickInteractionStart(Launcher activity, boolean activityVisible) {
activity.getStateManager().goToState(FAST_OVERVIEW, activityVisible);
}
@Override
public void executeOnNextDraw(Launcher activity, TaskView targetView, Runnable action) {
ViewOnDrawExecutor executor = new ViewOnDrawExecutor() {
@Override
public void onViewDetachedFromWindow(View v) {
if (!isCompleted()) {
runAllTasks();
}
}
};
executor.attachTo(activity, targetView, false /* waitForLoadAnimation */);
executor.execute(action);
}
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
RecentsView.getPageRect(dp, context, outRect);
if (dp.isVerticalBarLayout()) {
Rect targetInsets = dp.getInsets();
int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
return dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset;
} else {
return dp.heightPx - outRect.bottom;
}
}
@Override
public void onTransitionCancelled(Launcher activity, boolean activityVisible) {
LauncherState startState = activity.getStateManager().getRestState();
activity.getStateManager().goToState(startState, activityVisible);
}
@Override
public void onSwipeUpComplete(Launcher activity) {
// Re apply state in case we did something funky during the transition.
activity.getStateManager().reapplyState();
}
@Override
public void prepareRecentsUI(Launcher activity, boolean activityVisible) {
LauncherState startState = activity.getStateManager().getState();
if (startState.disableRestore) {
startState = activity.getStateManager().getRestState();
}
activity.getStateManager().setRestState(startState);
if (!activityVisible) {
// Since the launcher is not visible, we can safely reset the scroll position.
// This ensures then the next swipe up to all-apps starts from scroll 0.
activity.getAppsView().reset(false /* animate */);
activity.getStateManager().goToState(OVERVIEW, false);
// Optimization, hide the all apps view to prevent layout while initializing
activity.getAppsView().getContentView().setVisibility(View.GONE);
}
}
@Override
public AnimatorPlaybackController createControllerForVisibleActivity(Launcher activity) {
DeviceProfile dp = activity.getDeviceProfile();
long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
return activity.getStateManager().createAnimationToNewWorkspace(OVERVIEW, accuracy);
}
@Override
public AnimatorPlaybackController createControllerForHiddenActivity(
Launcher activity, int transitionLength) {
AllAppsTransitionController controller = activity.getAllAppsController();
AnimatorSet anim = new AnimatorSet();
if (activity.getDeviceProfile().isVerticalBarLayout()) {
// TODO:
} else {
float scrollRange = Math.max(controller.getShiftRange(), 1);
float progressDelta = (transitionLength / scrollRange);
float endProgress = OVERVIEW.getVerticalProgress(activity);
float startProgress = endProgress + progressDelta;
ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(
controller, ALL_APPS_PROGRESS, startProgress, endProgress);
shiftAnim.setInterpolator(LINEAR);
anim.play(shiftAnim);
}
// TODO: Link this animation to state animation, so that it is cancelled
// automatically on state change
anim.setDuration(transitionLength * 2);
return AnimatorPlaybackController.wrap(anim, transitionLength * 2);
}
@Override
public ActivityInitListener createActivityInitListener(
BiPredicate<Launcher, Boolean> onInitListener) {
return new LauncherInitListener(onInitListener);
}
@Override
public void startRecents(Context context, Intent intent,
AssistDataReceiver assistDataReceiver,
RecentsAnimationListener remoteAnimationListener) {
ActivityManagerWrapper.getInstance().startRecentsActivity(
intent, assistDataReceiver, remoteAnimationListener, null, null);
}
@Nullable
@UiThread
private Launcher getVisibleLaucher() {
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app == null) {
return null;
}
Launcher launcher = (Launcher) app.getModel().getCallback();
return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
launcher : null;
}
@Nullable
@Override
public RecentsView getVisibleRecentsView() {
Launcher launcher = getVisibleLaucher();
return launcher != null && launcher.isInState(OVERVIEW)
? launcher.getOverviewPanel() : null;
}
@Override
public boolean switchToRecentsIfVisible() {
Launcher launcher = getVisibleLaucher();
if (launcher != null) {
launcher.getStateManager().goToState(OVERVIEW);
return true;
}
return false;
}
}
class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> {
@Override
public void onQuickstepGestureStarted(RecentsActivity activity, boolean activityVisible) {
// TODO:
}
@Override
public void onQuickInteractionStart(RecentsActivity activity, boolean activityVisible) {
// TODO:
}
@Override
public void executeOnNextDraw(RecentsActivity activity, TaskView targetView,
Runnable action) {
// TODO:
new Handler(Looper.getMainLooper()).post(action);
}
@Override
public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
// TODO:
}
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
FallbackRecentsView.getCenterPageRect(dp, context, outRect);
if (dp.isVerticalBarLayout()) {
Rect targetInsets = dp.getInsets();
int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
return dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset;
} else {
return dp.heightPx - outRect.bottom;
}
}
@Override
public void onSwipeUpComplete(RecentsActivity activity) {
// TODO:
}
@Override
public void prepareRecentsUI(RecentsActivity activity, boolean activityVisible) {
// TODO:
}
@Override
public AnimatorPlaybackController createControllerForVisibleActivity(
RecentsActivity activity) {
DeviceProfile dp = activity.getDeviceProfile();
return createControllerForHiddenActivity(activity, Math.max(dp.widthPx, dp.heightPx));
}
@Override
public AnimatorPlaybackController createControllerForHiddenActivity(
RecentsActivity activity, int transitionLength) {
// We do not animate anything. Create a empty controller
AnimatorSet anim = new AnimatorSet();
return AnimatorPlaybackController.wrap(anim, transitionLength * 2);
}
@Override
public LayoutListener createLayoutListener(RecentsActivity activity) {
// We do not change anything as part of layout changes in fallback activity. Return a
// default layout listener.
return new LayoutListener() {
@Override
public void open() { }
@Override
public void setHandler(WindowTransformSwipeHandler handler) { }
@Override
public void finish() { }
};
}
@Override
public ActivityInitListener createActivityInitListener(
BiPredicate<RecentsActivity, Boolean> onInitListener) {
return new RecentsActivityTracker(onInitListener);
}
@Override
public void startRecents(Context context, Intent intent,
AssistDataReceiver assistDataReceiver,
final RecentsAnimationListener remoteAnimationListener) {
ActivityOptions options =
ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
new FallbackActivityOptions(remoteAnimationListener), 10000, 10000));
context.startActivity(intent, options.toBundle());
}
@Nullable
@Override
public RecentsView getVisibleRecentsView() {
RecentsActivity activity = RecentsActivityTracker.getCurrentActivity();
if (activity != null && activity.hasWindowFocus()) {
return activity.getOverviewPanel();
}
return null;
}
@Override
public boolean switchToRecentsIfVisible() {
return false;
}
}
interface LayoutListener {
void open();
void setHandler(WindowTransformSwipeHandler handler);
void finish();
}
interface ActivityInitListener {
void register();
void unregister();
}
}
@@ -77,6 +77,12 @@ public class AnimatedFloat {
}
}
public void finishAnimation() {
if (mValueAnimator != null && mValueAnimator.isRunning()) {
mValueAnimator.end();
}
}
public ObjectAnimator getCurrentAnimation() {
return mValueAnimator;
}
@@ -1,49 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.support.annotation.WorkerThread;
import com.android.launcher3.states.InternalStateHandler;
import com.android.quickstep.TouchConsumer.InteractionType;
public abstract class BaseSwipeInteractionHandler extends InternalStateHandler {
protected Runnable mGestureEndCallback;
public void setGestureEndCallback(Runnable gestureEndCallback) {
mGestureEndCallback = gestureEndCallback;
}
public void reset() {}
@WorkerThread
public abstract void onGestureStarted();
@WorkerThread
public abstract void onGestureEnded(float endVelocity);
public abstract void updateInteractionType(@InteractionType int interactionType);
@WorkerThread
public abstract void onQuickScrubEnd();
@WorkerThread
public abstract void onQuickScrubProgress(float progress);
@WorkerThread
public abstract void updateDisplacement(float displacement);
}
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.annotation.TargetApi;
import android.os.Build;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.VelocityTracker;
/**
* A TouchConsumer which defers all events on the UIThread until the consumer is created.
*/
@TargetApi(Build.VERSION_CODES.P)
public class DeferredTouchConsumer implements TouchConsumer {
private final VelocityTracker mVelocityTracker;
private final DeferredTouchProvider mTouchProvider;
private MotionEventQueue mMyQueue;
private TouchConsumer mTarget;
public DeferredTouchConsumer(DeferredTouchProvider touchProvider) {
mVelocityTracker = VelocityTracker.obtain();
mTouchProvider = touchProvider;
}
@Override
public void accept(MotionEvent event) {
mTarget.accept(event);
}
@Override
public void reset() {
mTarget.reset();
}
@Override
public void updateTouchTracking(int interactionType) {
mTarget.updateTouchTracking(interactionType);
}
@Override
public void onQuickScrubEnd() {
mTarget.onQuickScrubEnd();
}
@Override
public void onQuickScrubProgress(float progress) {
mTarget.onQuickScrubProgress(progress);
}
@Override
public void preProcessMotionEvent(MotionEvent ev) {
mVelocityTracker.addMovement(ev);
}
@Override
public Choreographer getIntrimChoreographer(MotionEventQueue queue) {
mMyQueue = queue;
return null;
}
@Override
public void deferInit() {
mTarget = mTouchProvider.createTouchConsumer(mVelocityTracker);
mTarget.getIntrimChoreographer(mMyQueue);
}
@Override
public boolean forceToLauncherConsumer() {
return mTarget.forceToLauncherConsumer();
}
@Override
public boolean deferNextEventToMainThread() {
// If our target is still null, defer the next target as well
TouchConsumer target = mTarget;
return target == null ? true : target.deferNextEventToMainThread();
}
public interface DeferredTouchProvider {
TouchConsumer createTouchConsumer(VelocityTracker tracker);
}
}
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.graphics.Rect;
import android.util.Log;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
/**
* Temporary class to create activity options to emulate recents transition for fallback activtiy.
*/
public class FallbackActivityOptions implements RemoteAnimationRunnerCompat {
private final RecentsAnimationListener mListener;
public FallbackActivityOptions(RecentsAnimationListener listener) {
mListener = listener;
}
@Override
public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats,
Runnable runnable) {
DummyRecentsAnimationControllerCompat dummyRecentsAnim =
new DummyRecentsAnimationControllerCompat(runnable);
Rect insets = new Rect();
WindowManagerWrapper.getInstance().getStableInsets(insets);
mListener.onAnimationStart(dummyRecentsAnim, targetCompats, insets, null);
}
@Override
public void onAnimationCancelled() {
mListener.onAnimationCanceled();
}
private static class DummyRecentsAnimationControllerCompat
extends RecentsAnimationControllerCompat {
final Runnable mFinishCallback;
public DummyRecentsAnimationControllerCompat(Runnable finishCallback) {
mFinishCallback = finishCallback;
}
@Override
public ThumbnailData screenshotTask(int taskId) {
return new ThumbnailData();
}
@Override
public void setInputConsumerEnabled(boolean enabled) { }
@Override
public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) { }
@Override
public void finish(boolean toHome) {
if (toHome) {
mFinishCallback.run();
}
}
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.quickstep.views.RecentsView;
public class FallbackRecentsView extends RecentsView<RecentsActivity> implements Insettable {
public FallbackRecentsView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOverviewStateEnabled(true);
}
@Override
protected void onAllTasksRemoved() {
mActivity.finish();
}
@Override
public void setInsets(Rect insets) {
mInsets.set(insets);
DeviceProfile dp = mActivity.getDeviceProfile();
Rect padding = getPadding(dp, getContext());
verticalCenter(padding, dp);
setPadding(padding.left, padding.top, padding.right, padding.bottom);
}
private static void verticalCenter(Rect padding, DeviceProfile dp) {
Rect insets = dp.getInsets();
int totalSpace = (padding.top + padding.bottom - insets.top - insets.bottom) / 2;
padding.top = insets.top + totalSpace;
padding.bottom = insets.bottom + totalSpace;
}
public static void getCenterPageRect(DeviceProfile grid, Context context, Rect outRect) {
Rect targetPadding = getPadding(grid, context);
verticalCenter(targetPadding, grid);
Rect insets = grid.getInsets();
outRect.set(
targetPadding.left + insets.left,
targetPadding.top + insets.top,
grid.widthPx - targetPadding.right - insets.right,
grid.heightPx - targetPadding.bottom - insets.bottom);
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstantAppInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import com.android.launcher3.AppInfo;
import com.android.launcher3.util.InstantAppResolver;
import java.util.ArrayList;
import java.util.List;
/**
* Implementation of InstantAppResolver using platform APIs
*/
@SuppressWarnings("unused")
public class InstantAppResolverImpl extends InstantAppResolver {
private static final String TAG = "InstantAppResolverImpl";
public static final String COMPONENT_CLASS_MARKER = "@instantapp";
private final PackageManager mPM;
public InstantAppResolverImpl(Context context)
throws NoSuchMethodException, ClassNotFoundException {
mPM = context.getPackageManager();
}
@Override
public boolean isInstantApp(ApplicationInfo info) {
return info.isInstantApp();
}
@Override
public boolean isInstantApp(AppInfo info) {
ComponentName cn = info.getTargetComponent();
return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
}
@Override
public List<ApplicationInfo> getInstantApps() {
try {
List<ApplicationInfo> result = new ArrayList<>();
for (InstantAppInfo iai : mPM.getInstantApps()) {
ApplicationInfo info = iai.getApplicationInfo();
if (info != null) {
result.add(info);
}
}
return result;
} catch (SecurityException se) {
Log.w(TAG, "getInstantApps failed. Launcher may not be the default home app.", se);
} catch (Exception e) {
Log.e(TAG, "Error calling API: getInstantApps", e);
}
return super.getInstantApps();
}
}
@@ -0,0 +1,96 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.ResolveInfo;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.os.Build;
import android.provider.SearchIndexablesContract.XmlResource;
import android.provider.SearchIndexablesProvider;
import android.util.Xml;
import com.android.launcher3.R;
import com.android.launcher3.graphics.IconShapeOverride;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS;
import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS;
@TargetApi(Build.VERSION_CODES.O)
public class LauncherSearchIndexablesProvider extends SearchIndexablesProvider {
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor queryXmlResources(String[] strings) {
MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
ResolveInfo settingsActivity = getContext().getPackageManager().resolveActivity(
new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
.setPackage(getContext().getPackageName()), 0);
cursor.newRow()
.add(XmlResource.COLUMN_XML_RESID, R.xml.indexable_launcher_prefs)
.add(XmlResource.COLUMN_INTENT_ACTION, Intent.ACTION_APPLICATION_PREFERENCES)
.add(XmlResource.COLUMN_INTENT_TARGET_PACKAGE, getContext().getPackageName())
.add(XmlResource.COLUMN_INTENT_TARGET_CLASS, settingsActivity.activityInfo.name);
return cursor;
}
@Override
public Cursor queryRawData(String[] projection) {
return new MatrixCursor(INDEXABLES_RAW_COLUMNS);
}
@Override
public Cursor queryNonIndexableKeys(String[] projection) {
MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
if (!getContext().getSystemService(LauncherApps.class).hasShortcutHostPermission()) {
// We are not the current launcher. Hide all preferences
try (XmlResourceParser parser = getContext().getResources()
.getXml(R.xml.indexable_launcher_prefs)) {
final int depth = parser.getDepth();
final int[] attrs = new int[] { android.R.attr.key };
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type == XmlPullParser.START_TAG) {
TypedArray a = getContext().obtainStyledAttributes(
Xml.asAttributeSet(parser), attrs);
cursor.addRow(new String[] {a.getString(0)});
a.recycle();
}
}
} catch (IOException |XmlPullParserException e) {
throw new RuntimeException(e);
}
} else if (!IconShapeOverride.isSupported(getContext())) {
cursor.addRow(new String[] {IconShapeOverride.KEY_PREFERENCE});
}
return cursor;
}
}
@@ -19,9 +19,7 @@ import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_MASK;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SWITCH;
import android.annotation.TargetApi;
import android.os.Build;
@@ -43,16 +41,18 @@ public class MotionEventQueue {
private static final int ACTION_VIRTUAL = ACTION_MASK - 1;
private static final int ACTION_QUICK_SWITCH =
ACTION_VIRTUAL | (1 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_QUICK_SCRUB_START =
ACTION_VIRTUAL | (2 << ACTION_POINTER_INDEX_SHIFT);
ACTION_VIRTUAL | (1 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_QUICK_SCRUB_PROGRESS =
ACTION_VIRTUAL | (3 << ACTION_POINTER_INDEX_SHIFT);
ACTION_VIRTUAL | (2 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_QUICK_SCRUB_END =
ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
ACTION_VIRTUAL | (3 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_RESET =
ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_DEFER_INIT =
ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_SHOW_OVERVIEW_FROM_ALT_TAB =
ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
private final EventArray mEmptyArray = new EventArray();
private final Object mExecutionLock = new Object();
@@ -76,10 +76,10 @@ public class MotionEventQueue {
public MotionEventQueue(Choreographer choreographer, TouchConsumer consumer) {
mMainChoreographer = choreographer;
mConsumer = consumer;
mCurrentChoreographer = mMainChoreographer;
mCurrentRunnable = mMainFrameCallback;
setInterimChoreographerLocked(consumer.getIntrimChoreographer(this));
setInterimChoreographer(consumer.getIntrimChoreographer(this));
}
public void setInterimChoreographer(Choreographer choreographer) {
@@ -141,9 +141,6 @@ public class MotionEventQueue {
MotionEvent event = array.get(i);
if (event.getActionMasked() == ACTION_VIRTUAL) {
switch (event.getAction()) {
case ACTION_QUICK_SWITCH:
mConsumer.updateTouchTracking(INTERACTION_QUICK_SWITCH);
break;
case ACTION_QUICK_SCRUB_START:
mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
break;
@@ -156,6 +153,13 @@ public class MotionEventQueue {
case ACTION_RESET:
mConsumer.reset();
break;
case ACTION_DEFER_INIT:
mConsumer.deferInit();
break;
case ACTION_SHOW_OVERVIEW_FROM_ALT_TAB:
mConsumer.onShowOverviewFromAltTab();
mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
break;
default:
Log.e(TAG, "Invalid virtual event: " + event.getAction());
}
@@ -184,14 +188,14 @@ public class MotionEventQueue {
queueNoPreProcess(MotionEvent.obtain(0, 0, action, progress, 0, 0));
}
public void onQuickSwitch() {
queueVirtualAction(ACTION_QUICK_SWITCH, 0);
}
public void onQuickScrubStart() {
queueVirtualAction(ACTION_QUICK_SCRUB_START, 0);
}
public void onOverviewShownFromAltTab() {
queueVirtualAction(ACTION_SHOW_OVERVIEW_FROM_ALT_TAB, 0);
}
public void onQuickScrubProgress(float progress) {
queueVirtualAction(ACTION_QUICK_SCRUB_PROGRESS, progress);
}
@@ -204,6 +208,14 @@ public class MotionEventQueue {
queueVirtualAction(ACTION_RESET, 0);
}
public void deferInit() {
queueVirtualAction(ACTION_DEFER_INIT, 0);
}
public TouchConsumer getConsumer() {
return mConsumer;
}
private static class EventArray extends ArrayList<MotionEvent> {
public int lastEventAction = ACTION_CANCEL;
@@ -1,392 +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 static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SWITCH;
import static com.android.quickstep.TouchConsumer.isInteractionQuick;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.RectEvaluator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.UiThread;
import android.view.View;
import android.view.ViewTreeObserver.OnPreDrawListener;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.TouchConsumer.InteractionType;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.WindowManagerWrapper;
@TargetApi(Build.VERSION_CODES.O)
public class NavBarSwipeInteractionHandler extends BaseSwipeInteractionHandler implements
OnResumeCallback {
private static final int STATE_LAUNCHER_READY = 1 << 0;
private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 4;
private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 5;
private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 6;
private static final long MAX_SWIPE_DURATION = 200;
private static final long MIN_SWIPE_DURATION = 80;
// Ideal velocity for a smooth transition
private static final float PIXEL_PER_MS = 2f;
private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
private final Rect mStableInsets = new Rect();
private final Rect mSourceRect = new Rect();
private final Rect mTargetRect = new Rect();
private final Rect mCurrentRect = new Rect();
private final RectEvaluator mRectEvaluator = new RectEvaluator(mCurrentRect);
// Shift in the range of [0, 1].
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
// 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
// visible.
private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
// Activity multiplier in the range of [0, 1]. When the activity becomes visible, this is
// animated to 1, so allow for a smooth transition.
private final AnimatedFloat mActivityMultiplier = new AnimatedFloat(this::updateFinalShift);
private final int mRunningTaskId;
private final Context mContext;
private final MultiStateCallback mStateCallback;
private Launcher mLauncher;
private SnapshotDragView mDragView;
private RecentsView mRecentsView;
private QuickScrubController mQuickScrubController;
private Hotseat mHotseat;
private boolean mWasLauncherAlreadyVisible;
private boolean mLauncherReady;
private boolean mTouchEndHandled;
private float mCurrentDisplacement;
private @InteractionType int mInteractionType;
private boolean mStartedQuickScrubFromHome;
private Bitmap mTaskSnapshot;
NavBarSwipeInteractionHandler(RunningTaskInfo runningTaskInfo, Context context,
@InteractionType int interactionType) {
mContext = context;
mInteractionType = interactionType;
mRunningTaskId = runningTaskInfo.id;
WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
// TODO: If in multi window mode, dp = dp.getMultiWindowProfile()
dp = dp.copy(mContext);
// TODO: Use different insets for multi-window mode
dp.updateInsets(mStableInsets);
RecentsView.getPageRect(dp, mContext, mTargetRect);
mSourceRect.set(0, 0, dp.widthPx - mStableInsets.left - mStableInsets.right,
dp.heightPx - mStableInsets.top - mStableInsets.bottom);
// Build the state callback
mStateCallback = new MultiStateCallback();
mStateCallback.addCallback(STATE_LAUNCHER_READY, this::onLauncherReady);
mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_APP, this::resumeLastTask);
mStateCallback.addCallback(
STATE_SCALED_SNAPSHOT_RECENTS | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
this::onAnimationToLauncherComplete);
mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_APP,
this::cleanupLauncher);
}
private void onLauncherReady() {
mLauncherReady = true;
executeFrameUpdate();
long duration = Math.min(MAX_SWIPE_DURATION,
Math.max((long) (-mCurrentDisplacement / PIXEL_PER_MS), MIN_SWIPE_DURATION));
if (mCurrentShift.getCurrentAnimation() != null) {
ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
long theirDuration = anim.getDuration() - anim.getCurrentPlayTime();
// TODO: Find a better heuristic
duration = (duration + theirDuration) / 2;
}
ObjectAnimator anim = mActivityMultiplier.animateToValue(1)
.setDuration(duration);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
}
});
anim.start();
}
public void setTaskSnapshot(Bitmap taskSnapshot) {
mTaskSnapshot = taskSnapshot;
}
@Override
public void onLauncherResume() {
TraceHelper.partitionSection("TouchInt", "Launcher On resume");
mDragView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mDragView.getViewTreeObserver().removeOnPreDrawListener(this);
mStateCallback.setState(STATE_LAUNCHER_READY);
TraceHelper.partitionSection("TouchInt", "Launcher drawn");
return true;
}
});
}
@Override
protected boolean init(Launcher launcher, boolean alreadyOnHome) {
launcher.setOnResumeCallback(this);
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
mRecentsView.showTask(mRunningTaskId);
mHotseat = mLauncher.getHotseat();
mWasLauncherAlreadyVisible = alreadyOnHome;
AbstractFloatingView.closeAllOpenViews(mLauncher, alreadyOnHome);
mLauncher.getStateManager().goToState(LauncherState.OVERVIEW, alreadyOnHome);
mDragView = new SnapshotDragView(mLauncher, mTaskSnapshot);
mLauncher.getDragLayer().addView(mDragView);
mDragView.setPivotX(0);
mDragView.setPivotY(0);
if (isInteractionQuick(mInteractionType)) {
updateUiForQuickScrub();
}
// Optimization
if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
// All-apps search box is visible in vertical bar layout.
mLauncher.getAppsView().setVisibility(View.GONE);
}
TraceHelper.partitionSection("TouchInt", "Launcher on new intent");
return false;
}
public void updateInteractionType(@InteractionType int interactionType) {
Preconditions.assertUIThread();
if (mInteractionType != INTERACTION_NORMAL) {
throw new IllegalArgumentException(
"Can't change interaction type from " + mInteractionType);
}
if (!isInteractionQuick(interactionType)) {
throw new IllegalArgumentException(
"Can't change interaction type to " + interactionType);
}
mInteractionType = interactionType;
if (mLauncher != null) {
updateUiForQuickScrub();
}
}
private void updateUiForQuickScrub() {
mStartedQuickScrubFromHome = mWasLauncherAlreadyVisible;
mQuickScrubController = mRecentsView.getQuickScrubController();
mQuickScrubController.onQuickScrubStart(mStartedQuickScrubFromHome);
animateToProgress(1f, MAX_SWIPE_DURATION);
if (mStartedQuickScrubFromHome) {
mDragView.setVisibility(View.INVISIBLE);
}
}
@UiThread
public void updateDisplacement(float displacement) {
mCurrentDisplacement = displacement;
executeFrameUpdate();
}
private void executeFrameUpdate() {
if (mLauncherReady) {
final float displacement = -mCurrentDisplacement;
int hotseatSize = getHotseatSize();
float translation = Utilities.boundToRange(displacement, 0, hotseatSize);
float shift = hotseatSize == 0 ? 0 : translation / hotseatSize;
mCurrentShift.updateValue(shift);
}
}
@UiThread
private void updateFinalShift() {
if (!mLauncherReady || mStartedQuickScrubFromHome) {
return;
}
float shift = mCurrentShift.value * mActivityMultiplier.value;
AllAppsTransitionController controller = mLauncher.getAllAppsController();
float range = getHotseatSize() / controller.getShiftRange();
controller.setProgress(1 + (1 - shift) * range);
mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
float scale = (float) mCurrentRect.width() / mSourceRect.width();
mDragView.setTranslationX(mCurrentRect.left - mStableInsets.left * scale * shift);
mDragView.setTranslationY(mCurrentRect.top - mStableInsets.top * scale * shift);
mDragView.setScaleX(scale);
mDragView.setScaleY(scale);
// TODO: mDragView.getViewBounds().setClipLeft((int) (mStableInsets.left * shift));
mDragView.getViewBounds().setClipTop((int) (mStableInsets.top * shift));
// TODO: mDragView.getViewBounds().setClipRight((int) (mStableInsets.right * shift));
mDragView.getViewBounds().setClipBottom((int) (mStableInsets.bottom * shift));
}
private int getHotseatSize() {
return mLauncher.getDeviceProfile().isVerticalBarLayout()
? mHotseat.getWidth() : mHotseat.getHeight();
}
@Override
public void onGestureStarted() { }
@UiThread
public void onGestureEnded(float endVelocity) {
if (mTouchEndHandled) {
return;
}
mTouchEndHandled = true;
Resources res = mContext.getResources();
float flingThreshold = res.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = Math.abs(endVelocity) > flingThreshold;
long duration = MAX_SWIPE_DURATION;
final float endShift;
if (!isFling) {
endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0;
} else {
endShift = endVelocity < 0 ? 1 : 0;
float minFlingVelocity = res.getDimension(R.dimen.quickstep_fling_min_velocity);
if (Math.abs(endVelocity) > minFlingVelocity && mLauncherReady) {
float distanceToTravel = (endShift - mCurrentShift.value) * getHotseatSize();
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
// derivative of the scroll interpolator at zero, ie. 5.
duration = 5 * Math.round(1000 * Math.abs(distanceToTravel / endVelocity));
}
}
animateToProgress(endShift, duration);
}
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
private void animateToProgress(float progress, long duration) {
ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
anim.setInterpolator(Interpolators.SCROLL);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
}
});
anim.start();
}
@UiThread
private void resumeLastTask() {
RecentsTaskLoadPlan loadPlan = RecentsModel.getInstance(mContext).getLastLoadPlan();
if (loadPlan != null) {
Task task = loadPlan.getTaskStack().findTaskWithId(mRunningTaskId);
if (task != null) {
ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
null, null);
}
}
}
public void reset() {
mCurrentShift.cancelAnimation();
if (mGestureEndCallback != null) {
mGestureEndCallback.run();
}
}
private void cleanupLauncher() {
reset();
// TODO: These should be done as part of ActivityOptions#OnAnimationStarted
mLauncher.getStateManager().reapplyState();
mLauncher.setOnResumeCallback(() -> mDragView.close(false));
}
private void onAnimationToLauncherComplete() {
reset();
mDragView.close(false);
View currentRecentsPage = mRecentsView.getPageAt(mRecentsView.getCurrentPage());
if (currentRecentsPage instanceof TaskView) {
((TaskView) currentRecentsPage).animateIconToScale(1f);
}
if (mInteractionType == INTERACTION_QUICK_SWITCH) {
if (mQuickScrubController != null) {
mQuickScrubController.onQuickSwitch();
}
}
}
public void onQuickScrubEnd() {
if (mQuickScrubController != null) {
mQuickScrubController.onQuickScrubEnd();
} else {
// TODO:
}
}
public void onQuickScrubProgress(float progress) {
if (mQuickScrubController != null) {
mQuickScrubController.onQuickScrubProgress(progress);
} else {
// TODO:
}
}
}
@@ -16,19 +16,20 @@
package com.android.quickstep;
import android.annotation.TargetApi;
import android.app.ActivityManager.TaskDescription;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.UserHandle;
import android.util.LruCache;
import android.util.SparseArray;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.graphics.BitmapInfo;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.systemui.shared.recents.model.IconLoader;
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
@@ -40,11 +41,13 @@ import com.android.systemui.shared.recents.model.TaskKeyLruCache;
public class NormalizedIconLoader extends IconLoader {
private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
private final DrawableFactory mDrawableFactory;
private LauncherIcons mLauncherIcons;
public NormalizedIconLoader(Context context, TaskKeyLruCache<Drawable> iconCache,
LruCache<ComponentName, ActivityInfo> activityInfoCache) {
super(context, iconCache, activityInfoCache);
mDrawableFactory = DrawableFactory.get(context);
}
@Override
@@ -53,7 +56,7 @@ public class NormalizedIconLoader extends IconLoader {
BitmapInfo info = mDefaultIcons.get(userId);
if (info == null) {
info = getBitmapInfo(Resources.getSystem()
.getDrawable(android.R.drawable.sym_def_app_icon), userId);
.getDrawable(android.R.drawable.sym_def_app_icon), userId, 0, false);
mDefaultIcons.put(userId, info);
}
@@ -62,23 +65,31 @@ public class NormalizedIconLoader extends IconLoader {
}
@Override
protected Drawable createBadgedDrawable(Drawable drawable, int userId) {
return new FastBitmapDrawable(getBitmapInfo(drawable, userId));
protected Drawable createBadgedDrawable(Drawable drawable, int userId, TaskDescription desc) {
return new FastBitmapDrawable(getBitmapInfo(drawable, userId, desc.getPrimaryColor(),
false));
}
private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId) {
private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId,
int primaryColor, boolean isInstantApp) {
if (mLauncherIcons == null) {
mLauncherIcons = LauncherIcons.obtain(mContext);
}
mLauncherIcons.setWrapperBackgroundColor(primaryColor);
// User version code O, so that the icon is always wrapped in an adaptive icon container.
return mLauncherIcons.createBadgedIconBitmap(drawable, UserHandle.of(userId),
Build.VERSION_CODES.O);
Build.VERSION_CODES.O, isInstantApp);
}
@Override
protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId) {
return createBadgedDrawable(
activityInfo.loadUnbadgedIcon(mContext.getPackageManager()), userId);
protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId,
TaskDescription desc) {
BitmapInfo bitmapInfo = getBitmapInfo(
activityInfo.loadUnbadgedIcon(mContext.getPackageManager()),
userId,
desc.getPrimaryColor(),
activityInfo.applicationInfo.isInstantApp());
return mDrawableFactory.newIcon(bitmapInfo, activityInfo);
}
}
@@ -21,27 +21,20 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
import static com.android.quickstep.RemoteRunnable.executeSafely;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.Choreographer;
import android.view.Display;
import android.view.MotionEvent;
@@ -51,9 +44,7 @@ import android.view.ViewConfiguration;
import android.view.WindowManager;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.TraceHelper;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.AssistDataReceiver;
import com.android.systemui.shared.system.BackgroundExecutor;
@@ -63,87 +54,60 @@ import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
class EventLogTags {
private EventLogTags() {
} // don't instantiate
/** 524292 sysui_multi_action (content|4) */
public static final int SYSUI_MULTI_ACTION = 524292;
public static void writeSysuiMultiAction(Object[] content) {
android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
}
}
class MetricsLogger {
private static MetricsLogger sMetricsLogger;
private static MetricsLogger getLogger() {
if (sMetricsLogger == null) {
sMetricsLogger = new MetricsLogger();
}
return sMetricsLogger;
}
protected void saveLog(Object[] rep) {
EventLogTags.writeSysuiMultiAction(rep);
}
public void write(LogMaker content) {
if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
content.setType(4/*MetricsEvent.TYPE_ACTION*/);
}
saveLog(content.serialize());
}
}
/**
* Touch consumer for handling events originating from an activity other than Launcher
*/
@TargetApi(Build.VERSION_CODES.P)
public class OtherActivityTouchConsumer extends ContextWrapper implements TouchConsumer {
private static final String TAG = "ActivityTouchConsumer";
private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150;
private static final int[] DEFERRED_HIT_TARGETS = false
? new int[] {HIT_TARGET_BACK, HIT_TARGET_OVERVIEW} : new int[] {HIT_TARGET_BACK};
private final RunningTaskInfo mRunningTask;
private final RecentsModel mRecentsModel;
private final Intent mHomeIntent;
private final ISystemUiProxy mISystemUiProxy;
private final ActivityControlHelper mActivityControlHelper;
private final MainThreadExecutor mMainThreadExecutor;
private final Choreographer mBackgroundThreadChoreographer;
private final boolean mIsDeferredDownTarget;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private int mActivePointerId = INVALID_POINTER_ID;
private boolean mTouchThresholdCrossed;
private int mTouchSlop;
private float mStartDisplacement;
private BaseSwipeInteractionHandler mInteractionHandler;
private WindowTransformSwipeHandler mInteractionHandler;
private int mDisplayRotation;
private Rect mStableInsets = new Rect();
private @HitTarget int mDownHitTarget = HIT_TARGET_NONE;
private VelocityTracker mVelocityTracker;
private MotionEventQueue mEventQueue;
private final MetricsLogger mMetricsLogger = new MetricsLogger();
private boolean mIsGoingToHome;
public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
RecentsModel recentsModel, Intent homeIntent, ISystemUiProxy systemUiProxy,
RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
@HitTarget int downHitTarget) {
@HitTarget int downHitTarget, VelocityTracker velocityTracker) {
super(base);
mRunningTask = runningTaskInfo;
mRecentsModel = recentsModel;
mHomeIntent = homeIntent;
mVelocityTracker = VelocityTracker.obtain();
mISystemUiProxy = systemUiProxy;
mVelocityTracker = velocityTracker;
mActivityControlHelper = activityControl;
mMainThreadExecutor = mainThreadExecutor;
mBackgroundThreadChoreographer = backgroundThreadChoreographer;
mDownHitTarget = downHitTarget;
mIsDeferredDownTarget = Arrays.binarySearch(DEFERRED_HIT_TARGETS, downHitTarget) >= 0;
}
@Override
public void onShowOverviewFromAltTab() {
startTouchTrackingForWindowAnimation(SystemClock.uptimeMillis());
}
@Override
@@ -162,7 +126,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
// Start the window animation on down to give more time for launcher to draw if the
// user didn't start the gesture over the back button
if (!isUsingScreenShot() && mDownHitTarget != HIT_TARGET_BACK) {
if (!mIsDeferredDownTarget) {
startTouchTrackingForWindowAnimation(ev.getEventTime());
}
@@ -202,14 +166,11 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
if (mTouchThresholdCrossed) {
mStartDisplacement = Math.signum(displacement) * mTouchSlop;
if (isUsingScreenShot()) {
startTouchTrackingForScreenshotAnimation();
} else if (mDownHitTarget == HIT_TARGET_BACK) {
if (mIsDeferredDownTarget) {
// If we deferred starting the window animation on touch down, then
// start tracking now
startTouchTrackingForWindowAnimation(ev.getEventTime());
}
notifyGestureStarted();
}
} else if (mInteractionHandler != null) {
@@ -235,11 +196,6 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
}
// Notify the handler that the gesture has actually started
mInteractionHandler.onGestureStarted();
// Notify the system that we have started tracking the event
if (mISystemUiProxy != null) {
executeSafely(mISystemUiProxy::onRecentsAnimationStarted);
}
}
private boolean isNavBarOnRight() {
@@ -250,69 +206,10 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
}
private boolean isUsingScreenShot() {
return Utilities.getPrefs(this).getBoolean("pref_use_screenshot_for_swipe_up", false);
}
/**
* Called when the gesture has started.
*/
private void startTouchTrackingForScreenshotAnimation() {
// Create the shared handler
final NavBarSwipeInteractionHandler handler =
new NavBarSwipeInteractionHandler(mRunningTask, this, INTERACTION_NORMAL);
TraceHelper.partitionSection("TouchInt", "Thershold crossed ");
// Start the recents activity on a background thread
BackgroundExecutor.get().submit(() -> {
// Get the snap shot before
handler.setTaskSnapshot(getCurrentTaskSnapshot());
// Start the launcher activity with our custom handler
Intent homeIntent = handler.addToIntent(new Intent(mHomeIntent));
startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle());
TraceHelper.partitionSection("TouchInt", "Home started");
});
// Preload the plan
mRecentsModel.loadTasks(mRunningTask.id, null);
mInteractionHandler = handler;
mInteractionHandler.setGestureEndCallback(mEventQueue::reset);
}
private Bitmap getCurrentTaskSnapshot() {
TraceHelper.beginSection("TaskSnapshot");
// TODO: We are using some hardcoded layers for now, to best approximate the activity layers
Point displaySize = new Point();
Display display = getSystemService(WindowManager.class).getDefaultDisplay();
display.getRealSize(displaySize);
int rotation = display.getRotation();
// The rotation is backwards in landscape, so flip it.
if (rotation == Surface.ROTATION_270) {
rotation = Surface.ROTATION_90;
} else if (rotation == Surface.ROTATION_90) {
rotation = Surface.ROTATION_270;
}
try {
return mISystemUiProxy.screenshot(new Rect(), displaySize.x, displaySize.y, 0, 100000,
false, rotation).toBitmap();
} catch (Exception e) {
Log.e(TAG, "Error capturing snapshot", e);
// Return a dummy bitmap
Bitmap bitmap = Bitmap.createBitmap(displaySize.x, displaySize.y, Config.RGB_565);
bitmap.eraseColor(Color.WHITE);
return bitmap;
} finally {
TraceHelper.endSection("TaskSnapshot");
}
}
private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
// Create the shared handler
final WindowTransformSwipeHandler handler =
new WindowTransformSwipeHandler(mRunningTask, this);
final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
mRunningTask, this, touchTimeMs, mActivityControlHelper);
// Preload the plan
mRecentsModel.loadTasks(mRunningTask.id, null);
@@ -329,8 +226,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
handler.initWhenReady();
TraceHelper.beginSection("RecentsController");
Runnable startActivity = () -> ActivityManagerWrapper.getInstance()
.startRecentsActivity(mHomeIntent,
Runnable startActivity = () -> mActivityControlHelper.startRecents(this, mHomeIntent,
new AssistDataReceiver() {
@Override
public void onHandleAssistData(Bundle bundle) {
@@ -350,16 +246,6 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
TraceHelper.endSection("RecentsController", "Finishing no handler");
controller.finish(false /* toHome */);
}
// Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
// "Recents" activity for app transition tests.
final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
builder.setPackageName("com.android.systemui");
builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
"com.android.systemui.recents.RecentsActivity");
builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
SystemClock.uptimeMillis() - touchTimeMs);
mMetricsLogger.write(builder);
}
public void onAnimationCanceled() {
@@ -369,7 +255,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
handler.onRecentsAnimationCanceled();
}
}
}, null, null);
});
if (Looper.myLooper() != Looper.getMainLooper()) {
startActivity.run();
@@ -398,7 +284,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
: isNavBarOnLeft() ? -mVelocityTracker.getXVelocity(mActivePointerId)
: mVelocityTracker.getYVelocity(mActivePointerId);
mInteractionHandler.onGestureEnded(velocity);
} else if (!isUsingScreenShot()) {
} else {
// Since we start touch tracking on DOWN, we may reach this state without actually
// starting the gesture. In that case, just cleanup immediately.
reset();
@@ -415,8 +301,9 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
public void reset() {
// Clean up the old interaction handler
if (mInteractionHandler != null) {
final BaseSwipeInteractionHandler handler = mInteractionHandler;
final WindowTransformSwipeHandler handler = mInteractionHandler;
mInteractionHandler = null;
mIsGoingToHome = handler.mIsGoingToHome;
mMainThreadExecutor.execute(handler::reset);
}
}
@@ -424,24 +311,15 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
@Override
public void updateTouchTracking(int interactionType) {
notifyGestureStarted();
if (isUsingScreenShot()) {
mMainThreadExecutor.execute(() -> {
if (mInteractionHandler != null) {
mInteractionHandler.updateInteractionType(interactionType);
}
});
} else {
if (mInteractionHandler != null) {
mInteractionHandler.updateInteractionType(interactionType);
}
if (mInteractionHandler != null) {
mInteractionHandler.updateInteractionType(interactionType);
}
}
@Override
public Choreographer getIntrimChoreographer(MotionEventQueue queue) {
mEventQueue = queue;
return isUsingScreenShot() ? null : mBackgroundThreadChoreographer;
return mBackgroundThreadChoreographer;
}
@Override
@@ -471,4 +349,15 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
}
}
}
@Override
public boolean forceToLauncherConsumer() {
return mIsGoingToHome;
}
@Override
public boolean deferNextEventToMainThread() {
// TODO: Consider also check if the eventQueue is using mainThread of not.
return mInteractionHandler != null;
}
}
@@ -0,0 +1,369 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static com.android.launcher3.LauncherState.OVERVIEW;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
import android.util.SparseArray;
import android.view.ViewConfiguration;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.states.InternalStateHandler;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.AssistDataReceiver;
import com.android.systemui.shared.system.BackgroundExecutor;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/**
* Helper class to handle various atomic commands for switching between Overview.
*/
@TargetApi(Build.VERSION_CODES.P)
public class OverviewCommandHelper extends InternalStateHandler {
private static final int RID_RESET_SWIPE_HANDLER = 0;
private static final int RID_CANCEL_CONTROLLER = 1;
private static final int RID_CANCEL_ZOOM_OUT_ANIMATION = 2;
private static final long RECENTS_LAUNCH_DURATION = 150;
private static final String TAG = "OverviewCommandHelper";
private static final boolean DEBUG_START_FALLBACK_ACTIVITY = false;
private final Context mContext;
private final ActivityManagerWrapper mAM;
private final RecentsModel mRecentsModel;
private final MainThreadExecutor mMainThreadExecutor;
public final Intent homeIntent;
public final ComponentName launcher;
private final SparseArray<Runnable> mCurrentCommandFinishRunnables = new SparseArray<>();
// Monotonically increasing command ids.
private int mCurrentCommandId = 0;
private long mLastToggleTime;
private WindowTransformSwipeHandler mWindowTransformSwipeHandler;
private final Point mWindowSize = new Point();
private final Rect mTaskTargetRect = new Rect();
private final RectF mTempTaskTargetRect = new RectF();
public OverviewCommandHelper(Context context) {
mContext = context;
mAM = ActivityManagerWrapper.getInstance();
mMainThreadExecutor = new MainThreadExecutor();
mRecentsModel = RecentsModel.getInstance(mContext);
homeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setPackage(context.getPackageName())
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ResolveInfo info = context.getPackageManager().resolveActivity(homeIntent, 0);
if (DEBUG_START_FALLBACK_ACTIVITY) {
launcher = new ComponentName(context, RecentsActivity.class);
homeIntent.addCategory(Intent.CATEGORY_DEFAULT)
.removeCategory(Intent.CATEGORY_HOME);
} else {
launcher = new ComponentName(context.getPackageName(), info.activityInfo.name);
}
// Clear the packageName as system can fail to dedupe it b/64108432
homeIntent.setComponent(launcher).setPackage(null);
}
private void openRecents() {
Intent intent = addToIntent(new Intent(homeIntent));
mContext.startActivity(intent);
initWhenReady();
}
@UiThread
private void addFinishCommand(int requestId, int id, Runnable action) {
if (requestId < mCurrentCommandId) {
action.run();
} else {
mCurrentCommandFinishRunnables.put(id, action);
}
}
@UiThread
private void clearFinishCommand(int requestId, int id) {
if (requestId == mCurrentCommandId) {
mCurrentCommandFinishRunnables.remove(id);
}
}
@UiThread
private void initSwipeHandler(ActivityControlHelper helper, long time,
Consumer<WindowTransformSwipeHandler> onAnimationInitCallback) {
final int commandId = mCurrentCommandId;
RunningTaskInfo taskInfo = ActivityManagerWrapper.getInstance().getRunningTask();
final WindowTransformSwipeHandler handler =
new WindowTransformSwipeHandler(taskInfo, mContext, time, helper);
// Preload the plan
mRecentsModel.loadTasks(taskInfo.id, null);
mWindowTransformSwipeHandler = handler;
mTempTaskTargetRect.setEmpty();
handler.setGestureEndCallback(() -> {
if (mWindowTransformSwipeHandler == handler) {
mWindowTransformSwipeHandler = null;
mTempTaskTargetRect.setEmpty();
}
clearFinishCommand(commandId, RID_RESET_SWIPE_HANDLER);
clearFinishCommand(commandId, RID_CANCEL_CONTROLLER);
});
handler.initWhenReady();
addFinishCommand(commandId, RID_RESET_SWIPE_HANDLER, handler::reset);
TraceHelper.beginSection(TAG);
Runnable startActivity = () -> helper.startRecents(mContext, homeIntent,
new AssistDataReceiver() {
@Override
public void onHandleAssistData(Bundle bundle) {
mRecentsModel.preloadAssistData(taskInfo.id, bundle);
}
},
new RecentsAnimationListener() {
public void onAnimationStart(
RecentsAnimationControllerCompat controller,
RemoteAnimationTargetCompat[] apps, Rect homeContentInsets,
Rect minimizedHomeBounds) {
if (mWindowTransformSwipeHandler == handler) {
TraceHelper.partitionSection(TAG, "Received");
handler.onRecentsAnimationStart(controller, apps, homeContentInsets,
minimizedHomeBounds);
mTempTaskTargetRect.set(handler.getTargetRect(mWindowSize));
mMainThreadExecutor.execute(() -> {
addFinishCommand(commandId,
RID_CANCEL_CONTROLLER, () -> controller.finish(true));
if (commandId == mCurrentCommandId) {
onAnimationInitCallback.accept(handler);
}
});
} else {
TraceHelper.endSection(TAG, "Finishing no handler");
controller.finish(false /* toHome */);
}
}
public void onAnimationCanceled() {
TraceHelper.endSection(TAG, "Cancelled: " + handler);
if (mWindowTransformSwipeHandler == handler) {
handler.onRecentsAnimationCanceled();
}
}
});
// We should almost always get touch-town on background thread. This is an edge case
// when the background Choreographer has not yet initialized.
BackgroundExecutor.get().submit(startActivity);
}
@UiThread
private void startZoomOutAnim(final WindowTransformSwipeHandler handler) {
final int commandId = mCurrentCommandId;
ValueAnimator anim = ValueAnimator.ofInt(0, -handler.getTransitionLength());
anim.addUpdateListener((a) -> handler.updateDisplacement((Integer) a.getAnimatedValue()));
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
handler.onGestureEnded(0);
clearFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION);
}
});
handler.onGestureStarted();
anim.setDuration(RECENTS_LAUNCH_DURATION);
anim.setInterpolator(Interpolators.AGGRESSIVE_EASE);
anim.start();
addFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION, anim::cancel);
}
public void onOverviewToggle() {
long time = SystemClock.elapsedRealtime();
mMainThreadExecutor.execute(() -> {
long elapsedTime = time - mLastToggleTime;
mLastToggleTime = time;
mCurrentCommandId++;
mTempTaskTargetRect.round(mTaskTargetRect);
boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
int runnableCount = mCurrentCommandFinishRunnables.size();
if (runnableCount > 0) {
for (int i = 0; i < runnableCount; i++) {
mCurrentCommandFinishRunnables.valueAt(i).run();
}
mCurrentCommandFinishRunnables.clear();
isQuickTap = true;
}
ActivityControlHelper helper = getActivityControlHelper();
RecentsView recents = helper.getVisibleRecentsView();
if (recents != null) {
int childCount = recents.getChildCount();
if (childCount != 0) {
((TaskView) recents.getChildAt(childCount >= 2 ? 1 : 0)).launchTask(true);
}
// There are not enough tasks. Skip
return;
}
if (isQuickTap) {
// Focus last task. Start is on background thread so that all ActivityManager calls
// are serialized
BackgroundExecutor.get().submit(this::startLastTask);
return;
}
if (helper.switchToRecentsIfVisible()) {
return;
}
initSwipeHandler(helper, time, this::startZoomOutAnim);
});
}
public void onOverviewShown() {
getLauncher().runOnUiThread(() -> {
if (isOverviewAlmostVisible()) {
final RecentsView rv = getLauncher().getOverviewPanel();
rv.snapToTaskAfterNext();
} else {
openRecents();
}
}
);
}
public void onOverviewHidden() {
getLauncher().runOnUiThread(() -> {
if (isOverviewAlmostVisible()) {
final RecentsView rv = getLauncher().getOverviewPanel();
rv.launchNextTask();
}
}
);
}
@WorkerThread
private void startLastTask() {
// TODO: This should go through recents model.
List<RecentTaskInfo> tasks = mAM.getRecentTasks(2, UserHandle.myUserId());
if (tasks.size() > 1) {
RecentTaskInfo rti = tasks.get(1);
final ActivityOptions options;
if (!mTaskTargetRect.isEmpty()) {
final Rect targetRect = new Rect(mTaskTargetRect);
targetRect.offset(Utilities.isRtl(mContext.getResources())
? - mTaskTargetRect.width() : mTaskTargetRect.width(), 0);
final AppTransitionAnimationSpecCompat specCompat =
new AppTransitionAnimationSpecCompat(rti.id, null, targetRect);
AppTransitionAnimationSpecsFuture specFuture =
new AppTransitionAnimationSpecsFuture(mMainThreadExecutor.getHandler()) {
@Override
public List<AppTransitionAnimationSpecCompat> composeSpecs() {
return Collections.singletonList(specCompat);
}
};
options = RecentsTransition.createAspectScaleAnimation(mContext,
mMainThreadExecutor.getHandler(), true /* scaleUp */,
specFuture, () -> {});
} else {
options = ActivityOptions.makeBasic();
}
mAM.startActivityFromRecents(rti.id, options);
}
}
private boolean isOverviewAlmostVisible() {
if (clearReference()) {
return true;
}
if (!mAM.getRunningTask().topActivity.equals(launcher)) {
return false;
}
Launcher launcher = getLauncher();
return launcher != null && launcher.isStarted() && launcher.isInState(OVERVIEW);
}
private Launcher getLauncher() {
return (Launcher) LauncherAppState.getInstance(mContext).getModel().getCallback();
}
@Override
protected boolean init(Launcher launcher, boolean alreadyOnHome) {
AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
launcher.getStateManager().goToState(OVERVIEW, alreadyOnHome);
clearReference();
return false;
}
public boolean isUsingFallbackActivity() {
return DEBUG_START_FALLBACK_ACTIVITY;
}
public ActivityControlHelper getActivityControlHelper() {
if (DEBUG_START_FALLBACK_ACTIVITY) {
return new FallbackActivityControllerHelper();
} else {
return new LauncherActivityControllerHelper();
}
}
}
@@ -15,18 +15,28 @@
*/
package com.android.quickstep;
import static com.android.launcher3.Utilities.getPrefs;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_HIDE_BACK_BUTTON;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.support.annotation.WorkerThread;
import android.util.Log;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.util.UiThreadHelper;
import com.android.systemui.shared.recents.ISystemUiProxy;
import java.util.concurrent.ExecutionException;
/**
* Sets overview interaction flags, such as:
*
@@ -37,55 +47,109 @@ import com.android.systemui.shared.recents.ISystemUiProxy;
*
* @see com.android.systemui.shared.system.NavigationBarCompat.InteractionType and associated flags.
*/
public class OverviewInteractionState {
public class OverviewInteractionState implements OnSharedPreferenceChangeListener {
private static final String TAG = "OverviewFlags";
private static final Handler sUiHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
updateOverviewInteractionFlag((Context) msg.obj, msg.what, msg.arg1 == 1);
}
};
private static final Handler sBackgroundHandler = new Handler(
UiThreadHelper.getBackgroundLooper()) {
@Override
public void handleMessage(Message msg) {
ISystemUiProxy systemUiProxy = (ISystemUiProxy) msg.obj;
int flags = msg.what;
try {
systemUiProxy.setInteractionState(flags);
} catch (RemoteException e) {
Log.w(TAG, "Unable to update overview interaction flags", e);
// We do not need any synchronization for this variable as its only written on UI thread.
private static OverviewInteractionState INSTANCE;
public static OverviewInteractionState getInstance(final Context context) {
if (INSTANCE == null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
INSTANCE = new OverviewInteractionState(context.getApplicationContext());
} else {
try {
return new MainThreadExecutor().submit(
() -> OverviewInteractionState.getInstance(context)).get();
} catch (InterruptedException|ExecutionException e) {
throw new RuntimeException(e);
}
}
}
};
private static int sFlags;
public static void setBackButtonVisible(Context context, boolean visible) {
updateFlagOnUi(context, FLAG_HIDE_BACK_BUTTON, !visible);
return INSTANCE;
}
private static void updateFlagOnUi(Context context, int flag, boolean enabled) {
sUiHandler.removeMessages(flag);
sUiHandler.sendMessage(sUiHandler.obtainMessage(flag, enabled ? 1 : 0, 0, context));
public static final String KEY_SWIPE_UP_ENABLED = "pref_enable_quickstep";
private static final int MSG_SET_PROXY = 200;
private static final int MSG_SET_BACK_BUTTON_VISIBLE = 201;
private static final int MSG_SET_SWIPE_UP_ENABLED = 202;
private final Handler mUiHandler;
private final Handler mBgHandler;
// These are updated on the background thread
private ISystemUiProxy mISystemUiProxy;
private boolean mBackButtonVisible = true;
private boolean mSwipeUpEnabled = true;
private OverviewInteractionState(Context context) {
mUiHandler = new Handler(this::handleUiMessage);
mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
SharedPreferences prefs = getPrefs(context);
prefs.registerOnSharedPreferenceChangeListener(this);
onSharedPreferenceChanged(prefs, KEY_SWIPE_UP_ENABLED);
}
private static void updateOverviewInteractionFlag(Context context, int flag, boolean enabled) {
if (enabled) {
sFlags |= flag;
} else {
sFlags &= ~flag;
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String s) {
if (KEY_SWIPE_UP_ENABLED.equals(s)) {
mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
boolean swipeUpEnabled = prefs.getBoolean(s, true);
mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED,
swipeUpEnabled ? 1 : 0, 0).sendToTarget();
}
}
ISystemUiProxy systemUiProxy = RecentsModel.getInstance(context).getSystemUiProxy();
if (systemUiProxy == null) {
Log.w(TAG, "Unable to update overview interaction flags; not bound to service");
public void setBackButtonVisible(boolean visible) {
mUiHandler.removeMessages(MSG_SET_BACK_BUTTON_VISIBLE);
mUiHandler.obtainMessage(MSG_SET_BACK_BUTTON_VISIBLE, visible ? 1 : 0, 0)
.sendToTarget();
}
public void setSystemUiProxy(ISystemUiProxy proxy) {
mBgHandler.obtainMessage(MSG_SET_PROXY, proxy).sendToTarget();
}
private boolean handleUiMessage(Message msg) {
mBgHandler.obtainMessage(msg.what, msg.arg1, msg.arg2).sendToTarget();
return true;
}
private boolean handleBgMessage(Message msg) {
switch (msg.what) {
case MSG_SET_PROXY:
mISystemUiProxy = (ISystemUiProxy) msg.obj;
break;
case MSG_SET_BACK_BUTTON_VISIBLE:
mBackButtonVisible = msg.arg1 != 0;
break;
case MSG_SET_SWIPE_UP_ENABLED:
mSwipeUpEnabled = msg.arg1 != 0;
break;
}
applyFlags();
return true;
}
@WorkerThread
private void applyFlags() {
if (mISystemUiProxy == null) {
return;
}
// If we aren't already setting these flags, do so now on the background thread.
if (!sBackgroundHandler.hasMessages(sFlags)) {
sBackgroundHandler.sendMessage(sBackgroundHandler.obtainMessage(sFlags, systemUiProxy));
int flags;
if (mSwipeUpEnabled) {
flags = mBackButtonVisible ? 0 : FLAG_HIDE_BACK_BUTTON;
} else {
flags = FLAG_DISABLE_SWIPE_UP | FLAG_DISABLE_QUICK_SCRUB | FLAG_SHOW_OVERVIEW_BUTTON;
}
try {
mISystemUiProxy.setInteractionState(flags);
} catch (RemoteException e) {
Log.w(TAG, "Unable to update overview interaction flags", e);
}
}
}
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.os.Build;
import java.util.ArrayList;
import java.util.function.Consumer;
/**
* Utility class to keep track of a running animation.
*
* This class allows attaching end callbacks to an animation is intended to be used with
* {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case
* AnimationListeners are not properly dispatched.
*/
@TargetApi(Build.VERSION_CODES.O)
public class PendingAnimation {
private final ArrayList<Consumer<Boolean>> mEndListeners = new ArrayList<>();
public final AnimatorSet anim;
public PendingAnimation(AnimatorSet anim) {
this.anim = anim;
}
public void finish(boolean isSuccess) {
for (Consumer<Boolean> listeners : mEndListeners) {
listeners.accept(isSuccess);
}
mEndListeners.clear();
}
public void addEndListener(Consumer<Boolean> listener) {
mEndListeners.add(listener);
}
}
@@ -19,13 +19,14 @@ package com.android.quickstep;
import android.view.HapticFeedbackConstants;
import com.android.launcher3.Alarm;
import com.android.launcher3.Launcher;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.Utilities;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
/**
* Responds to quick scrub callbacks to page through and launch recent tasks.
@@ -35,8 +36,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
*/
public class QuickScrubController implements OnAlarmListener {
public static final int QUICK_SWITCH_START_DURATION = 133;
public static final int QUICK_SWITCH_SNAP_DURATION = 120;
public static final int QUICK_SCRUB_START_DURATION = 210;
private static final boolean ENABLE_AUTO_ADVANCE = true;
private static final int NUM_QUICK_SCRUB_SECTIONS = 3;
@@ -47,15 +47,16 @@ public class QuickScrubController implements OnAlarmListener {
private final Alarm mAutoAdvanceAlarm;
private final RecentsView mRecentsView;
private final Launcher mLauncher;
private final BaseActivity mActivity;
private boolean mInQuickScrub;
private int mQuickScrubSection;
private int mStartPage;
private boolean mStartedFromHome;
private boolean mHasAlarmRun;
private boolean mFinishedTransitionToQuickScrub;
public QuickScrubController(Launcher launcher, RecentsView recentsView) {
mLauncher = launcher;
public QuickScrubController(BaseActivity activity, RecentsView recentsView) {
mActivity = activity;
mRecentsView = recentsView;
if (ENABLE_AUTO_ADVANCE) {
mAutoAdvanceAlarm = new Alarm();
@@ -65,10 +66,13 @@ public class QuickScrubController implements OnAlarmListener {
public void onQuickScrubStart(boolean startingFromHome) {
mInQuickScrub = true;
mStartPage = startingFromHome ? 0 : mRecentsView.getFirstTaskIndex();
mStartedFromHome = startingFromHome;
mQuickScrubSection = 0;
mHasAlarmRun = false;
mLauncher.getUserEventDispatcher().resetActionDurationMillis();
mFinishedTransitionToQuickScrub = false;
snapToNextTaskIfAvailable();
mActivity.getUserEventDispatcher().resetActionDurationMillis();
}
public void onQuickScrubEnd() {
@@ -78,10 +82,12 @@ public class QuickScrubController implements OnAlarmListener {
}
int page = mRecentsView.getNextPage();
Runnable launchTaskRunnable = () -> {
if (page < mRecentsView.getFirstTaskIndex()) {
mRecentsView.getPageAt(page).performClick();
TaskView taskView = ((TaskView) mRecentsView.getPageAt(page));
if (taskView != null) {
taskView.launchTask(true);
} else {
((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
// Break out of quick scrub so user can interact with launcher.
mActivity.onBackPressed();
}
};
int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
@@ -93,8 +99,8 @@ public class QuickScrubController implements OnAlarmListener {
// No page move needed, just launch it
launchTaskRunnable.run();
}
mLauncher.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
mActivity.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
ContainerType.WORKSPACE : ContainerType.APP);
}
@@ -102,7 +108,9 @@ public class QuickScrubController implements OnAlarmListener {
int quickScrubSection = Math.round(progress * NUM_QUICK_SCRUB_SECTIONS);
if (quickScrubSection != mQuickScrubSection) {
int pageToGoTo = mRecentsView.getNextPage() + quickScrubSection - mQuickScrubSection;
goToPageWithHaptic(pageToGoTo);
if (mFinishedTransitionToQuickScrub) {
goToPageWithHaptic(pageToGoTo);
}
if (ENABLE_AUTO_ADVANCE) {
if (quickScrubSection == NUM_QUICK_SCRUB_SECTIONS || quickScrubSection == 0) {
mAutoAdvanceAlarm.setAlarm(mHasAlarmRun
@@ -115,39 +123,24 @@ public class QuickScrubController implements OnAlarmListener {
}
}
public void onQuickSwitch() {
for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
if (taskView.getTask().key.id != mRecentsView.getRunningTaskId()) {
Runnable launchTaskRunnable = () -> taskView.launchTask(true);
if (mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION)) {
// Snap to the new page then launch it
mRecentsView.setNextPageSwitchRunnable(launchTaskRunnable);
} else {
// No need to move page, just launch task directly
launchTaskRunnable.run();
}
break;
}
}
mLauncher.getUserEventDispatcher().logActionOnControl(Touch.FLING,
ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
ContainerType.WORKSPACE : ContainerType.APP);
public void onFinishedTransitionToQuickScrub() {
mFinishedTransitionToQuickScrub = true;
}
public void snapToPageForCurrentQuickScrubSection() {
if (mInQuickScrub) {
goToPageWithHaptic(mRecentsView.getFirstTaskIndex() + mQuickScrubSection);
public void snapToNextTaskIfAvailable() {
if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
int toPage = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1;
mRecentsView.snapToPage(toPage, QUICK_SCRUB_START_DURATION);
}
}
private void goToPageWithHaptic(int pageToGoTo) {
pageToGoTo = Utilities.boundToRange(pageToGoTo, mStartPage, mRecentsView.getPageCount() - 1);
pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getPageCount() - 1);
if (pageToGoTo != mRecentsView.getNextPage()) {
int duration = Math.abs(pageToGoTo - mRecentsView.getNextPage())
* QUICKSCRUB_SNAP_DURATION_PER_PAGE;
* QUICKSCRUB_SNAP_DURATION_PER_PAGE;
mRecentsView.snapToPage(pageToGoTo, duration);
mRecentsView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
}
}
@@ -158,7 +151,7 @@ public class QuickScrubController implements OnAlarmListener {
if (mQuickScrubSection == NUM_QUICK_SCRUB_SECTIONS
&& currPage < mRecentsView.getPageCount() - 1) {
goToPageWithHaptic(currPage + 1);
} else if (mQuickScrubSection == 0 && currPage > mStartPage) {
} else if (mQuickScrubSection == 0 && currPage > 0) {
goToPageWithHaptic(currPage - 1);
}
mHasAlarmRun = true;
@@ -15,34 +15,88 @@
*/
package com.android.quickstep;
import android.app.ListActivity;
import android.app.ActivityOptions;
import android.os.Bundle;
import android.os.UserHandle;
import android.support.annotation.Nullable;
import android.widget.ArrayAdapter;
import android.view.View;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
import com.android.systemui.shared.recents.model.Task;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.views.BaseDragLayer;
/**
* A simple activity to show the recently launched tasks
*/
public class RecentsActivity extends ListActivity {
public class RecentsActivity extends BaseDraggingActivity {
private ArrayAdapter<Task> mAdapter;
private RecentsRootView mRecentsRootView;
private FallbackRecentsView mFallbackRecentsView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this);
plan.preloadPlan(new PreloadOptions(), new RecentsTaskLoader(this, 1, 1, 0), -1,
UserHandle.myUserId());
// In case we are reusing IDP, create a copy so that we dont conflict with Launcher
// activity.
LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
setDeviceProfile(appState != null
? appState.getInvariantDeviceProfile().getDeviceProfile(this).copy(this)
: new InvariantDeviceProfile(this).getDeviceProfile(this));
mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
mAdapter.addAll(plan.getTaskStack().getTasks());
setListAdapter(mAdapter);
setContentView(R.layout.fallback_recents_activity);
mRecentsRootView = findViewById(R.id.drag_layer);
mFallbackRecentsView = findViewById(R.id.overview_panel);
RecentsActivityTracker.onRecentsActivityCreate(this);
}
@Override
public BaseDragLayer getDragLayer() {
return mRecentsRootView;
}
@Override
public View getRootView() {
return mRecentsRootView;
}
@Override
public <T extends View> T getOverviewPanel() {
return (T) mFallbackRecentsView;
}
@Override
public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
return null;
}
@Override
public ActivityOptions getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) {
return null;
}
@Override
public void invalidateParent(ItemInfo info) { }
@Override
protected void onStart() {
super.onStart();
UiFactory.onStart(this);
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
UiFactory.onTrimMemory(this, level);
}
@Override
protected void onDestroy() {
super.onDestroy();
RecentsActivityTracker.onRecentsActivityDestroy(this);
}
}
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.annotation.TargetApi;
import android.os.Build;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
import java.lang.ref.WeakReference;
import java.util.function.BiPredicate;
/**
* Utility class to track create/destroy for RecentsActivity
*/
@TargetApi(Build.VERSION_CODES.P)
public class RecentsActivityTracker implements ActivityInitListener {
private static final Object LOCK = new Object();
private static WeakReference<RecentsActivityTracker> sTracker = new WeakReference<>(null);
private static WeakReference<RecentsActivity> sCurrentActivity = new WeakReference<>(null);
private final BiPredicate<RecentsActivity, Boolean> mOnInitListener;
public RecentsActivityTracker(BiPredicate<RecentsActivity, Boolean> onInitListener) {
mOnInitListener = onInitListener;
}
@Override
public void register() {
synchronized (LOCK) {
sTracker = new WeakReference<>(this);
}
}
@Override
public void unregister() {
synchronized (LOCK) {
if (sTracker.get() == this) {
sTracker.clear();
}
}
}
public static void onRecentsActivityCreate(RecentsActivity activity) {
synchronized (LOCK) {
RecentsActivityTracker tracker = sTracker.get();
if (tracker != null && tracker.mOnInitListener.test(activity, false)) {
sTracker.clear();
}
sCurrentActivity = new WeakReference<>(activity);
}
}
public static void onRecentsActivityDestroy(RecentsActivity activity) {
synchronized (LOCK) {
if (sCurrentActivity.get() == activity) {
sCurrentActivity.clear();
}
}
}
public static RecentsActivity getCurrentActivity() {
synchronized (LOCK) {
return sCurrentActivity.get();
}
}
}
@@ -16,6 +16,8 @@
package com.android.quickstep;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -28,6 +30,7 @@ import android.os.UserHandle;
import android.support.annotation.WorkerThread;
import android.util.LruCache;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityManager;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
@@ -51,7 +54,6 @@ import java.util.function.Consumer;
*/
@TargetApi(Build.VERSION_CODES.O)
public class RecentsModel extends TaskStackChangeListener {
// We do not need any synchronization for this variable as its only written on UI thread.
private static RecentsModel INSTANCE;
@@ -83,10 +85,17 @@ public class RecentsModel extends TaskStackChangeListener {
private int mTaskChangeId;
private ISystemUiProxy mSystemUiProxy;
private boolean mClearAssistCacheOnStackChange = true;
private final boolean mPreloadTasksInBackground;
private final AccessibilityManager mAccessibilityManager;
private RecentsModel(Context context) {
mContext = context;
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mPreloadTasksInBackground = !activityManager.isLowRamDevice();
mMainThreadExecutor = new MainThreadExecutor();
Resources res = context.getResources();
mRecentsTaskLoader = new RecentsTaskLoader(mContext,
res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize),
@@ -100,12 +109,11 @@ public class RecentsModel extends TaskStackChangeListener {
}
};
mRecentsTaskLoader.startLoader(mContext);
mMainThreadExecutor = new MainThreadExecutor();
ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
mTaskChangeId = 1;
loadTasks(-1, null);
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
}
public RecentsTaskLoader getRecentsTaskLoader() {
@@ -135,7 +143,7 @@ public class RecentsModel extends TaskStackChangeListener {
// Preload the plan
RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(mContext);
PreloadOptions opts = new PreloadOptions();
opts.loadTitles = false;
opts.loadTitles = mAccessibilityManager.isEnabled();
loadPlan.preloadPlan(opts, mRecentsTaskLoader, taskId, UserHandle.myUserId());
// Set the load plan on UI thread
mMainThreadExecutor.execute(() -> {
@@ -162,6 +170,31 @@ public class RecentsModel extends TaskStackChangeListener {
}
}
@Override
public void onTaskStackChangedBackground() {
int userId = UserHandle.myUserId();
if (!mPreloadTasksInBackground || !checkCurrentUserId(userId, false /* debug */)) {
// TODO: Only register this for the current user
return;
}
// Preload a fixed number of task icons/thumbnails in the background
ActivityManager.RunningTaskInfo runningTaskInfo =
ActivityManagerWrapper.getInstance().getRunningTask();
RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
launchOpts.numVisibleTasks = 2;
launchOpts.numVisibleTaskThumbnails = 2;
launchOpts.onlyLoadForCache = true;
launchOpts.onlyLoadPausedActivities = true;
launchOpts.loadThumbnails = true;
PreloadOptions preloadOpts = new PreloadOptions();
preloadOpts.loadTitles = mAccessibilityManager.isEnabled();
plan.preloadPlan(preloadOpts, mRecentsTaskLoader, -1, userId);
mRecentsTaskLoader.loadTasks(plan, launchOpts);
}
public boolean isLoadPlanValid(int resultId) {
return mTaskChangeId == resultId;
}
@@ -178,6 +211,19 @@ public class RecentsModel extends TaskStackChangeListener {
return mSystemUiProxy;
}
public void onStart() {
mRecentsTaskLoader.startLoader(mContext);
mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(true);
}
public void onTrimMemory(int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// We already stop the loader in UI_HIDDEN, so stop the high res loader as well
mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(false);
}
mRecentsTaskLoader.onTrimMemory(level);
}
@WorkerThread
public void preloadAssistData(int taskId, Bundle data) {
mMainThreadExecutor.execute(() -> {
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
public class RecentsRootView extends BaseDragLayer<RecentsActivity> {
private final BaseActivity mActivity;
public RecentsRootView(Context context, AttributeSet attrs) {
super(context, attrs);
mActivity = BaseActivity.fromContext(context);
mControllers = new TouchController[0];
}
@TargetApi(23)
@Override
protected boolean fitSystemWindows(Rect insets) {
// Update device profile before notifying the children.
mActivity.getDeviceProfile().updateInsets(insets);
setInsets(insets);
return true; // I'll take it from here
}
@Override
public void setInsets(Rect insets) {
// If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by
// modifying child layout params.
if (!insets.equals(mInsets)) {
super.setInsets(insets);
}
setBackground(insets.top == 0 ? null
: Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
}
}
@@ -1,564 +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 static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.quickstep.TaskView.CURVE_FACTOR;
import static com.android.quickstep.TaskView.CURVE_INTERPOLATOR;
import android.animation.LayoutTransition;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.uioverrides.OverviewState;
import com.android.launcher3.uioverrides.RecentsViewStateController;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.ArrayList;
/**
* A list of recent tasks.
*/
public class RecentsView extends PagedView implements Insettable {
private static final Rect sTempStableInsets = new Rect();
public static final int SCROLL_TYPE_NONE = 0;
public static final int SCROLL_TYPE_TASK = 1;
public static final int SCROLL_TYPE_WORKSPACE = 2;
private final Launcher mLauncher;
private QuickScrubController mQuickScrubController;
private final ScrollState mScrollState = new ScrollState();
private boolean mOverviewStateEnabled;
private boolean mTaskStackListenerRegistered;
private LayoutTransition mLayoutTransition;
private Runnable mNextPageSwitchRunnable;
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
final TaskView taskView = (TaskView) getChildAt(i);
if (taskView.getTask().key.id == taskId) {
taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
return;
}
}
}
};
private RecentsViewStateController mStateController;
private int mFirstTaskIndex;
private final RecentsModel mModel;
private int mLoadPlanId = -1;
// Only valid until the launcher state changes to NORMAL
private int mRunningTaskId = -1;
private Bitmap mScrim;
private Paint mFadePaint;
private Shader mFadeShader;
private Matrix mFadeMatrix;
private boolean mScrimOnLeft;
private boolean mFirstTaskIconScaledDown = false;
public RecentsView(Context context) {
this(context, null);
}
public RecentsView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
enableFreeScroll(true);
setupLayoutTransition();
setClipToOutline(true);
mLauncher = Launcher.getLauncher(context);
mQuickScrubController = new QuickScrubController(mLauncher, this);
mModel = RecentsModel.getInstance(context);
mScrollState.isRtl = mIsRtl;
}
public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
final TaskView taskView = (TaskView) getChildAt(i);
if (taskView.getTask().key.id == taskId) {
taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
taskView.setAlpha(1);
return taskView;
}
}
return null;
}
private void setupLayoutTransition() {
// We want to show layout transitions when pages are deleted, to close the gap.
mLayoutTransition = new LayoutTransition();
mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
setLayoutTransition(mLayoutTransition);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mFirstTaskIndex = getPageCount();
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
updateTaskStackListenerState();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
updateTaskStackListenerState();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
updateTaskStackListenerState();
}
@Override
public void setInsets(Rect insets) {
mInsets.set(insets);
DeviceProfile dp = Launcher.getLauncher(getContext()).getDeviceProfile();
Rect padding = getPadding(dp, getContext());
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
lp.bottomMargin = padding.bottom;
setLayoutParams(lp);
setPadding(padding.left, padding.top, padding.right, 0);
if (dp.isVerticalBarLayout()) {
boolean wasScrimOnLeft = mScrimOnLeft;
mScrimOnLeft = dp.isSeascape();
if (mScrim == null || wasScrimOnLeft != mScrimOnLeft) {
Drawable scrim = getContext().getDrawable(mScrimOnLeft
? R.drawable.recents_horizontal_scrim_left
: R.drawable.recents_horizontal_scrim_right);
if (scrim instanceof BitmapDrawable) {
mScrim = ((BitmapDrawable) scrim).getBitmap();
mFadePaint = new Paint();
mFadePaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
mFadeShader = new BitmapShader(mScrim, TileMode.CLAMP, TileMode.REPEAT);
mFadeMatrix = new Matrix();
} else {
mScrim = null;
}
}
} else {
mScrim = null;
mFadePaint = null;
mFadeShader = null;
mFadeMatrix = null;
}
}
public int getFirstTaskIndex() {
return mFirstTaskIndex;
}
public boolean isTaskViewVisible(TaskView tv) {
// For now, just check if it's the active task or an adjacent task
return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
}
public TaskView getTaskView(int taskId) {
for (int i = getFirstTaskIndex(); i < getChildCount(); i++) {
TaskView tv = (TaskView) getChildAt(i);
if (tv.getTask().key.id == taskId) {
return tv;
}
}
return null;
}
public void setStateController(RecentsViewStateController stateController) {
mStateController = stateController;
}
public RecentsViewStateController getStateController() {
return mStateController;
}
public void setOverviewStateEnabled(boolean enabled) {
mOverviewStateEnabled = enabled;
updateTaskStackListenerState();
}
public void setNextPageSwitchRunnable(Runnable r) {
mNextPageSwitchRunnable = r;
}
@Override
protected void onPageEndTransition() {
super.onPageEndTransition();
if (mNextPageSwitchRunnable != null) {
mNextPageSwitchRunnable.run();
mNextPageSwitchRunnable = null;
}
}
private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
final RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
if (stack == null) {
removeAllViews();
return;
}
int oldChildCount = getChildCount();
// Ensure there are as many views as there are tasks in the stack (adding and trimming as
// necessary)
final LayoutInflater inflater = LayoutInflater.from(getContext());
final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
setLayoutTransition(null);
final int requiredChildCount = tasks.size() + mFirstTaskIndex;
for (int i = getChildCount(); i < requiredChildCount; i++) {
final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
addView(taskView);
}
while (getChildCount() > requiredChildCount) {
final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
removeView(taskView);
loader.unloadTaskData(taskView.getTask());
}
setLayoutTransition(mLayoutTransition);
// Rebind and reset all task views
for (int i = tasks.size() - 1; i >= 0; i--) {
final Task task = tasks.get(i);
final TaskView taskView = (TaskView) getChildAt(tasks.size() - i - 1 + mFirstTaskIndex);
taskView.bind(task);
taskView.resetVisualProperties();
loader.loadTaskData(task);
}
updateCurveProperties();
applyIconScale(false /* animate */);
if (oldChildCount != getChildCount()) {
mQuickScrubController.snapToPageForCurrentQuickScrubSection();
}
}
private void updateTaskStackListenerState() {
boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
&& getWindowVisibility() == VISIBLE;
if (registerStackListener != mTaskStackListenerRegistered) {
if (registerStackListener) {
ActivityManagerWrapper.getInstance()
.registerTaskStackListener(mTaskStackListener);
reloadIfNeeded();
} else {
ActivityManagerWrapper.getInstance()
.unregisterTaskStackListener(mTaskStackListener);
}
mTaskStackListenerRegistered = registerStackListener;
}
}
private static Rect getPadding(DeviceProfile profile, Context context) {
WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
Rect padding = new Rect(profile.workspacePadding);
float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
float overviewHeight, overviewWidth;
if (profile.isVerticalBarLayout()) {
// Use the same padding on both sides for symmetry.
float availableWidth = taskWidth - 2 * Math.max(padding.left, padding.right);
float availableHeight = profile.availableHeightPx - padding.top
- sTempStableInsets.top - Math.max(padding.bottom,
profile.heightPx * (1 - OverviewState.getVerticalProgress(profile, context)));
float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
overviewHeight = taskHeight * scaledRatio;
overviewWidth = taskWidth * scaledRatio;
} else {
overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
- sTempStableInsets.top;
overviewWidth = taskWidth * overviewHeight / taskHeight;
}
padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
- Math.round(overviewHeight);
padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
return padding;
}
/**
* Sets the {@param outRect} to match the position of the first tile such that it is scaled
* down to match the 2nd taskView.
* @return returns the factor which determines the scaling factor for the second task.
*/
public static float getScaledDownPageRect(DeviceProfile dp, Context context, Rect outRect) {
getPageRect(dp, context, outRect);
int pageSpacing = context.getResources()
.getDimensionPixelSize(R.dimen.recents_page_spacing);
float halfScreenWidth = dp.widthPx * 0.5f;
float halfPageWidth = outRect.width() * 0.5f;
float pageCenter = outRect.right + pageSpacing + halfPageWidth;
float distanceFromCenter = Math.abs(halfScreenWidth - pageCenter);
float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
float linearInterpolation = Math.min(1, distanceFromCenter / distanceToReachEdge);
float scale = 1 - CURVE_INTERPOLATOR.getInterpolation(linearInterpolation) * CURVE_FACTOR;
int topMargin = context.getResources()
.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
outRect.top -= topMargin;
Utilities.scaleRectAboutCenter(outRect, scale);
outRect.top += (int) (scale * topMargin);
return linearInterpolation;
}
public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
Rect targetPadding = getPadding(grid, context);
Rect insets = grid.getInsets();
outRect.set(
targetPadding.left + insets.left,
targetPadding.top + insets.top,
grid.widthPx - targetPadding.right - insets.right,
grid.heightPx - targetPadding.bottom - insets.bottom);
outRect.top += context.getResources()
.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
}
@Override
public void computeScroll() {
super.computeScroll();
updateCurveProperties();
}
/**
* Scales and adjusts translation of adjacent pages as if on a curved carousel.
*/
public void updateCurveProperties() {
if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
return;
}
final int halfPageWidth = mScrollState.halfPageWidth = getNormalChildWidth() / 2;
mScrollState.lastScrollType = SCROLL_TYPE_NONE;
final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
final int halfScreenWidth = getMeasuredWidth() / 2;
final int pageSpacing = mPageSpacing;
final int pageCount = getPageCount();
for (int i = 0; i < pageCount; i++) {
View page = getPageAt(i);
int pageCenter = page.getLeft() + halfPageWidth;
mScrollState.distanceFromScreenCenter = screenCenter - pageCenter;
float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
mScrollState.linearInterpolation = Math.min(1,
Math.abs(mScrollState.distanceFromScreenCenter) / distanceToReachEdge);
mScrollState.lastScrollType = ((PageCallbacks) page).onPageScroll(mScrollState);
}
}
public void onTaskDismissed(TaskView taskView) {
ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
removeView(taskView);
if (getTaskCount() == 0) {
mLauncher.getStateManager().goToState(NORMAL);
}
}
public void reset() {
mRunningTaskId = -1;
setCurrentPage(0);
}
public int getTaskCount() {
return getChildCount() - mFirstTaskIndex;
}
public int getRunningTaskId() {
return mRunningTaskId;
}
/**
* Reloads the view if anything in recents changed.
*/
public void reloadIfNeeded() {
if (!mModel.isLoadPlanValid(mLoadPlanId)) {
mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
}
}
/**
* Ensures that the first task in the view represents {@param task} and reloads the view
* if needed. This allows the swipe-up gesture to assume that the first tile always
* corresponds to the correct task.
* All subsequent calls to reload will keep the task as the first item until {@link #reset()}
* is called.
* Also scrolls the view to this task
*/
public void showTask(int runningTaskId) {
boolean needsReload = false;
if (getTaskCount() == 0) {
needsReload = true;
// Add an empty view for now
setLayoutTransition(null);
final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
.inflate(R.layout.task, this, false);
addView(taskView, mFirstTaskIndex);
setLayoutTransition(mLayoutTransition);
}
if (!needsReload) {
needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
}
if (needsReload) {
mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
}
mRunningTaskId = runningTaskId;
setCurrentPage(mFirstTaskIndex);
if (mCurrentPage >= mFirstTaskIndex) {
getPageAt(mCurrentPage).setAlpha(0);
}
}
public QuickScrubController getQuickScrubController() {
return mQuickScrubController;
}
public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) {
if (mFirstTaskIconScaledDown == isScaledDown) {
return;
}
mFirstTaskIconScaledDown = isScaledDown;
applyIconScale(animate);
}
private void applyIconScale(boolean animate) {
float scale = mFirstTaskIconScaledDown ? 0 : 1;
TaskView firstTask = (TaskView) getChildAt(mFirstTaskIndex);
if (firstTask != null) {
if (animate) {
firstTask.animateIconToScale(scale);
} else {
firstTask.setIconScale(scale);
}
}
}
@Override
public void draw(Canvas canvas) {
if (mScrim == null) {
super.draw(canvas);
return;
}
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
int length = mScrim.getWidth();
int height = getHeight();
int saveCount = canvas.getSaveCount();
int scrimLeft;
if (mScrimOnLeft) {
scrimLeft = getScrollX();
} else {
scrimLeft = getScrollX() + getWidth() - length;
}
canvas.saveLayer(scrimLeft, 0, scrimLeft + length, height, null, flags);
super.draw(canvas);
mFadeMatrix.setTranslate(scrimLeft, 0);
mFadeShader.setLocalMatrix(mFadeMatrix);
mFadePaint.setShader(mFadeShader);
canvas.drawRect(scrimLeft, 0, scrimLeft + length, height, mFadePaint);
canvas.restoreToCount(saveCount);
}
public interface PageCallbacks {
/**
* Updates the page UI based on scroll params and returns the type of scroll
* effect performed.
*
* @see #SCROLL_TYPE_NONE
* @see #SCROLL_TYPE_TASK
* @see #SCROLL_TYPE_WORKSPACE
*/
int onPageScroll(ScrollState scrollState);
}
public static class ScrollState {
public boolean isRtl;
public int lastScrollType;
public int halfPageWidth;
public float distanceFromScreenCenter;
public float linearInterpolation;
public float prevPageExtraWidth;
}
}
@@ -1,52 +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.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowManager;
/**
* A simple view which keeps its size proportional to the display size
*/
public class SimpleTaskView extends View {
private static final Point sTempPoint = new Point();
public SimpleTaskView(Context context) {
super(context);
}
public SimpleTaskView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SimpleTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
getContext().getSystemService(WindowManager.class)
.getDefaultDisplay().getRealSize(sTempPoint);
int width = (int) ((float) height * sTempPoint.x / sTempPoint.y);
setMeasuredDimension(width, height);
}
}
@@ -1,92 +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.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.systemui.shared.recents.view.AnimateableViewBounds;
/**
* Floating view which shows the task snapshot allowing it to be dragged and placed.
*/
public class SnapshotDragView extends AbstractFloatingView implements Insettable {
private final Launcher mLauncher;
private final Bitmap mSnapshot;
private final AnimateableViewBounds mViewBounds;
public SnapshotDragView(Launcher launcher, Bitmap snapshot) {
super(launcher, null);
mLauncher = launcher;
mSnapshot = snapshot;
mViewBounds = new AnimateableViewBounds(this, 0);
setWillNotDraw(false);
setClipToOutline(true);
setOutlineProvider(mViewBounds);
}
AnimateableViewBounds getViewBounds() {
return mViewBounds;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mSnapshot != null) {
setMeasuredDimension(mSnapshot.getWidth(), mSnapshot.getHeight());
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
public void setInsets(Rect insets) {
}
@Override
protected void onDraw(Canvas canvas) {
if (mSnapshot != null) {
canvas.drawBitmap(mSnapshot, 0, 0, null);
}
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
return false;
}
@Override
protected void handleClose(boolean animate) {
// We dont suupport animate.
mLauncher.getDragLayer().removeView(this);
}
@Override
public void logActionCommand(int command) {
// We should probably log the weather
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_QUICKSTEP_PREVIEW) != 0;
}
}
@@ -16,27 +16,41 @@
package com.android.quickstep;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver.OnPreDrawListener;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.InstantAppResolver;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/**
@@ -58,11 +72,12 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
}
@Override
public View.OnClickListener getOnClickListener(Launcher launcher, ItemInfo itemInfo) {
public View.OnClickListener getOnClickListener(
BaseDraggingActivity activity, ItemInfo itemInfo) {
return null;
}
public View.OnClickListener getOnClickListener(final Launcher launcher, final TaskView view) {
public View.OnClickListener getOnClickListener(BaseDraggingActivity activity, TaskView view) {
Task task = view.getTask();
ShortcutInfo dummyInfo = new ShortcutInfo();
@@ -70,14 +85,14 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
ComponentName component = task.getTopComponent();
dummyInfo.intent.setComponent(component);
dummyInfo.user = UserHandle.of(task.key.userId);
dummyInfo.title = TaskUtils.getTitle(launcher, task);
dummyInfo.title = TaskUtils.getTitle(activity, task);
return getOnClickListenerForTask(launcher, task, dummyInfo);
return getOnClickListenerForTask(activity, task, dummyInfo);
}
protected View.OnClickListener getOnClickListenerForTask(final Launcher launcher,
final Task task, final ItemInfo dummyInfo) {
return mSystemShortcut.getOnClickListener(launcher, dummyInfo);
protected View.OnClickListener getOnClickListenerForTask(
BaseDraggingActivity activity, Task task, ItemInfo dummyInfo) {
return mSystemShortcut.getOnClickListener(activity, dummyInfo);
}
public static class AppInfo extends TaskSystemShortcut<SystemShortcut.AppInfo> {
@@ -86,9 +101,13 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
}
}
public static class SplitScreen extends TaskSystemShortcut {
public static class SplitScreen extends TaskSystemShortcut implements OnPreDrawListener,
DeviceProfile.OnDeviceProfileChangeListener, View.OnLayoutChangeListener {
private Handler mHandler;
private RecentsView mRecentsView;
private TaskView mTaskView;
private BaseDraggingActivity mActivity;
public SplitScreen() {
super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
@@ -96,30 +115,95 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
}
@Override
public View.OnClickListener getOnClickListener(Launcher launcher, TaskView taskView) {
if (launcher.getDeviceProfile().isMultiWindowMode) {
public View.OnClickListener getOnClickListener(
BaseDraggingActivity activity, TaskView taskView) {
if (activity.getDeviceProfile().isMultiWindowMode) {
return null;
}
final Task task = taskView.getTask();
final int taskId = task.key.id;
if (!task.isDockable) {
return null;
}
mActivity = activity;
mRecentsView = activity.getOverviewPanel();
mTaskView = taskView;
final TaskThumbnailView thumbnailView = taskView.getThumbnail();
return (v -> {
final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions(true);
final Consumer<Boolean> resultCallback = success -> {
if (success) {
launcher.<RecentsView>getOverviewPanel().removeView(taskView);
}
};
ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(
task.key, options, resultCallback, mHandler);
AbstractFloatingView.closeOpenViews(activity, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
// TODO improve transition; see:
// DockedFirstAnimationFrameEvent
// RecentsTransitionHelper#composeDockAnimationSpec
// WindowManagerWrapper#overridePendingAppTransitionMultiThumbFuture
if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
ActivityOptionsCompat.makeSplitScreenOptions(true))) {
ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
try {
sysUiProxy.onSplitScreenInvoked();
} catch (RemoteException e) {
Log.w(TAG, "Failed to notify SysUI of split screen: ", e);
return;
}
// Add a device profile change listener to kick off animating the side tasks
// once we enter multiwindow mode and relayout
activity.addOnDeviceProfileChangeListener(this);
final Runnable animStartedListener = () -> {
// Hide the task view and wait for the window to be resized
// TODO: Consider animating in launcher and do an in-place start activity
// afterwards
mRecentsView.addIgnoreResetTask(mTaskView);
mTaskView.setAlpha(0f);
mTaskView.getViewTreeObserver().addOnPreDrawListener(SplitScreen.this);
};
final int[] position = new int[2];
thumbnailView.getLocationOnScreen(position);
final int width = (int) (thumbnailView.getWidth() * taskView.getScaleX());
final int height = (int) (thumbnailView.getHeight() * taskView.getScaleY());
final Rect taskBounds = new Rect(position[0], position[1],
position[0] + width, position[1] + height);
Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
taskBounds.width(), taskBounds.height(), thumbnailView, 1f,
Color.BLACK);
AppTransitionAnimationSpecsFuture future =
new AppTransitionAnimationSpecsFuture(mHandler) {
@Override
public List<AppTransitionAnimationSpecCompat> composeSpecs() {
return Collections.singletonList(new AppTransitionAnimationSpecCompat(
taskId, thumbnail, taskBounds));
}
};
WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
future, animStartedListener, mHandler, true /* scaleUp */);
}
});
}
@Override
public boolean onPreDraw() {
mTaskView.getViewTreeObserver().removeOnPreDrawListener(this);
WindowManagerWrapper.getInstance().endProlongedAnimations();
return true;
}
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
mActivity.removeOnDeviceProfileChangeListener(this);
if (dp.isMultiWindowMode) {
mTaskView.getRootView().addOnLayoutChangeListener(this);
}
}
@Override
public void onLayoutChange(View v, int l, int t, int r, int b,
int oldL, int oldT, int oldR, int oldB) {
mTaskView.getRootView().removeOnLayoutChangeListener(this);
mRecentsView.removeIgnoreResetTask(mTaskView);
// Start animating in the side pages once launcher has been resized
mRecentsView.dismissTask(mTaskView, false, false);
}
}
public static class Pin extends TaskSystemShortcut {
@@ -132,12 +216,17 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
}
@Override
public View.OnClickListener getOnClickListener(Launcher launcher, TaskView taskView) {
ISystemUiProxy sysUiProxy = RecentsModel.getInstance(launcher).getSystemUiProxy();
public View.OnClickListener getOnClickListener(
BaseDraggingActivity activity, TaskView taskView) {
ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
if (sysUiProxy == null) {
return null;
}
if (!ActivityManagerWrapper.getInstance().isLockToAppEnabled()) {
if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
return null;
}
if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
// We shouldn't be able to pin while an app is locked.
return null;
}
return view -> {
@@ -161,11 +250,11 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
}
@Override
protected View.OnClickListener getOnClickListenerForTask(Launcher launcher, Task task,
ItemInfo itemInfo) {
if (InstantAppResolver.newInstance(launcher).isInstantApp(launcher,
protected View.OnClickListener getOnClickListenerForTask(
BaseDraggingActivity activity, Task task, ItemInfo itemInfo) {
if (InstantAppResolver.newInstance(activity).isInstantApp(activity,
task.getTopComponent().getPackageName())) {
return mSystemShortcut.createOnClickListener(launcher, itemInfo);
return mSystemShortcut.createOnClickListener(activity, itemInfo);
}
return null;
}
@@ -16,12 +16,12 @@
package com.android.quickstep;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.Log;
import com.android.launcher3.Launcher;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.systemui.shared.recents.model.Task;
@@ -34,10 +34,10 @@ public class TaskUtils {
private static final String TAG = "TaskUtils";
public static CharSequence getTitle(Launcher launcher, Task task) {
LauncherAppsCompat launcherAppsCompat = LauncherAppsCompat.getInstance(launcher);
UserManagerCompat userManagerCompat = UserManagerCompat.getInstance(launcher);
PackageManager packageManager = launcher.getPackageManager();
public static CharSequence getTitle(Context context, Task task) {
LauncherAppsCompat launcherAppsCompat = LauncherAppsCompat.getInstance(context);
UserManagerCompat userManagerCompat = UserManagerCompat.getInstance(context);
PackageManager packageManager = context.getPackageManager();
UserHandle user = UserHandle.of(task.key.userId);
ApplicationInfo applicationInfo = launcherAppsCompat.getApplicationInfo(
task.getTopComponent().getPackageName(), 0, user);
@@ -21,8 +21,6 @@ import android.support.annotation.IntDef;
import android.view.Choreographer;
import android.view.MotionEvent;
import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Consumer;
@@ -31,21 +29,14 @@ import java.util.function.Consumer;
@FunctionalInterface
public interface TouchConsumer extends Consumer<MotionEvent> {
static boolean isInteractionQuick(@InteractionType int interactionType) {
return interactionType == INTERACTION_QUICK_SCRUB ||
interactionType == INTERACTION_QUICK_SWITCH;
}
@IntDef(flag = true, value = {
INTERACTION_NORMAL,
INTERACTION_QUICK_SWITCH,
INTERACTION_QUICK_SCRUB
})
@Retention(RetentionPolicy.SOURCE)
@interface InteractionType {}
int INTERACTION_NORMAL = 0;
int INTERACTION_QUICK_SWITCH = 1;
int INTERACTION_QUICK_SCRUB = 2;
int INTERACTION_QUICK_SCRUB = 1;
default void reset() { }
@@ -64,4 +55,16 @@ public interface TouchConsumer extends Consumer<MotionEvent> {
default Choreographer getIntrimChoreographer(MotionEventQueue queue) {
return null;
}
default void deferInit() { }
default boolean deferNextEventToMainThread() {
return false;
}
default boolean forceToLauncherConsumer() {
return false;
}
default void onShowOverviewFromAltTab() {}
}
@@ -21,15 +21,14 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.PointF;
import android.os.Build;
import android.os.Handler;
@@ -40,15 +39,17 @@ import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherState;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -60,6 +61,8 @@ import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
@TargetApi(Build.VERSION_CODES.O)
public class TouchInteractionService extends Service {
public static final boolean DEBUG_OPEN_OVERVIEW_VIA_ALT_TAB = false;
private static final SparseArray<String> sMotionEventNames;
static {
@@ -83,7 +86,7 @@ public class TouchInteractionService extends Service {
@Override
public void onPreMotionEvent(@HitTarget int downHitTarget) throws RemoteException {
TraceHelper.beginSection("SysUiBinder");
onBinderPreMotionEvent(downHitTarget);
setupTouchConsumer(downHitTarget);
TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget);
}
@@ -103,21 +106,12 @@ public class TouchInteractionService extends Service {
mRecentsModel.setSystemUiProxy(mISystemUiProxy);
RemoteRunnable.executeSafely(() -> mISystemUiProxy.setRecentsOnboardingText(
getResources().getString(R.string.recents_swipe_up_onboarding)));
Launcher launcher = (Launcher) LauncherAppState.getInstance(
TouchInteractionService.this).getModel().getCallback();
UiFactory.onLauncherStateOrFocusChanged(launcher);
}
@Override
public void onQuickSwitch() {
mEventQueue.onQuickSwitch();
TraceHelper.endSection("SysUiBinder", "onQuickSwitch");
mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
}
@Override
public void onQuickScrubStart() {
mEventQueue.onQuickScrubStart();
sQuickScrubEnabled = true;
TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart");
}
@@ -130,35 +124,57 @@ public class TouchInteractionService extends Service {
public void onQuickScrubEnd() {
mEventQueue.onQuickScrubEnd();
TraceHelper.endSection("SysUiBinder", "onQuickScrubEnd");
sQuickScrubEnabled = false;
}
@Override
public void onOverviewToggle() {
mOverviewCommandHelper.onOverviewToggle();
}
@Override
public void onOverviewShown(boolean triggeredFromAltTab) {
if (DEBUG_OPEN_OVERVIEW_VIA_ALT_TAB) {
if (triggeredFromAltTab) {
setupTouchConsumer(HIT_TARGET_NONE);
mEventQueue.onOverviewShownFromAltTab();
}
}
}
@Override
public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (DEBUG_OPEN_OVERVIEW_VIA_ALT_TAB) {
if (triggeredFromAltTab && !triggeredFromHomeKey) {
// onOverviewShownFromAltTab initiates quick scrub. Ending it here.
mEventQueue.onQuickScrubEnd();
}
}
}
@Override
public void onQuickStep(MotionEvent motionEvent) {
}
};
private final TouchConsumer mNoOpTouchConsumer = (ev) -> {};
private static boolean sConnected = false;
private static boolean sQuickScrubEnabled = false;
public static boolean isConnected() {
return sConnected;
}
public static boolean isQuickScrubEnabled() {
return sQuickScrubEnabled;
}
private ActivityManagerWrapper mAM;
private RunningTaskInfo mRunningTask;
private RecentsModel mRecentsModel;
private Intent mHomeIntent;
private ComponentName mLauncher;
private MotionEventQueue mEventQueue;
private MainThreadExecutor mMainThreadExecutor;
private ISystemUiProxy mISystemUiProxy;
private OverviewCommandHelper mOverviewCommandHelper;
private OverviewInteractionState mOverviewInteractionState;
private Choreographer mMainThreadChoreographer;
private Choreographer mBackgroundThreadChoreographer;
private MotionEventQueue mNoOpEventQueue;
@Override
public void onCreate() {
@@ -166,19 +182,10 @@ public class TouchInteractionService extends Service {
mAM = ActivityManagerWrapper.getInstance();
mRecentsModel = RecentsModel.getInstance(this);
mMainThreadExecutor = new MainThreadExecutor();
mHomeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setPackage(getPackageName())
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ResolveInfo info = getPackageManager().resolveActivity(mHomeIntent, 0);
mLauncher = new ComponentName(getPackageName(), info.activityInfo.name);
// Clear the packageName as system can fail to dedupe it b/64108432
mHomeIntent.setComponent(mLauncher).setPackage(null);
mOverviewCommandHelper = new OverviewCommandHelper(this);
mMainThreadChoreographer = Choreographer.getInstance();
mNoOpEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
mEventQueue = mNoOpEventQueue;
mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
mOverviewInteractionState = OverviewInteractionState.getInstance(this);
sConnected = true;
@@ -190,7 +197,6 @@ public class TouchInteractionService extends Service {
@Override
public void onDestroy() {
sConnected = false;
sQuickScrubEnabled = false;
super.onDestroy();
}
@@ -200,32 +206,47 @@ public class TouchInteractionService extends Service {
return mMyBinder;
}
private void onBinderPreMotionEvent(@HitTarget int downHitTarget) {
mRunningTask = mAM.getRunningTask();
private void setupTouchConsumer(@HitTarget int downHitTarget) {
mEventQueue.reset();
if (mRunningTask == null) {
mEventQueue = mNoOpEventQueue;
} else if (mRunningTask.topActivity.equals(mLauncher)) {
mEventQueue = getLauncherEventQueue();
} else {
TouchConsumer oldConsumer = mEventQueue.getConsumer();
if (oldConsumer.deferNextEventToMainThread()) {
mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
new OtherActivityTouchConsumer(this, mRunningTask, mRecentsModel,
mHomeIntent, mISystemUiProxy, mMainThreadExecutor,
mBackgroundThreadChoreographer, downHitTarget));
new DeferredTouchConsumer((v) -> getCurrentTouchConsumer(downHitTarget,
oldConsumer.forceToLauncherConsumer(), v)));
mEventQueue.deferInit();
} else {
mEventQueue = new MotionEventQueue(
mMainThreadChoreographer, getCurrentTouchConsumer(downHitTarget, false, null));
}
}
private MotionEventQueue getLauncherEventQueue() {
private TouchConsumer getCurrentTouchConsumer(
@HitTarget int downHitTarget, boolean forceToLauncher, VelocityTracker tracker) {
RunningTaskInfo runningTaskInfo = mAM.getRunningTask();
if (runningTaskInfo == null && !forceToLauncher) {
return mNoOpTouchConsumer;
} else if (forceToLauncher ||
runningTaskInfo.topActivity.equals(mOverviewCommandHelper.launcher)) {
return getLauncherConsumer();
} else {
if (tracker == null) {
tracker = VelocityTracker.obtain();
}
return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
mOverviewCommandHelper.homeIntent,
mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
mBackgroundThreadChoreographer, downHitTarget, tracker);
}
}
private TouchConsumer getLauncherConsumer() {
Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
if (launcher == null) {
return mNoOpEventQueue;
return mNoOpTouchConsumer;
}
View target = launcher.getDragLayer();
return new MotionEventQueue(mMainThreadChoreographer,
new LauncherTouchConsumer(launcher, target));
return new LauncherTouchConsumer(launcher, target);
}
private static class LauncherTouchConsumer implements TouchConsumer {
@@ -307,16 +328,11 @@ public class TouchInteractionService extends Service {
if (mInvalidated) {
return;
}
if (TouchConsumer.isInteractionQuick(interactionType)) {
if (interactionType == INTERACTION_QUICK_SCRUB) {
Runnable action = () -> {
Runnable onComplete = null;
if (interactionType == INTERACTION_QUICK_SCRUB) {
mQuickScrubController.onQuickScrubStart(true);
} else if (interactionType == INTERACTION_QUICK_SWITCH) {
onComplete = mQuickScrubController::onQuickSwitch;
}
mLauncher.getStateManager().goToState(OVERVIEW, true, 0,
QUICK_SWITCH_START_DURATION, onComplete);
LauncherState fromState = mLauncher.getStateManager().getState();
mLauncher.getStateManager().goToState(FAST_OVERVIEW, true);
mQuickScrubController.onQuickScrubStart(fromState == NORMAL);
};
if (mLauncher.getWorkspace().runOnOverlayHidden(action)) {
@@ -15,24 +15,20 @@
*/
package com.android.quickstep;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_START_DURATION;
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SWITCH;
import static com.android.quickstep.TouchConsumer.isInteractionQuick;
import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
import static com.android.systemui.shared.recents.utilities.Utilities
.postAtFrontOfQueueAsynchronously;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
@@ -42,21 +38,21 @@ import android.graphics.RectF;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver.OnDrawListener;
import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherState;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
@@ -65,11 +61,16 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
import com.android.quickstep.ActivityControlHelper.LayoutListener;
import com.android.quickstep.TouchConsumer.InteractionType;
import com.android.quickstep.util.SysuiEventLogger;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.RectFEvaluator;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.TransactionCompat;
@@ -78,7 +79,7 @@ import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.StringJoiner;
@TargetApi(Build.VERSION_CODES.O)
public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
private static final boolean DEBUG_STATES = false;
@@ -101,9 +102,8 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
// States for quick switch/scrub
private static final int STATE_SWITCH_TO_SCREENSHOT_COMPLETE = 1 << 10;
private static final int STATE_QUICK_SWITCH = 1 << 11;
private static final int STATE_QUICK_SCRUB_START = 1 << 12;
private static final int STATE_QUICK_SCRUB_END = 1 << 13;
private static final int STATE_QUICK_SCRUB_START = 1 << 11;
private static final int STATE_QUICK_SCRUB_END = 1 << 12;
private static final int LAUNCHER_UI_STATES =
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE
@@ -152,6 +152,8 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
// The clip rect in source app window coordinates
private final Rect mClipRect = new Rect();
private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
protected Runnable mGestureEndCallback;
protected boolean mIsGoingToHome;
private DeviceProfile mDp;
private int mTransitionDragLength;
@@ -165,12 +167,14 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
private final Context mContext;
private final int mRunningTaskId;
private final ActivityControlHelper<T> mActivityControlHelper;
private final ActivityInitListener mActivityInitListener;
private MultiStateCallback mStateCallback;
private AnimatorPlaybackController mLauncherTransitionController;
private Launcher mLauncher;
private LauncherLayoutListener mLauncherLayoutListener;
private T mActivity;
private LayoutListener mLayoutListener;
private RecentsView mRecentsView;
private QuickScrubController mQuickScrubController;
@@ -189,11 +193,21 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
private final RecentsAnimationWrapper mRecentsAnimationWrapper = new RecentsAnimationWrapper();
private Matrix mTmpMatrix = new Matrix();
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context) {
WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs,
ActivityControlHelper<T> controller) {
mContext = context;
mRunningTaskId = runningTaskInfo.id;
mInputConsumer.registerInputConsumer();
mTouchTimeMs = touchTimeMs;
mActivityControlHelper = controller;
mActivityInitListener = mActivityControlHelper
.createActivityInitListener(this::onActivityInit);
// Register the input consumer on the UI thread, to ensure that it runs after any pending
// unregister calls
mMainExecutor.execute(mInputConsumer::registerInputConsumer);
initStateCallbacks();
}
@@ -236,13 +250,10 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_QUICK_SWITCH,
this::onQuickInteractionStart);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_QUICK_SCRUB_START,
this::onQuickInteractionStart);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SWITCH_TO_SCREENSHOT_COMPLETE
| STATE_QUICK_SWITCH, this::switchToFinalAppAfterQuickSwitch);
this::onQuickScrubStart);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_QUICK_SCRUB_START
| STATE_SCALED_CONTROLLER_RECENTS, this::onFinishedTransitionToQuickScrub);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SWITCH_TO_SCREENSHOT_COMPLETE
| STATE_QUICK_SCRUB_END, this::switchToFinalAppAfterQuickScrub);
}
@@ -263,7 +274,8 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
mSourceStackBounds.height() - mSourceInsets.bottom);
Rect tempRect = new Rect();
RecentsView.getPageRect(dp, mContext, tempRect);
mTransitionDragLength = mActivityControlHelper
.getSwipeUpDestinationAndLength(dp, mContext, tempRect);
mTargetRect.set(tempRect);
mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
@@ -284,14 +296,15 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
mSourceRect.set(scaledTargetRect);
}
Rect targetInsets = dp.getInsets();
if (dp.isVerticalBarLayout()) {
int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
mTransitionDragLength = dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset;
} else {
mTransitionDragLength = dp.heightPx - tempRect.bottom;
}
public int getTransitionLength() {
return mTransitionDragLength;
}
public RectF getTargetRect(Point outWindowSize) {
outWindowSize.set(mDp.widthPx, mDp.heightPx);
return mInitialTargetRect;
}
private long getFadeInDuration() {
@@ -306,39 +319,39 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
}
}
@Override
protected boolean init(final Launcher launcher, boolean alreadyOnHome) {
if (launcher == mLauncher) {
public void initWhenReady() {
mActivityInitListener.register();
}
private boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
if (mActivity == activity) {
return true;
}
if (mLauncher != null) {
if (mActivity != null) {
// The launcher may have been recreated as a result of device rotation.
int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
initStateCallbacks();
mStateCallback.setState(oldState);
mLauncherLayoutListener.setHandler(null);
mLayoutListener.setHandler(null);
}
mWasLauncherAlreadyVisible = alreadyOnHome;
mLauncher = launcher;
mActivity = activity;
// For the duration of the gesture, lock the screen orientation to ensure that we do not
// rotate mid-quickscrub
mLauncher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
mRecentsView = mLauncher.getOverviewPanel();
mRecentsView = activity.getOverviewPanel();
mQuickScrubController = mRecentsView.getQuickScrubController();
mLauncherLayoutListener = new LauncherLayoutListener(mLauncher);
mLayoutListener = mActivityControlHelper.createLayoutListener(mActivity);
mStateCallback.setState(STATE_LAUNCHER_PRESENT);
if (alreadyOnHome) {
onLauncherStart(launcher);
onLauncherStart(activity);
} else {
launcher.setOnStartCallback(this::onLauncherStart);
activity.setOnStartCallback(this::onLauncherStart);
}
return true;
}
private void onLauncherStart(final Launcher launcher) {
if (mLauncher != launcher) {
private void onLauncherStart(final T activity) {
if (mActivity != activity) {
return;
}
if ((mStateCallback.getState() & STATE_HANDLER_INVALIDATED) != 0) {
@@ -346,31 +359,20 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
}
mStateCallback.setState(STATE_LAUNCHER_STARTED);
LauncherState startState = mLauncher.getStateManager().getState();
if (startState.disableRestore) {
startState = mLauncher.getStateManager().getRestState();
}
mLauncher.getStateManager().setRestState(startState);
mActivityControlHelper.prepareRecentsUI(mActivity, mWasLauncherAlreadyVisible);
AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
AbstractFloatingView.closeAllOpenViews(mLauncher, mWasLauncherAlreadyVisible);
if (mWasLauncherAlreadyVisible && !mLauncher.getAppTransitionManager().isAnimating()) {
DeviceProfile dp = mLauncher.getDeviceProfile();
long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
mLauncherTransitionController = mLauncher.getStateManager()
.createAnimationToNewWorkspace(OVERVIEW, accuracy);
if (mWasLauncherAlreadyVisible) {
mLauncherTransitionController = mActivityControlHelper
.createControllerForVisibleActivity(activity);
mLauncherTransitionController.dispatchOnStart();
mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_DRAWN);
} else {
TraceHelper.beginSection("WTS-init");
mLauncher.getStateManager().goToState(OVERVIEW, false);
TraceHelper.partitionSection("WTS-init", "State changed");
// TODO: Implement a better animation for fading in
View rootView = mLauncher.getRootView();
View rootView = activity.getRootView();
rootView.setAlpha(0);
rootView.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
@@ -379,21 +381,18 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
TraceHelper.endSection("WTS-init", "Launcher frame is drawn");
rootView.post(() ->
rootView.getViewTreeObserver().removeOnDrawListener(this));
if (launcher != mLauncher) {
if (activity != mActivity) {
return;
}
mStateCallback.setState(STATE_LAUNCHER_DRAWN);
}
});
// Optimization, hide the all apps view to prevent layout while initializing
mLauncher.getAppsView().setVisibility(View.GONE);
}
mRecentsView.showTask(mRunningTaskId);
mRecentsView.setFirstTaskIconScaledDown(true /* isScaledDown */, false /* animate */);
mLauncherLayoutListener.open();
mLayoutListener.open();
}
public void setLauncherOnDrawCallback(Runnable callback) {
@@ -401,7 +400,7 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
}
private void launcherFrameDrawn() {
View rootView = mLauncher.getRootView();
View rootView = mActivity.getRootView();
if (rootView.getAlpha() < 1) {
if (mGestureStarted) {
final MultiStateCallback callback = mStateCallback;
@@ -416,11 +415,19 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
if (mLauncherDrawnCallback != null) {
mLauncherDrawnCallback.run();
}
mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
}
private void initializeLauncherAnimationController() {
mLauncherLayoutListener.setHandler(this);
mLayoutListener.setHandler(this);
onLauncherLayoutChanged();
final long transitionDelay = mLauncherFrameDrawnTime - mTouchTimeMs;
SysuiEventLogger.writeDummyRecentsTransition(transitionDelay);
if (LatencyTrackerCompat.isEnabled(mContext)) {
LatencyTrackerCompat.logToggleRecents((int) transitionDelay);
}
}
public void updateInteractionType(@InteractionType int interactionType) {
@@ -428,21 +435,16 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
throw new IllegalArgumentException(
"Can't change interaction type from " + mInteractionType);
}
if (!isInteractionQuick(interactionType)) {
if (interactionType != INTERACTION_QUICK_SCRUB) {
throw new IllegalArgumentException(
"Can't change interaction type to " + interactionType);
}
mInteractionType = interactionType;
setStateOnUiThread(interactionType == INTERACTION_QUICK_SWITCH
? STATE_QUICK_SWITCH : STATE_QUICK_SCRUB_START);
setStateOnUiThread(STATE_QUICK_SCRUB_START);
// Start the window animation without waiting for launcher.
animateToProgress(1f, QUICK_SWITCH_START_DURATION);
}
private void onQuickInteractionStart() {
mQuickScrubController.onQuickScrubStart(false);
animateToProgress(1f, QUICK_SCRUB_START_DURATION);
}
@WorkerThread
@@ -455,32 +457,14 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
}
/**
* Called by {@link #mLauncherLayoutListener} when launcher layout changes
* Called by {@link #mLayoutListener} when launcher layout changes
*/
public void onLauncherLayoutChanged() {
initTransitionEndpoints(mLauncher.getDeviceProfile());
initTransitionEndpoints(mActivity.getDeviceProfile());
if (!mWasLauncherAlreadyVisible) {
float startProgress;
AllAppsTransitionController controller = mLauncher.getAllAppsController();
if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
startProgress = 1;
} else {
float scrollRange = Math.max(controller.getShiftRange(), 1);
startProgress = (mTransitionDragLength / scrollRange) + 1;
}
AnimatorSet anim = new AnimatorSet();
ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(controller, ALL_APPS_PROGRESS,
startProgress, OVERVIEW.getVerticalProgress(mLauncher));
shiftAnim.setInterpolator(LINEAR);
anim.play(shiftAnim);
// TODO: Link this animation to state animation, so that it is cancelled
// automatically on state change
anim.setDuration(mTransitionDragLength * 2);
mLauncherTransitionController =
AnimatorPlaybackController.wrap(anim, mTransitionDragLength * 2);
mLauncherTransitionController = mActivityControlHelper
.createControllerForHiddenActivity(mActivity, mTransitionDragLength);
mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
}
}
@@ -493,7 +477,12 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
if (mRecentsAnimationWrapper.controller != null) {
RectF currentRect;
synchronized (mTargetRect) {
currentRect = mRectFEvaluator.evaluate(shift, mSourceRect, mTargetRect);
Interpolator interpolator = mInteractionType == INTERACTION_QUICK_SCRUB
? ACCEL_2 : LINEAR;
float interpolated = interpolator.getInterpolation(shift);
currentRect = mRectFEvaluator.evaluate(interpolated, mSourceRect, mTargetRect);
// Stay lined up with the center of the target, since it moves for quick scrub.
currentRect.offset(mTargetRect.centerX() - currentRect.centerX(), 0);
}
mClipRect.left = (int) (mSourceWindowClipInsets.left * shift);
@@ -509,8 +498,13 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
if (app.mode == MODE_CLOSING) {
transaction.setMatrix(app.leash, mTmpMatrix)
.setWindowCrop(app.leash, mClipRect)
.show(app.leash);
.setWindowCrop(app.leash, mClipRect);
if (app.isNotInRecents) {
transaction.setAlpha(app.leash, 1 - shift);
}
transaction.show(app.leash);
}
}
transaction.apply();
@@ -525,17 +519,21 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
mLauncherTransitionController.setPlayFraction(shift);
// Make sure the window follows the first task if it moves, e.g. during quick scrub.
int firstTaskIndex = mRecentsView.getFirstTaskIndex();
View firstTask = mRecentsView.getPageAt(firstTaskIndex);
int scrollForFirstTask = mRecentsView.getScrollForPage(firstTaskIndex);
View firstTask = mRecentsView.getPageAt(0);
int scrollForFirstTask = mRecentsView.getScrollForPage(0);
int offsetFromFirstTask = (scrollForFirstTask - mRecentsView.getScrollX());
if (offsetFromFirstTask != 0) {
synchronized (mTargetRect) {
mTargetRect.set(mInitialTargetRect);
Utilities.scaleRectFAboutCenter(mTargetRect, firstTask.getScaleX());
float offsetX = offsetFromFirstTask + firstTask.getTranslationX();
mTargetRect.offset(offsetX, 0);
}
synchronized (mTargetRect) {
mTargetRect.set(mInitialTargetRect);
Utilities.scaleRectFAboutCenter(mTargetRect, firstTask.getScaleX());
float offsetX = offsetFromFirstTask + firstTask.getTranslationX();
float offsetY = mRecentsView.getTranslationY();
mTargetRect.offset(offsetX, offsetY);
}
if (mRecentsAnimationWrapper.controller != null) {
// TODO: This logic is spartanic!
mRecentsAnimationWrapper.controller.setAnimationTargetsBehindSystemBars(
shift < 0.12f);
}
};
if (Looper.getMainLooper() == Looper.myLooper()) {
@@ -583,14 +581,13 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
}
}
}
mRecentsAnimationWrapper.setController(controller, apps);
setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
}
public void onRecentsAnimationCanceled() {
mRecentsAnimationWrapper.setController(null, null);
clearReference();
mActivityInitListener.unregister();
setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
}
@@ -606,9 +603,10 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
* on both background and UI threads
*/
private void notifyGestureStarted() {
final Launcher curLauncher = mLauncher;
if (curLauncher != null) {
curLauncher.onQuickstepGestureStarted(mWasLauncherAlreadyVisible);
final T curActivity = mActivity;
if (curActivity != null) {
mActivityControlHelper.onQuickstepGestureStarted(
curActivity, mWasLauncherAlreadyVisible);
}
}
@@ -658,13 +656,14 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
private void animateToProgress(float progress, long duration) {
mIsGoingToHome = Float.compare(progress, 1) == 0;
ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
anim.setInterpolator(Interpolators.SCROLL);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
setStateOnUiThread((Float.compare(mCurrentShift.value, 0) == 0)
? STATE_SCALED_CONTROLLER_APP : STATE_SCALED_CONTROLLER_RECENTS);
setStateOnUiThread(mIsGoingToHome ?
STATE_SCALED_CONTROLLER_RECENTS : STATE_SCALED_CONTROLLER_APP);
}
});
anim.start();
@@ -685,30 +684,26 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
}
private void invalidateHandler() {
mCurrentShift.cancelAnimation();
mCurrentShift.finishAnimation();
if (mGestureEndCallback != null) {
mGestureEndCallback.run();
}
clearReference();
mActivityInitListener.unregister();
mInputConsumer.unregisterInputConsumer();
}
private void invalidateHandlerWithLauncher() {
mLauncherTransitionController = null;
mLauncherLayoutListener.setHandler(null);
mLauncherLayoutListener.close(false);
mLayoutListener.finish();
// Restore the requested orientation to the user preference after the gesture has ended
mLauncher.updateRequestedOrientation();
mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, false /* animate */);
}
private void resetStateForAnimationCancel() {
LauncherState startState = mLauncher.getStateManager().getRestState();
boolean animate = mWasLauncherAlreadyVisible || mGestureStarted;
mLauncher.getStateManager().goToState(startState, animate);
boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
mActivityControlHelper.onTransitionCancelled(mActivity, wasVisible);
}
public void layoutListenerClosed() {
@@ -737,17 +732,8 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
if (taskView != null) {
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
ViewOnDrawExecutor executor = new ViewOnDrawExecutor() {
@Override
public void onViewDetachedFromWindow(View v) {
if (!isCompleted()) {
runAllTasks();
}
}
};
executor.attachTo(mLauncher, taskView,
false /* waitForLoadAnimation */);
executor.execute(finishTransitionRunnable);
mActivityControlHelper.executeOnNextDraw(mActivity, taskView,
finishTransitionRunnable);
finishTransitionPosted = true;
}
}
@@ -763,8 +749,7 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
}
private void setupLauncherUiAfterSwipeUpAnimation() {
// Re apply state in case we did something funky during the transition.
mLauncher.getStateManager().reapplyState();
mActivityControlHelper.onSwipeUpComplete(mActivity);
// Animate the first icon.
mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, true /* animate */);
@@ -772,21 +757,13 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
reset();
}
public void onQuickScrubEnd() {
setStateOnUiThread(STATE_QUICK_SCRUB_END);
private void onQuickScrubStart() {
mActivityControlHelper.onQuickInteractionStart(mActivity, mWasLauncherAlreadyVisible);
mQuickScrubController.onQuickScrubStart(false);
}
private void switchToFinalAppAfterQuickSwitch() {
mQuickScrubController.onQuickSwitch();
}
private void switchToFinalAppAfterQuickScrub() {
mQuickScrubController.onQuickScrubEnd();
// Normally this is handled in reset(), but since we are still scrubbing after the
// transition into recents, we need to defer the handler invalidation for quick scrub until
// after the gesture ends
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
private void onFinishedTransitionToQuickScrub() {
mQuickScrubController.onFinishedTransitionToQuickScrub();
}
public void onQuickScrubProgress(float progress) {
@@ -799,6 +776,19 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
mQuickScrubController.onQuickScrubProgress(progress);
}
public void onQuickScrubEnd() {
setStateOnUiThread(STATE_QUICK_SCRUB_END);
}
private void switchToFinalAppAfterQuickScrub() {
mQuickScrubController.onQuickScrubEnd();
// Normally this is handled in reset(), but since we are still scrubbing after the
// transition into recents, we need to defer the handler invalidation for quick scrub until
// after the gesture ends
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
private void debugNewState(int stateFlag) {
if (!DEBUG_STATES) {
return;
@@ -818,4 +808,8 @@ public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding " + stateFlagStr + " to "
+ currentStateStr);
}
public void setGestureEndCallback(Runnable gestureEndCallback) {
mGestureEndCallback = gestureEndCallback;
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.util;
import android.metrics.LogMaker;
import android.util.EventLog;
/**
* Utility class for writing logs on behalf of systemUI
*/
public class SysuiEventLogger {
/** 524292 sysui_multi_action (content|4) */
public static final int SYSUI_MULTI_ACTION = 524292;
private static void write(LogMaker content) {
if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
content.setType(4/*MetricsEvent.TYPE_ACTION*/);
}
EventLog.writeEvent(SYSUI_MULTI_ACTION, content.serialize());
}
public static void writeDummyRecentsTransition(long transitionDelay) {
// Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
// "Recents" activity for app transition tests for the app-to-recents case.
final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
builder.setPackageName("com.android.systemui");
builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
"com.android.systemui.recents.RecentsActivity");
builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
transitionDelay);
write(builder);
}
}
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
package com.android.quickstep.views;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import android.graphics.Rect;
import android.view.MotionEvent;
@@ -21,11 +23,14 @@ import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.quickstep.ActivityControlHelper.LayoutListener;
import com.android.quickstep.WindowTransformSwipeHandler;
/**
* Floating view which shows the task snapshot allowing it to be dragged and placed.
*/
public class LauncherLayoutListener extends AbstractFloatingView implements Insettable {
public class LauncherLayoutListener extends AbstractFloatingView
implements Insettable, LayoutListener {
private final Launcher mLauncher;
private WindowTransformSwipeHandler mHandler;
@@ -36,6 +41,7 @@ public class LauncherLayoutListener extends AbstractFloatingView implements Inse
setVisibility(INVISIBLE);
}
@Override
public void setHandler(WindowTransformSwipeHandler handler) {
mHandler = handler;
}
@@ -65,6 +71,7 @@ public class LauncherLayoutListener extends AbstractFloatingView implements Inse
}
}
@Override
public void open() {
if (!mIsOpen) {
mLauncher.getDragLayer().addView(this);
@@ -86,4 +93,11 @@ public class LauncherLayoutListener extends AbstractFloatingView implements Inse
protected boolean isOfType(int type) {
return (type & TYPE_QUICKSTEP_PREVIEW) != 0;
}
@Override
public void finish() {
setHandler(null);
close(false);
mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
}
}
@@ -0,0 +1,129 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.views;
import static com.android.launcher3.LauncherState.NORMAL;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.widget.FrameLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
/**
* {@link RecentsView} used in Launcher activity
*/
@TargetApi(Build.VERSION_CODES.O)
public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {
public static final FloatProperty<LauncherRecentsView> TRANSLATION_X_FACTOR =
new FloatProperty<LauncherRecentsView>("translationXFactor") {
@Override
public void setValue(LauncherRecentsView view, float v) {
view.setTranslationXFactor(v);
}
@Override
public Float get(LauncherRecentsView view) {
return view.mTranslationXFactor;
}
};
public static final FloatProperty<LauncherRecentsView> TRANSLATION_Y_FACTOR =
new FloatProperty<LauncherRecentsView>("translationYFactor") {
@Override
public void setValue(LauncherRecentsView view, float v) {
view.setTranslationYFactor(v);
}
@Override
public Float get(LauncherRecentsView view) {
return view.mTranslationYFactor;
}
};
private float mTranslationXFactor;
private float mTranslationYFactor;
private Rect mPagePadding = new Rect();
public LauncherRecentsView(Context context) {
this(context, null);
}
public LauncherRecentsView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setContentAlpha(0);
}
@Override
public void setInsets(Rect insets) {
mInsets.set(insets);
DeviceProfile dp = mActivity.getDeviceProfile();
Rect padding = getPadding(dp, getContext());
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
lp.bottomMargin = padding.bottom;
setLayoutParams(lp);
setPadding(padding.left, padding.top, padding.right, 0);
mPagePadding.set(padding);
mPagePadding.top += getResources().getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
}
@Override
protected void onAllTasksRemoved() {
mActivity.getStateManager().goToState(NORMAL);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int width = right - left;
setTranslationX(mTranslationXFactor * (mIsRtl ? -width : width));
setTranslationYFactor(mTranslationYFactor);
}
public void setTranslationXFactor(float translationFactor) {
mTranslationXFactor = translationFactor;
setTranslationX(translationFactor * (mIsRtl ? -getWidth() : getWidth()));
}
public float getTranslationXFactor() {
return mTranslationXFactor;
}
public void setTranslationYFactor(float translationFactor) {
mTranslationYFactor = translationFactor;
setTranslationY(mTranslationYFactor * (mPagePadding.bottom - mPagePadding.top));
}
public float getTranslationYFactor() {
return mTranslationYFactor;
}
}
@@ -0,0 +1,711 @@
/*
* 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.views;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.graphics.Rect;
import android.os.Build;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.SparseBooleanArray;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.PendingAnimation;
import com.android.quickstep.QuickScrubController;
import com.android.quickstep.RecentsModel;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.ArrayList;
/**
* A list of recent tasks.
*/
@TargetApi(Build.VERSION_CODES.P)
public abstract class RecentsView<T extends BaseActivity>
extends PagedView implements OnSharedPreferenceChangeListener {
public static final FloatProperty<RecentsView> CONTENT_ALPHA =
new FloatProperty<RecentsView>("contentAlpha") {
@Override
public void setValue(RecentsView recentsView, float v) {
recentsView.setContentAlpha(v);
}
@Override
public Float get(RecentsView recentsView) {
return recentsView.mContentAlpha;
}
};
private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
private static final int DISMISS_TASK_DURATION = 300;
private static final Rect sTempStableInsets = new Rect();
protected final T mActivity;
private final QuickScrubController mQuickScrubController;
private final float mFastFlingVelocity;
private final RecentsModel mModel;
private final ScrollState mScrollState = new ScrollState();
// Keeps track of the previously known visible tasks for purposes of loading/unloading task data
private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
for (int i = 0; i < getChildCount(); i++) {
final TaskView taskView = (TaskView) getChildAt(i);
if (taskView.getTask().key.id == taskId) {
taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
return;
}
}
}
};
private int mLoadPlanId = -1;
// Only valid until the launcher state changes to NORMAL
private int mRunningTaskId = -1;
private boolean mFirstTaskIconScaledDown = false;
private boolean mOverviewStateEnabled;
private boolean mTaskStackListenerRegistered;
private Runnable mNextPageSwitchRunnable;
private PendingAnimation mPendingAnimation;
private float mContentAlpha = 1;
// Keeps track of task views whose visual state should not be reset
private ArraySet<TaskView> mIgnoreResetTaskViews = new ArraySet<>();
public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
enableFreeScroll(true);
setClipToOutline(true);
mFastFlingVelocity = getResources()
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
mActivity = (T) BaseActivity.fromContext(context);
mQuickScrubController = new QuickScrubController(mActivity, this);
mModel = RecentsModel.getInstance(context);
onSharedPreferenceChanged(Utilities.getPrefs(context), PREF_FLIP_RECENTS);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
if (s.equals(PREF_FLIP_RECENTS)) {
mIsRtl = Utilities.isRtl(getResources());
if (sharedPreferences.getBoolean(PREF_FLIP_RECENTS, false)) {
mIsRtl = !mIsRtl;
}
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
}
}
public boolean isRtl() {
return mIsRtl;
}
public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
for (int i = 0; i < getChildCount(); i++) {
final TaskView taskView = (TaskView) getChildAt(i);
if (taskView.getTask().key.id == taskId) {
taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
taskView.setAlpha(1);
return taskView;
}
}
return null;
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
updateTaskStackListenerState();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
updateTaskStackListenerState();
Utilities.getPrefs(getContext()).registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
updateTaskStackListenerState();
Utilities.getPrefs(getContext()).unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
// Clear the task data for the removed child if it was visible
Task task = ((TaskView) child).getTask();
if (mHasVisibleTaskData.get(task.key.id)) {
mHasVisibleTaskData.delete(task.key.id);
RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
loader.unloadTaskData(task);
loader.getHighResThumbnailLoader().onTaskInvisible(task);
}
}
public boolean isTaskViewVisible(TaskView tv) {
// For now, just check if it's the active task or an adjacent task
return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
}
public TaskView getTaskView(int taskId) {
for (int i = 0; i < getChildCount(); i++) {
TaskView tv = (TaskView) getChildAt(i);
if (tv.getTask().key.id == taskId) {
return tv;
}
}
return null;
}
public void setOverviewStateEnabled(boolean enabled) {
mOverviewStateEnabled = enabled;
updateTaskStackListenerState();
}
public void setNextPageSwitchRunnable(Runnable r) {
mNextPageSwitchRunnable = r;
}
@Override
protected void onPageEndTransition() {
super.onPageEndTransition();
if (mNextPageSwitchRunnable != null) {
mNextPageSwitchRunnable.run();
mNextPageSwitchRunnable = null;
}
}
private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
if (mPendingAnimation != null) {
mPendingAnimation.addEndListener((b) -> applyLoadPlan(loadPlan));
return;
}
TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
if (stack == null) {
removeAllViews();
return;
}
int oldChildCount = getChildCount();
// Ensure there are as many views as there are tasks in the stack (adding and trimming as
// necessary)
final LayoutInflater inflater = LayoutInflater.from(getContext());
final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
final int requiredChildCount = tasks.size();
for (int i = getChildCount(); i < requiredChildCount; i++) {
final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
addView(taskView);
}
while (getChildCount() > requiredChildCount) {
final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
removeView(taskView);
}
// Unload existing visible task data
unloadVisibleTaskData();
// Rebind and reset all task views
for (int i = requiredChildCount - 1; i >= 0; i--) {
final int pageIndex = requiredChildCount - i - 1;
final Task task = tasks.get(i);
final TaskView taskView = (TaskView) getChildAt(pageIndex);
taskView.bind(task);
}
resetTaskVisuals();
applyIconScale(false /* animate */);
if (oldChildCount != getChildCount()) {
mQuickScrubController.snapToNextTaskIfAvailable();
}
}
public void resetTaskVisuals() {
for (int i = getChildCount() - 1; i >= 0; i--) {
TaskView taskView = (TaskView) getChildAt(i);
if (!mIgnoreResetTaskViews.contains(taskView)) {
taskView.resetVisualProperties();
}
}
updateCurveProperties();
// Update the set of visible task's data
loadVisibleTaskData();
}
private void updateTaskStackListenerState() {
boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
&& getWindowVisibility() == VISIBLE;
if (registerStackListener != mTaskStackListenerRegistered) {
if (registerStackListener) {
ActivityManagerWrapper.getInstance()
.registerTaskStackListener(mTaskStackListener);
reloadIfNeeded();
} else {
ActivityManagerWrapper.getInstance()
.unregisterTaskStackListener(mTaskStackListener);
}
mTaskStackListenerRegistered = registerStackListener;
}
}
protected static Rect getPadding(DeviceProfile profile, Context context) {
WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
Rect padding = new Rect(profile.workspacePadding);
float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
float overviewHeight, overviewWidth;
if (profile.isVerticalBarLayout()) {
float maxPadding = Math.max(padding.left, padding.right);
// Use the same padding on both sides for symmetry.
float availableWidth = taskWidth - 2 * maxPadding;
float availableHeight = profile.availableHeightPx - padding.top - padding.bottom
- sTempStableInsets.top;
float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
overviewHeight = taskHeight * scaledRatio;
overviewWidth = taskWidth * scaledRatio;
} else {
overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
- sTempStableInsets.top;
overviewWidth = taskWidth * overviewHeight / taskHeight;
}
padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
- Math.round(overviewHeight);
padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
return padding;
}
public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
Rect targetPadding = getPadding(grid, context);
Rect insets = grid.getInsets();
outRect.set(
targetPadding.left + insets.left,
targetPadding.top + insets.top,
grid.widthPx - targetPadding.right - insets.right,
grid.heightPx - targetPadding.bottom - insets.bottom);
outRect.top += context.getResources()
.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
}
@Override
protected boolean computeScrollHelper() {
boolean scrolling = super.computeScrollHelper();
boolean isFlingingFast = false;
updateCurveProperties();
if (scrolling || (mTouchState == TOUCH_STATE_SCROLLING)) {
if (scrolling) {
// Check if we are flinging quickly to disable high res thumbnail loading
isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
}
// After scrolling, update the visible task's data
loadVisibleTaskData();
}
// Update the high res thumbnail loader
RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
loader.getHighResThumbnailLoader().setFlingingFast(isFlingingFast);
return scrolling;
}
/**
* Scales and adjusts translation of adjacent pages as if on a curved carousel.
*/
public void updateCurveProperties() {
if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
return;
}
final int halfPageWidth = getNormalChildWidth() / 2;
final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
final int halfScreenWidth = getMeasuredWidth() / 2;
final int pageSpacing = mPageSpacing;
final int pageCount = getPageCount();
for (int i = 0; i < pageCount; i++) {
View page = getPageAt(i);
float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth;
float distanceFromScreenCenter = screenCenter - pageCenter;
float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
mScrollState.linearInterpolation = Math.min(1,
Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
((PageCallbacks) page).onPageScroll(mScrollState);
}
}
/**
* Iterates through all thet asks, and loads the associated task data for newly visible tasks,
* and unloads the associated task data for tasks that are no longer visible.
*/
public void loadVisibleTaskData() {
RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
int centerPageIndex = getPageNearestToCenterOfScreen();
int lower = Math.max(0, centerPageIndex - 2);
int upper = Math.min(centerPageIndex + 2, getChildCount() - 1);
int numChildren = getChildCount();
// Update the task data for the in/visible children
for (int i = 0; i < numChildren; i++) {
TaskView taskView = (TaskView) getChildAt(i);
Task task = taskView.getTask();
boolean visible = lower <= i && i <= upper;
if (visible) {
if (!mHasVisibleTaskData.get(task.key.id)) {
loader.loadTaskData(task);
loader.getHighResThumbnailLoader().onTaskVisible(task);
}
mHasVisibleTaskData.put(task.key.id, visible);
} else {
if (mHasVisibleTaskData.get(task.key.id)) {
loader.unloadTaskData(task);
loader.getHighResThumbnailLoader().onTaskInvisible(task);
}
mHasVisibleTaskData.delete(task.key.id);
}
}
}
/**
* Unloads any associated data from the currently visible tasks
*/
private void unloadVisibleTaskData() {
RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
if (mHasVisibleTaskData.valueAt(i)) {
TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
Task task = taskView.getTask();
loader.unloadTaskData(task);
loader.getHighResThumbnailLoader().onTaskInvisible(task);
}
}
mHasVisibleTaskData.clear();
}
protected abstract void onAllTasksRemoved();
public void reset() {
unloadVisibleTaskData();
mRunningTaskId = -1;
setCurrentPage(0);
}
/**
* Reloads the view if anything in recents changed.
*/
public void reloadIfNeeded() {
if (!mModel.isLoadPlanValid(mLoadPlanId)) {
mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
}
}
/**
* Ensures that the first task in the view represents {@param task} and reloads the view
* if needed. This allows the swipe-up gesture to assume that the first tile always
* corresponds to the correct task.
* All subsequent calls to reload will keep the task as the first item until {@link #reset()}
* is called.
* Also scrolls the view to this task
*/
public void showTask(int runningTaskId) {
boolean needsReload = false;
if (getChildCount() == 0) {
needsReload = true;
// Add an empty view for now
final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
.inflate(R.layout.task, this, false);
addView(taskView, 0);
}
mRunningTaskId = runningTaskId;
setCurrentPage(0);
if (!needsReload) {
needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
}
if (needsReload) {
mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
} else {
loadVisibleTaskData();
}
getPageAt(mCurrentPage).setAlpha(0);
}
public QuickScrubController getQuickScrubController() {
return mQuickScrubController;
}
public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) {
if (mFirstTaskIconScaledDown == isScaledDown) {
return;
}
mFirstTaskIconScaledDown = isScaledDown;
applyIconScale(animate);
}
private void applyIconScale(boolean animate) {
float scale = mFirstTaskIconScaledDown ? 0 : 1;
TaskView firstTask = (TaskView) getChildAt(0);
if (firstTask != null) {
if (animate) {
firstTask.animateIconToScale(scale);
} else {
firstTask.setIconScale(scale);
}
}
}
public interface PageCallbacks {
/**
* Updates the page UI based on scroll params.
*/
default void onPageScroll(ScrollState scrollState) {};
}
public static class ScrollState {
/**
* The progress from 0 to 1, where 0 is the center
* of the screen and 1 is the edge of the screen.
*/
public float linearInterpolation;
}
public void addIgnoreResetTask(TaskView taskView) {
mIgnoreResetTaskViews.add(taskView);
}
public void removeIgnoreResetTask(TaskView taskView) {
mIgnoreResetTaskViews.remove(taskView);
}
public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
boolean removeTask, long duration) {
if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
throw new IllegalStateException("Another pending animation is still running");
}
AnimatorSet anim = new AnimatorSet();
PendingAnimation pendingAnimation = new PendingAnimation(anim);
int count = getChildCount();
if (count == 0) {
return pendingAnimation;
}
int[] oldScroll = new int[count];
getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
int[] newScroll = new int[count];
getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
int maxScrollDiff = 0;
int lastPage = mIsRtl ? 0 : count - 1;
if (getChildAt(lastPage) == taskView) {
if (count > 1) {
int secondLastPage = mIsRtl ? 1 : count - 2;
maxScrollDiff = oldScroll[lastPage] - newScroll[secondLastPage];
}
}
boolean needsCurveUpdates = false;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child == taskView) {
if (animateTaskView) {
addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
duration, LINEAR, anim);
}
} else {
int scrollDiff = newScroll[i] - oldScroll[i] + maxScrollDiff;
if (scrollDiff != 0) {
addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff),
duration, ACCEL, anim);
needsCurveUpdates = true;
}
}
}
if (needsCurveUpdates) {
ValueAnimator va = ValueAnimator.ofFloat(0, 1);
va.addUpdateListener((a) -> updateCurveProperties());
anim.play(va);
}
// Add a tiny bit of translation Z, so that it draws on top of other views
if (animateTaskView) {
taskView.setTranslationZ(0.1f);
}
mPendingAnimation = pendingAnimation;
mPendingAnimation.addEndListener((isSuccess) -> {
if (isSuccess) {
if (removeTask) {
ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
}
removeView(taskView);
if (getChildCount() == 0) {
onAllTasksRemoved();
}
}
resetTaskVisuals();
mPendingAnimation = null;
});
return pendingAnimation;
}
private static void addAnim(ObjectAnimator anim, long duration,
TimeInterpolator interpolator, AnimatorSet set) {
anim.setDuration(duration).setInterpolator(interpolator);
set.play(anim);
}
private void snapToPageRelative(int delta) {
snapToPage((getNextPage() + getPageCount() + delta) % getPageCount());
}
@Override
public void onVisibilityAggregated(boolean isVisible) {
super.onVisibilityAggregated(isVisible);
if (isVisible && !isFocused()) {
// Having focus, even in touch mode, keeps us from losing [Alt+]Tab by preventing
// switching to keyboard mode.
requestFocus();
}
}
public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
PendingAnimation pendingAnim = createTaskDismissAnimation(taskView, animateTaskView,
removeTask, DISMISS_TASK_DURATION);
AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(
pendingAnim.anim, DISMISS_TASK_DURATION);
controller.dispatchOnStart();
controller.setEndAction(() -> pendingAnim.finish(true));
controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
controller.start();
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_TAB:
snapToPageRelative(event.isShiftPressed() ? -1 : 1);
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
snapToPageRelative(mIsRtl ? -1 : 1);
return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
snapToPageRelative(mIsRtl ? 1 : -1);
return true;
case KeyEvent.KEYCODE_DEL:
case KeyEvent.KEYCODE_FORWARD_DEL:
dismissTask((TaskView) getChildAt(getNextPage()), true /*animateTaskView*/,
true /*removeTask*/);
return true;
case KeyEvent.KEYCODE_NUMPAD_DOT:
if (event.isAltPressed()) {
// Numpad DEL pressed while holding Alt.
dismissTask((TaskView) getChildAt(getNextPage()), true /*animateTaskView*/,
true /*removeTask*/);
return true;
}
}
}
return super.dispatchKeyEvent(event);
}
public void snapToTaskAfterNext() {
snapToPageRelative(1);
}
public void launchNextTask() {
final TaskView nextTask = (TaskView) getChildAt(getNextPage());
nextTask.launchTask(true);
}
public void setContentAlpha(float alpha) {
if (mContentAlpha == alpha) {
return;
}
mContentAlpha = alpha;
for (int i = getChildCount() - 1; i >= 0; i--) {
getChildAt(i).setAlpha(alpha);
}
setVisibility(alpha > 0 ? VISIBLE : GONE);
}
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
child.setAlpha(mContentAlpha);
}
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.quickstep;
package com.android.quickstep.views;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -32,14 +32,16 @@ import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.TaskSystemShortcut;
import com.android.quickstep.TaskUtils;
/**
* Contains options for a recent task when long-pressing its icon.
@@ -58,7 +60,7 @@ public class TaskMenuView extends AbstractFloatingView {
private static final long OPEN_CLOSE_DURATION = 220;
private Launcher mLauncher;
private BaseDraggingActivity mActivity;
private TextView mTaskIconAndName;
private AnimatorSet mOpenCloseAnimator;
private TaskView mTaskView;
@@ -70,7 +72,7 @@ public class TaskMenuView extends AbstractFloatingView {
public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
mActivity = BaseDraggingActivity.fromContext(context);
setClipToOutline(true);
setOutlineProvider(new ViewOutlineProvider() {
@Override
@@ -90,7 +92,7 @@ public class TaskMenuView extends AbstractFloatingView {
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
DragLayer dl = mLauncher.getDragLayer();
BaseDragLayer dl = mActivity.getDragLayer();
if (!dl.isEventOverView(this, ev)) {
// TODO: log this once we have a new container type for it?
close(true);
@@ -120,9 +122,9 @@ public class TaskMenuView extends AbstractFloatingView {
}
public static boolean showForTask(TaskView taskView) {
Launcher launcher = Launcher.getLauncher(taskView.getContext());
final TaskMenuView taskMenuView = (TaskMenuView) launcher.getLayoutInflater().inflate(
R.layout.task_menu, launcher.getDragLayer(), false);
BaseDraggingActivity activity = BaseDraggingActivity.fromContext(taskView.getContext());
final TaskMenuView taskMenuView = (TaskMenuView) activity.getLayoutInflater().inflate(
R.layout.task_menu, activity.getDragLayer(), false);
return taskMenuView.populateAndShowForTask(taskView);
}
@@ -130,7 +132,7 @@ public class TaskMenuView extends AbstractFloatingView {
if (isAttachedToWindow()) {
return false;
}
mLauncher.getDragLayer().addView(this);
mActivity.getDragLayer().addView(this);
mTaskView = taskView;
addMenuOptions(mTaskView);
orientAroundTaskView(mTaskView);
@@ -143,11 +145,11 @@ public class TaskMenuView extends AbstractFloatingView {
int iconSize = getResources().getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
icon.setBounds(0, 0, iconSize, iconSize);
mTaskIconAndName.setCompoundDrawables(null, icon, null, null);
mTaskIconAndName.setText(TaskUtils.getTitle(mLauncher, taskView.getTask()));
mTaskIconAndName.setText(TaskUtils.getTitle(getContext(), taskView.getTask()));
mTaskIconAndName.setOnClickListener(v -> close(true));
for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
OnClickListener onClickListener = menuOption.getOnClickListener(mLauncher, taskView);
OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, taskView);
if (onClickListener != null) {
addMenuOption(menuOption, onClickListener);
}
@@ -155,7 +157,7 @@ public class TaskMenuView extends AbstractFloatingView {
}
private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) {
DeepShortcutView menuOptionView = (DeepShortcutView) mLauncher.getLayoutInflater().inflate(
DeepShortcutView menuOptionView = (DeepShortcutView) mActivity.getLayoutInflater().inflate(
R.layout.system_shortcut, this, false);
menuOptionView.getIconView().setBackgroundResource(menuOption.iconResId);
menuOptionView.getBubbleText().setText(menuOption.labelResId);
@@ -165,8 +167,8 @@ public class TaskMenuView extends AbstractFloatingView {
private void orientAroundTaskView(TaskView taskView) {
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
mLauncher.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
Rect insets = mLauncher.getDragLayer().getInsets();
mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
Rect insets = mActivity.getDragLayer().getInsets();
int x = sTempRect.left + (sTempRect.width() - getMeasuredWidth()) / 2 - insets.left;
setX(Utilities.isRtl(getResources()) ? -x : x);
setY(sTempRect.top - mTaskIconAndName.getPaddingTop() - insets.top);
@@ -209,7 +211,7 @@ public class TaskMenuView extends AbstractFloatingView {
private void closeComplete() {
mIsOpen = false;
mLauncher.getDragLayer().removeView(this);
mActivity.getDragLayer().removeView(this);
}
private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.quickstep;
package com.android.quickstep.views;
import android.content.Context;
import android.content.res.Configuration;
@@ -33,9 +33,10 @@ import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -74,6 +75,7 @@ public class TaskThumbnailView extends View {
mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
mFadeLength = getResources().getDimension(R.dimen.task_fade_length);
mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
mPaint.setFilterBitmap(true);
}
public void bind() {
@@ -151,7 +153,8 @@ public class TaskThumbnailView extends View {
} else {
final Configuration configuration =
getContext().getApplicationContext().getResources().getConfiguration();
final DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile();
final DeviceProfile profile = BaseActivity.fromContext(getContext())
.getDeviceProfile();
if (configuration.orientation == mThumbnailData.orientation) {
// If we are in the same orientation as the screenshot, just scale it to the
// width of the task view
@@ -14,10 +14,7 @@
* limitations under the License.
*/
package com.android.quickstep;
import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
package com.android.quickstep.views;
import android.animation.TimeInterpolator;
import android.app.ActivityOptions;
@@ -31,10 +28,10 @@ import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.launcher3.Launcher;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.quickstep.RecentsView.PageCallbacks;
import com.android.quickstep.RecentsView.ScrollState;
import com.android.quickstep.views.RecentsView.PageCallbacks;
import com.android.quickstep.views.RecentsView.ScrollState;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -47,17 +44,20 @@ import java.util.function.Consumer;
*/
public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
/** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
public static final float CURVE_FACTOR = 0.25f;
/** A circular curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
public static final TimeInterpolator CURVE_INTERPOLATOR
= x -> (float) (1 - Math.sqrt(1 - Math.pow(x, 2)));
/** A curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
private static final TimeInterpolator CURVE_INTERPOLATOR
= x -> (float) -Math.cos(x * Math.PI) / 2f + .5f;
/**
* The alpha of a black scrim on a page in the carousel as it leaves the screen.
* In the resting position of the carousel, the adjacent pages have about half this scrim.
*/
private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
private static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
/**
* How much to scale down pages near the edge of the screen.
*/
private static final float EDGE_SCALE_DOWN_FACTOR = 0.03f;
private static final long SCALE_ICON_DURATION = 120;
@@ -96,6 +96,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
mTask = task;
mSnapshotView.bind();
task.addCallback(this);
setContentDescription(task.titleDescription);
}
public Task getTask() {
@@ -115,7 +116,8 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
if (mTask != null) {
final ActivityOptions opts;
if (animate) {
opts = Launcher.getLauncher(getContext()).getActivityLaunchOptions(this, false);
opts = BaseDraggingActivity.fromContext(getContext())
.getActivityLaunchOptions(this, false);
} else {
opts = ActivityOptions.makeCustomAnimation(getContext(), 0, 0);
}
@@ -159,38 +161,26 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
setScaleY(1f);
setTranslationX(0f);
setTranslationY(0f);
setTranslationZ(0);
setAlpha(1f);
}
@Override
public int onPageScroll(ScrollState scrollState) {
public void onPageScroll(ScrollState scrollState) {
float curveInterpolation =
CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
float scale = 1 - curveInterpolation * CURVE_FACTOR;
setScaleX(scale);
setScaleY(scale);
// Make sure the biggest card (i.e. the one in front) shows on top of the adjacent ones.
setTranslationZ(scale);
mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
float translation =
scrollState.distanceFromScreenCenter * curveInterpolation * CURVE_FACTOR;
float scale = 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
setScaleX(scale);
setScaleY(scale);
}
if (scrollState.lastScrollType == SCROLL_TYPE_WORKSPACE) {
// Make sure that the task cards do not overlap with the workspace card
float min = scrollState.halfPageWidth * (1 - scale);
if (scrollState.isRtl) {
setTranslationX(Math.min(translation, min) - scrollState.prevPageExtraWidth);
} else {
setTranslationX(Math.max(translation, -min) + scrollState.prevPageExtraWidth);
}
} else {
setTranslationX(translation);
}
scrollState.prevPageExtraWidth = 0;
return SCROLL_TYPE_TASK;
@Override
public boolean hasOverlappingRendering() {
// TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
return false;
}
private static final class TaskOutlineProvider extends ViewOutlineProvider {
@@ -15,5 +15,5 @@
-->
<com.android.launcher3.graphics.ShadowDrawable
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_info_no_shadow"
android:src="@drawable/ic_setting"
android:elevation="@dimen/drop_target_shadow_elevation" />
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?android:attr/colorAccent" />
<size android:height="1dp" />
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="?attr/popupColorPrimary" />
<corners android:radius="2dp" />
</shape>
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="2dp"
android:width="16dp"
android:viewportHeight="2.0"
android:viewportWidth="16.0">
<path
android:fillColor="@android:color/white"
android:pathData="M1,0h14c0.55,0,1,0.45,1,1s-0.45,1-1,1H1C0.45,2,0,1.55,0,1S0.45,0,1,0z"/>
</vector>
+3 -2
View File
@@ -17,9 +17,10 @@ Copyright (C) 2016 The Android Open Source Project
android:width="@dimen/options_menu_icon_size"
android:height="@dimen/options_menu_icon_size"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
android:viewportHeight="48.0"
android:tint="?android:attr/textColorPrimary" >
<path
android:fillColor="?android:attr/textColorPrimary"
android:fillColor="#FFFFFFFF"
android:pathData="M42.8,28.4l-3.88-2.9c0.06-0.5,0.08-1,0.08-1.52s-0.02-1.02-0.08-1.52l3.88-2.86c0.84-0.62,1.04-1.88,
0.48-2.82l-3.2-5.52c-0.56-0.96-1.76-1.4-2.72-1l-4.28,1.82c-0.96-0.74-2.02-1.36-3.14-1.84l-0.54-4.4C29.28,4.8,28.28,4,
27.18,4h-6.36c-1.1,0-2.1,0.8-2.22,1.84l-0.52,4.38c-1.14,0.48-2.2,1.1-3.16,1.84l-4.28-1.82c-0.96-0.4-2.16,0.04-2.72,
+2 -9
View File
@@ -29,20 +29,13 @@
<include layout="@layout/all_apps_rv_layout" />
<include layout="@layout/all_apps_fast_scroller" />
<include layout="@layout/all_apps_floating_header" />
<!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
platform bug, which prevents using custom attributes in <include> tag -->
<include
android:id="@id/search_container_all_apps"
layout="?android:attr/keyboardLayout"/>
layout="@layout/search_container_all_apps"/>
<View
android:id="@+id/nav_bar_bg"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_alignParentBottom="true"
android:background="?attr/allAppsNavBarScrimColor" />
<include layout="@layout/all_apps_fast_scroller" />
</com.android.launcher3.allapps.AllAppsContainerView>
+3 -3
View File
@@ -21,8 +21,8 @@
android:id="@+id/fast_scroller_popup"
style="@style/FastScrollerPopup"
android:layout_alignParentEnd="true"
android:layout_below="@+id/search_container_all_apps"
android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
android:layout_marginEnd="@dimen/fastscroll_popup_margin"
android:layout_marginTop="@dimen/all_apps_search_bar_field_height_and_margin" />
<com.android.launcher3.views.RecyclerViewFastScroller
android:id="@+id/fast_scroller"
@@ -30,8 +30,8 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_below="@+id/search_container_all_apps"
android:layout_marginEnd="@dimen/fastscroll_end_margin"
android:layout_marginTop="@dimen/all_apps_search_bar_field_height_and_margin"
launcher:canThumbDetach="true" />
</merge>
+3 -3
View File
@@ -18,10 +18,10 @@
android:id="@+id/all_apps_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/search_container_all_apps"
android:layout_marginTop="@dimen/all_apps_search_bar_field_height_and_margin"
android:clipToPadding="false"
android:paddingTop="@dimen/all_apps_header_top_padding"
android:orientation="vertical" >
android:orientation="vertical"
android:paddingTop="@dimen/all_apps_header_top_padding" >
<com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
android:id="@+id/tabs"
+2 -3
View File
@@ -19,8 +19,7 @@
android:id="@+id/apps_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/search_container_all_apps"
android:layout_marginTop="@dimen/all_apps_search_bar_field_height_and_margin"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:focusable="true"
android:overScrollMode="never" />
android:focusable="true" />
+1 -2
View File
@@ -20,9 +20,8 @@
android:id="@+id/all_apps_tabs_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/search_container_all_apps"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="@dimen/all_apps_header_tab_height"
android:layout_marginTop="@dimen/all_apps_tabs_top_margin"
android:clipChildren="true"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
-1
View File
@@ -35,7 +35,6 @@
android:textColor="?android:attr/textColorPrimary"
android:fontFamily="sans-serif"
launcher:layoutHorizontal="true"
launcher:deferShadowGeneration="true"
launcher:iconDisplay="shortcut_popup"
launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
+1 -11
View File
@@ -32,18 +32,8 @@
android:gravity="center"
android:text="@string/remove_drop_target_label" />
<!-- App Info -->
<com.android.launcher3.InfoDropTarget
android:id="@+id/info_target_text"
style="@style/DropTargetButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/app_info_drop_target_label" />
<!-- Uninstall target -->
<com.android.launcher3.UninstallDropTarget
<com.android.launcher3.SecondaryDropTarget
android:id="@+id/uninstall_target_text"
style="@style/DropTargetButton"
android:layout_width="wrap_content"
+10
View File
@@ -17,6 +17,16 @@
android:theme="@style/HomeScreenElementTheme"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/drag_indicator"
android:layout_width="@dimen/hotseat_drag_indicator_height"
android:layout_height="@dimen/hotseat_drag_indicator_height"
android:src="@drawable/ic_drag_indicator"
android:tint="?attr/workspaceTextColor"
android:scaleType="centerInside"
android:layout_gravity="top|center_horizontal"/>
<com.android.launcher3.CellLayout
android:id="@+id/layout"
android:layout_width="wrap_content"
+7 -12
View File
@@ -44,18 +44,6 @@
layout="@layout/overview_panel"
android:visibility="gone" />
<com.android.launcher3.views.AllAppsScrim
android:id="@+id/all_apps_scrim"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- DO NOT CHANGE THE ID -->
<include
android:id="@+id/hotseat"
layout="@layout/hotseat"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Keep these behind the workspace so that they are not visible when
we go into AllApps -->
<com.android.launcher3.pageindicators.WorkspacePageIndicator
@@ -74,6 +62,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
<!-- DO NOT CHANGE THE ID -->
<include
android:id="@+id/hotseat"
layout="@layout/hotseat"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.launcher3.dragndrop.DragLayer>
</com.android.launcher3.LauncherRootView>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,10 +14,14 @@
limitations under the License.
-->
<com.android.launcher3.graphics.GradientView
<com.android.launcher3.views.HighlightableListView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:id="@+id/gradient_bg"
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
launcher:layout_ignoreInsets="true" />
android:cacheColorHint="@android:color/transparent"
android:clipToPadding="false"
android:drawSelectorOnTop="false"
android:orientation="vertical"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:scrollbarStyle="outsideOverlay" />
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.uioverrides.OptionsPopupView
<com.android.launcher3.views.OptionsPopupView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -94,4 +94,4 @@
</FrameLayout>
</com.android.launcher3.uioverrides.OptionsPopupView>
</com.android.launcher3.views.OptionsPopupView>
+3 -57
View File
@@ -14,61 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.uioverrides.OverviewPanel
<Space
xmlns:android="http://schemas.android.com/apk/res/android"
android:theme="@style/HomeScreenElementTheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:gravity="top"
android:orientation="horizontal">
<TextView
android:id="@+id/wallpaper_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawablePadding="4dp"
android:drawableTop="@drawable/ic_wallpaper"
android:drawableTint="?attr/workspaceTextColor"
android:fontFamily="sans-serif-condensed"
android:gravity="center_horizontal"
android:stateListAnimator="@animator/overview_button_anim"
android:text="@string/wallpaper_button_text"
android:textAllCaps="true"
android:textColor="?attr/workspaceTextColor"
android:textSize="12sp" />
<TextView
android:id="@+id/widget_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawablePadding="4dp"
android:drawableTop="@drawable/ic_widget"
android:drawableTint="?attr/workspaceTextColor"
android:fontFamily="sans-serif-condensed"
android:gravity="center_horizontal"
android:stateListAnimator="@animator/overview_button_anim"
android:text="@string/widget_button_text"
android:textAllCaps="true"
android:textColor="?attr/workspaceTextColor"
android:textSize="12sp" />
<TextView
android:id="@+id/settings_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawablePadding="4dp"
android:drawableTop="@drawable/ic_setting"
android:drawableTint="?attr/workspaceTextColor"
android:fontFamily="sans-serif-condensed"
android:gravity="center_horizontal"
android:stateListAnimator="@animator/overview_button_anim"
android:text="@string/settings_button_text"
android:textAllCaps="true"
android:textColor="?attr/workspaceTextColor"
android:textSize="12sp" />
</com.android.launcher3.uioverrides.OverviewPanel>
android:layout_width="0dp"
android:layout_height="0dp" />
+20 -50
View File
@@ -17,53 +17,23 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@id/search_container_all_apps"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_height"
android:layout_gravity="center|top"
android:layout_marginBottom="-8dp"
android:gravity="center|bottom"
android:paddingLeft="@dimen/dynamic_grid_edge_margin"
android:paddingRight="@dimen/dynamic_grid_edge_margin"
android:saveEnabled="false" >
<!--
Note: The following relation should always be true so that the shadows are aligned properly
search_box_input.layout_marginBottom
== search_divider.layout_marginBottom
== - (search_container.layout_marginBottom)
>= 5dp
-->
<com.android.launcher3.ExtendedEditText
android:id="@+id/search_box_input"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_field_height"
android:layout_gravity="bottom"
android:layout_marginBottom="8dp"
android:background="@android:color/transparent"
android:focusableInTouchMode="true"
android:gravity="center"
android:hint="@string/all_apps_search_bar_hint"
android:imeOptions="actionSearch|flagNoExtractUi"
android:inputType="text|textNoSuggestions|textCapWords"
android:maxLines="1"
android:saveEnabled="false"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textColorHint="@drawable/all_apps_search_hint"
android:textSize="16sp" />
<!-- This needs to be a container with a view, to simulate a scrolling effect for the header.
We translate the header down, and its content up by the same amount, so that it gets
clipped by the parent, making it look like the divider was scrolled out of the view. -->
<FrameLayout
android:id="@+id/search_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:layout_marginBottom="8dp" >
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@drawable/all_apps_search_divider"/>
</FrameLayout>
</com.android.launcher3.allapps.search.AppsSearchContainerLayout>
android:layout_height="@dimen/all_apps_search_bar_field_height"
android:layout_centerHorizontal="true"
android:layout_gravity="top|center_horizontal"
android:layout_marginTop="8dp"
android:background="@drawable/bg_all_apps_searchbox"
android:elevation="1dp"
android:focusableInTouchMode="true"
android:gravity="center"
android:hint="@string/all_apps_search_bar_hint"
android:imeOptions="actionSearch|flagNoExtractUi"
android:inputType="text|textNoSuggestions|textCapWords"
android:maxLines="1"
android:padding="8dp"
android:saveEnabled="false"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textColorHint="@drawable/all_apps_search_hint"
android:textSize="16sp"
android:translationY="24dp" />
-1
View File
@@ -34,7 +34,6 @@
android:fontFamily="sans-serif"
launcher:iconDisplay="shortcut_popup"
launcher:layoutHorizontal="true"
launcher:deferShadowGeneration="true"
android:focusable="false" />
<View
+5 -17
View File
@@ -20,11 +20,6 @@
android:orientation="vertical"
android:theme="?attr/widgetsTheme" >
<com.android.launcher3.graphics.GradientView
android:id="@+id/gradient_bg"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.android.launcher3.views.TopRoundedCornerView
android:id="@+id/container"
android:layout_width="match_parent"
@@ -36,29 +31,22 @@
android:id="@+id/widgets_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
/>
android:clipToPadding="false" />
<!-- Fast scroller popup -->
<TextView
android:id="@+id/fast_scroller_popup"
style="@style/FastScrollerPopup"
android:layout_gravity="top|end"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
<com.android.launcher3.views.RecyclerViewFastScroller
android:id="@+id/fast_scroller"
android:layout_width="@dimen/fastscroll_width"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="@dimen/fastscroll_end_margin" />
<View
android:id="@+id/nav_bar_bg"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="bottom"
android:background="?attr/allAppsNavBarScrimColor"
android:focusable="false" />
</com.android.launcher3.views.TopRoundedCornerView>
</com.android.launcher3.widget.WidgetsFullSheet>
-2
View File
@@ -31,7 +31,6 @@
android:layout_height="@dimen/widget_section_height"
android:background="?android:attr/colorPrimary"
android:drawablePadding="@dimen/widget_section_horizontal_padding"
android:ellipsize="end"
android:focusable="true"
android:gravity="start|center_vertical"
android:paddingBottom="@dimen/widget_section_vertical_padding"
@@ -42,7 +41,6 @@
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:textAlignment="viewStart"
launcher:deferShadowGeneration="true"
launcher:iconDisplay="widget_section"
launcher:iconSizeOverride="@dimen/widget_section_icon_size"
launcher:layoutHorizontal="true" />
@@ -20,6 +20,7 @@
android:layout_gravity="bottom"
android:background="?android:attr/colorAccent"
android:elevation="2dp"
android:focusable="true"
android:orientation="horizontal">
<ImageView
@@ -42,6 +43,7 @@
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:layout_gravity="right"
android:contentDescription="@string/bottom_work_tab_user_education_close_button"
android:src="@drawable/ic_close"/>
<TextView
+1
View File
@@ -17,6 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_bottom_padding"
android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
android:paddingRight="@dimen/dynamic_grid_cell_padding_x"
+11 -8
View File
@@ -40,9 +40,15 @@
<string name="all_apps_no_search_results" msgid="3200346862396363786">"Kon geen programme kry wat by \"<xliff:g id="QUERY">%1$s</xliff:g>\" pas nie"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Soek meer programme"</string>
<string name="notifications_header" msgid="1404149926117359025">"Kennisgewings"</string>
<!-- no translation found for long_press_shortcut_to_add (4524750017792716791) -->
<skip />
<!-- no translation found for long_accessible_way_to_add_shortcut (3327314059613154633) -->
<skip />
<string name="out_of_space" msgid="4691004494942118364">"Niks meer spasie op die tuisskerm nie."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Geen plek meer in die Gunstelinge-laai nie"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Programmelys"</string>
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"Lys persoonlike programme"</string>
<string name="all_apps_button_work_label" msgid="7270707118948892488">"Lys werkprogramme"</string>
<string name="all_apps_home_button_label" msgid="252062713717058851">"Tuis"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"Verwyder"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deïnstalleer"</string>
@@ -77,19 +83,17 @@
<string name="wallpaper_button_text" msgid="8404103075899945851">"Muurpapiere"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Home-instellings"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Gedeaktiveer deur jou administrateur"</string>
<string name="accessibility_action_overview" msgid="6257665857640347026">"Oorsig"</string>
<string name="allow_rotation_title" msgid="7728578836261442095">"Laat toe dat tuisskerm gedraai word"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Wanneer foon gedraai word"</string>
<string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Huidige vertooninstelling laat nie rotasie toe nie"</string>
<string name="icon_badging_title" msgid="874121399231955394">"Kennisgewingkolle"</string>
<string name="icon_badging_desc_on" msgid="2627952638544674079">"Aan"</string>
<string name="icon_badging_desc_off" msgid="5503319969924580241">"Af"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"Kennisgewingtoegang word benodig"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"Skakel programkennisgewings vir <xliff:g id="NAME">%1$s</xliff:g> aan om kennisgewingkolle te sien"</string>
<string name="title_change_settings" msgid="1376365968844349552">"Verander instellings"</string>
<string name="icon_badging_service_title" msgid="2309733118428242174">"Wys kennisgewingkolle"</string>
<string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Voeg ikoon by tuisskerm"</string>
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Vir nuwe programme"</string>
<string name="icon_shape_override_label" msgid="2977264953998281004">"Verander ikoon se vorm"</string>
<string name="icon_shape_override_label_location" msgid="3841607380657692863">"op tuisskerm"</string>
<string name="icon_shape_system_default" msgid="1709762974822753030">"Gebruik stelselverstek"</string>
<string name="icon_shape_square" msgid="633575066111622774">"Vierkant"</string>
<string name="icon_shape_squircle" msgid="5658049910802669495">"Sirkelvierkant"</string>
@@ -119,9 +123,6 @@
<string name="create_folder_with" msgid="4050141361160214248">"Skep vouer met: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_created" msgid="6409794597405184510">"Vouer geskep"</string>
<string name="action_move_to_workspace" msgid="1603837886334246317">"Skuif na tuisskerm"</string>
<string name="action_move_screen_left" msgid="8854216831569401665">"Skuif skerm na links"</string>
<string name="action_move_screen_right" msgid="329334910274311123">"Skuif skerm na regs"</string>
<string name="screen_moved" msgid="266230079505650577">"Skerm is geskuif"</string>
<string name="action_resize" msgid="1802976324781771067">"Verander grootte"</string>
<string name="action_increase_width" msgid="8773715375078513326">"Vermeerder breedte"</string>
<string name="action_increase_height" msgid="459390020612501122">"Vermeerder hoogte"</string>
@@ -137,7 +138,9 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"Werk"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Werkprofiel"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Kry werkprogramme hier"</string>
<string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"Elke werkprogram het \'n oranje kenteken en word deur jou organisasie veilig gehou. Skuif programme na jou Tuisskerm vir makliker toegang."</string>
<string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Elke werkprogram het \'n kenteken en word deur jou organisasie veilig gehou. Skuif programme na jou tuisskerm toe vir makliker toegang."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Bestuur deur jou organisasie"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Kennisgewings en programme is af"</string>
<string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"Maak toe"</string>
<string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"Toe"</string>
</resources>
+2 -1
View File
@@ -137,7 +137,8 @@
<string name="all_apps_work_tab" msgid="4884822796154055118">"ሥራ"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"የሥራ መገለጫ"</string>
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"የስራ መተግበሪያዎችን እዚህ ያግኙ"</string>
<string name="bottom_work_tab_user_education_body" msgid="1485375451542813426">"እያንዳንዱ የሥራ መተግበሪያ ብርቱካናማ ባጅ አለው እና በእርስዎ ድርጅት በኩል ደህንነቱ ተጠብቋል። ለቀለለ መዳረሻ መተግበሪያዎችን ወደ የእርስዎ መነሻ ማያ ገጽ ያንቀሳቅሱ።"</string>
<!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
<skip />
<string name="work_mode_on_label" msgid="4781128097185272916">"በእርስዎ ድርጅት የሚተዳደር"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"ማሳወቂያዎች እና መተግበሪያዎች ጠፍተዋል"</string>
</resources>

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