Merge branch 'ub-launcher3-master' into pi-dev
am: 6303a794df
Change-Id: I66f6a79666134e1d8398ea5d726ba45ae184ce49
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.** {
|
||||
*;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 563 B |
|
Before Width: | Height: | Size: 527 B |
|
Before Width: | Height: | Size: 426 B |
|
Before Width: | Height: | Size: 433 B |
|
Before Width: | Height: | Size: 648 B |
|
Before Width: | Height: | Size: 631 B |
|
Before Width: | Height: | Size: 798 B |
|
Before Width: | Height: | Size: 787 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
@@ -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" />
|
||||
@@ -34,7 +34,6 @@
|
||||
android:fontFamily="sans-serif"
|
||||
launcher:iconDisplay="shortcut_popup"
|
||||
launcher:layoutHorizontal="true"
|
||||
launcher:deferShadowGeneration="true"
|
||||
android:focusable="false" />
|
||||
|
||||
<View
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||