android10_Launcher3_original
This commit is contained in:
141
quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
Normal file
141
quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.Utilities.SINGLE_FRAME_MS;
|
||||
import static com.android.launcher3.Utilities.postAsyncCallback;
|
||||
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 com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
|
||||
import androidx.annotation.BinderThread;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
|
||||
|
||||
private final Handler mHandler;
|
||||
private final boolean mStartAtFrontOfQueue;
|
||||
private AnimationResult mAnimationResult;
|
||||
|
||||
/**
|
||||
* @param startAtFrontOfQueue If true, the animation start will be posted at the front of the
|
||||
* queue to minimize latency.
|
||||
*/
|
||||
public LauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue) {
|
||||
mHandler = handler;
|
||||
mStartAtFrontOfQueue = startAtFrontOfQueue;
|
||||
}
|
||||
|
||||
@BinderThread
|
||||
@Override
|
||||
public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats, Runnable runnable) {
|
||||
Runnable r = () -> {
|
||||
finishExistingAnimation();
|
||||
mAnimationResult = new AnimationResult(runnable);
|
||||
onCreateAnimation(targetCompats, mAnimationResult);
|
||||
};
|
||||
if (mStartAtFrontOfQueue) {
|
||||
postAtFrontOfQueueAsynchronously(mHandler, r);
|
||||
} else {
|
||||
postAsyncCallback(mHandler, r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on the UI thread when the animation targets are received. The implementation must
|
||||
* call {@link AnimationResult#setAnimation(AnimatorSet)} with the target animation to be run.
|
||||
*/
|
||||
@UiThread
|
||||
public abstract void onCreateAnimation(
|
||||
RemoteAnimationTargetCompat[] targetCompats, AnimationResult result);
|
||||
|
||||
@UiThread
|
||||
private void finishExistingAnimation() {
|
||||
if (mAnimationResult != null) {
|
||||
mAnimationResult.finish();
|
||||
mAnimationResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system
|
||||
*/
|
||||
@BinderThread
|
||||
@Override
|
||||
public void onAnimationCancelled() {
|
||||
postAsyncCallback(mHandler, this::finishExistingAnimation);
|
||||
}
|
||||
|
||||
public static final class AnimationResult {
|
||||
|
||||
private final Runnable mFinishRunnable;
|
||||
|
||||
private AnimatorSet mAnimator;
|
||||
private boolean mFinished = false;
|
||||
private boolean mInitialized = false;
|
||||
|
||||
private AnimationResult(Runnable finishRunnable) {
|
||||
mFinishRunnable = finishRunnable;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void finish() {
|
||||
if (!mFinished) {
|
||||
mFinishRunnable.run();
|
||||
mFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void setAnimation(AnimatorSet animation) {
|
||||
if (mInitialized) {
|
||||
throw new IllegalStateException("Animation already initialized");
|
||||
}
|
||||
mInitialized = true;
|
||||
mAnimator = animation;
|
||||
if (mAnimator == null) {
|
||||
finish();
|
||||
} else if (mFinished) {
|
||||
// Animation callback was already finished, skip the animation.
|
||||
mAnimator.start();
|
||||
mAnimator.end();
|
||||
} else {
|
||||
// Start the animation
|
||||
mAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
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(SINGLE_FRAME_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Handler;
|
||||
|
||||
import com.android.launcher3.states.InternalStateHandler;
|
||||
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
|
||||
import com.android.quickstep.OverviewCallbacks;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public class LauncherInitListener extends InternalStateHandler implements ActivityInitListener {
|
||||
|
||||
private final BiPredicate<Launcher, Boolean> mOnInitListener;
|
||||
|
||||
private RemoteAnimationProvider mRemoteAnimationProvider;
|
||||
|
||||
public LauncherInitListener(BiPredicate<Launcher, Boolean> onInitListener) {
|
||||
mOnInitListener = onInitListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean init(Launcher launcher, boolean alreadyOnHome) {
|
||||
if (mRemoteAnimationProvider != null) {
|
||||
QuickstepAppTransitionManagerImpl appTransitionManager =
|
||||
(QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
|
||||
|
||||
// Set a one-time animation provider. After the first call, this will get cleared.
|
||||
// TODO: Probably also check the intended target id.
|
||||
CancellationSignal cancellationSignal = new CancellationSignal();
|
||||
appTransitionManager.setRemoteAnimationProvider((targets) -> {
|
||||
|
||||
// On the first call clear the reference.
|
||||
cancellationSignal.cancel();
|
||||
RemoteAnimationProvider provider = mRemoteAnimationProvider;
|
||||
mRemoteAnimationProvider = null;
|
||||
|
||||
if (provider != null && launcher.getStateManager().getState().overviewUi) {
|
||||
return provider.createWindowAnimation(targets);
|
||||
}
|
||||
return null;
|
||||
}, cancellationSignal);
|
||||
}
|
||||
OverviewCallbacks.get(launcher).onInitOverviewTransition();
|
||||
return mOnInitListener.test(launcher, alreadyOnHome);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register() {
|
||||
initWhenReady();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
mRemoteAnimationProvider = null;
|
||||
clearReference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
|
||||
Context context, Handler handler, long duration) {
|
||||
mRemoteAnimationProvider = animProvider;
|
||||
|
||||
register();
|
||||
|
||||
Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
|
||||
context.startActivity(addToIntent(new Intent((intent))), options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,828 @@
|
||||
/*
|
||||
* 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.BaseActivity.INVISIBLE_ALL;
|
||||
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
|
||||
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
|
||||
import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
|
||||
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.Utilities.postAsyncCallback;
|
||||
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
|
||||
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
|
||||
import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
|
||||
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
|
||||
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
|
||||
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
|
||||
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityOptions;
|
||||
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.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutView;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
|
||||
import com.android.launcher3.views.FloatingIconView;
|
||||
import com.android.quickstep.util.MultiValueUpdateListener;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
import com.android.quickstep.util.RemoteAnimationTargetSet;
|
||||
import com.android.systemui.shared.system.ActivityCompat;
|
||||
import com.android.systemui.shared.system.ActivityOptionsCompat;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
|
||||
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
|
||||
import com.android.systemui.shared.system.WindowManagerWrapper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
|
||||
* home and/or all-apps.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTransitionManager
|
||||
implements OnDeviceProfileChangeListener {
|
||||
|
||||
private static final String TAG = "QuickstepTransition";
|
||||
|
||||
/** Duration of status bar animations. */
|
||||
public static final int STATUS_BAR_TRANSITION_DURATION = 120;
|
||||
|
||||
/**
|
||||
* Since our animations decelerate heavily when finishing, we want to start status bar animations
|
||||
* x ms before the ending.
|
||||
*/
|
||||
public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96;
|
||||
|
||||
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
|
||||
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
|
||||
|
||||
private static final long APP_LAUNCH_DURATION = 450;
|
||||
// Use a shorter duration for x or y translation to create a curve effect
|
||||
private static final long APP_LAUNCH_CURVED_DURATION = 250;
|
||||
private static final long APP_LAUNCH_ALPHA_DURATION = 50;
|
||||
private static final long APP_LAUNCH_ALPHA_START_DELAY = 25;
|
||||
|
||||
// We scale the durations for the downward app launch animations (minus the scale animation).
|
||||
private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f;
|
||||
private static final long APP_LAUNCH_DOWN_DURATION =
|
||||
(long) (APP_LAUNCH_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
|
||||
private static final long APP_LAUNCH_DOWN_CURVED_DURATION =
|
||||
(long) (APP_LAUNCH_CURVED_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
|
||||
private static final long APP_LAUNCH_ALPHA_DOWN_DURATION =
|
||||
(long) (APP_LAUNCH_ALPHA_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
|
||||
|
||||
private static final long CROP_DURATION = 375;
|
||||
private static final long RADIUS_DURATION = 375;
|
||||
|
||||
public static final int RECENTS_LAUNCH_DURATION = 336;
|
||||
private static final int LAUNCHER_RESUME_START_DELAY = 100;
|
||||
private static final int CLOSING_TRANSITION_DURATION_MS = 250;
|
||||
|
||||
protected static final int CONTENT_ALPHA_DURATION = 217;
|
||||
protected static final int CONTENT_TRANSLATION_DURATION = 350;
|
||||
|
||||
// Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
|
||||
public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
|
||||
|
||||
protected final Launcher mLauncher;
|
||||
|
||||
private final DragLayer mDragLayer;
|
||||
private final AlphaProperty mDragLayerAlpha;
|
||||
|
||||
final Handler mHandler;
|
||||
private final boolean mIsRtl;
|
||||
|
||||
private final float mContentTransY;
|
||||
private final float mWorkspaceTransY;
|
||||
private final float mClosingWindowTransY;
|
||||
|
||||
private DeviceProfile mDeviceProfile;
|
||||
|
||||
private RemoteAnimationProvider mRemoteAnimationProvider;
|
||||
|
||||
private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mLauncher.addForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mLauncher.clearForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS);
|
||||
}
|
||||
};
|
||||
|
||||
public QuickstepAppTransitionManagerImpl(Context context) {
|
||||
mLauncher = Launcher.getLauncher(context);
|
||||
mDragLayer = mLauncher.getDragLayer();
|
||||
mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mIsRtl = Utilities.isRtl(mLauncher.getResources());
|
||||
mDeviceProfile = mLauncher.getDeviceProfile();
|
||||
|
||||
Resources res = mLauncher.getResources();
|
||||
mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
|
||||
mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
|
||||
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
|
||||
|
||||
mLauncher.addOnDeviceProfileChangeListener(this);
|
||||
registerRemoteAnimations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceProfileChanged(DeviceProfile dp) {
|
||||
mDeviceProfile = dp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAdaptiveIconAnimation() {
|
||||
return hasControlRemoteAppTransitionPermission()
|
||||
&& FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ActivityOptions with remote animations that controls how the window of the opening
|
||||
* targets are displayed.
|
||||
*/
|
||||
@Override
|
||||
public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
|
||||
if (hasControlRemoteAppTransitionPermission()) {
|
||||
boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
|
||||
RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler,
|
||||
true /* startAtFrontOfQueue */) {
|
||||
|
||||
@Override
|
||||
public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
|
||||
AnimationResult result) {
|
||||
AnimatorSet anim = new AnimatorSet();
|
||||
|
||||
boolean launcherClosing =
|
||||
launcherIsATargetWithMode(targetCompats, MODE_CLOSING);
|
||||
|
||||
if (isLaunchingFromRecents(v, targetCompats)) {
|
||||
composeRecentsLaunchAnimator(anim, v, targetCompats, launcherClosing);
|
||||
} else {
|
||||
composeIconLaunchAnimator(anim, v, targetCompats, launcherClosing);
|
||||
}
|
||||
|
||||
if (launcherClosing) {
|
||||
anim.addListener(mForceInvisibleListener);
|
||||
}
|
||||
|
||||
result.setAnimation(anim);
|
||||
}
|
||||
};
|
||||
|
||||
// Note that this duration is a guess as we do not know if the animation will be a
|
||||
// recents launch or not for sure until we know the opening app targets.
|
||||
long duration = fromRecents
|
||||
? RECENTS_LAUNCH_DURATION
|
||||
: APP_LAUNCH_DURATION;
|
||||
|
||||
long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
|
||||
- STATUS_BAR_TRANSITION_PRE_DELAY;
|
||||
return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
|
||||
runner, duration, statusBarTransitionDelay));
|
||||
}
|
||||
return super.getActivityLaunchOptions(launcher, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the launch is a recents app transition and we should do a launch animation
|
||||
* from the recents view. Note that if the remote animation targets are not provided, this
|
||||
* may not always be correct as we may resolve the opening app to a task when the animation
|
||||
* starts.
|
||||
*
|
||||
* @param v the view to launch from
|
||||
* @param targets apps that are opening/closing
|
||||
* @return true if the app is launching from recents, false if it most likely is not
|
||||
*/
|
||||
protected abstract boolean isLaunchingFromRecents(@NonNull View v,
|
||||
@Nullable RemoteAnimationTargetCompat[] targets);
|
||||
|
||||
/**
|
||||
* Composes the animations for a launch from the recents list.
|
||||
*
|
||||
* @param anim the animator set to add to
|
||||
* @param v the launching view
|
||||
* @param targets the apps that are opening/closing
|
||||
* @param launcherClosing true if the launcher app is closing
|
||||
*/
|
||||
protected abstract void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
|
||||
@NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing);
|
||||
|
||||
/**
|
||||
* Compose the animations for a launch from the app icon.
|
||||
*
|
||||
* @param anim the animation to add to
|
||||
* @param v the launching view with the icon
|
||||
* @param targets the list of opening/closing apps
|
||||
* @param launcherClosing true if launcher is closing
|
||||
*/
|
||||
private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
|
||||
@NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
|
||||
// Set the state animation first so that any state listeners are called
|
||||
// before our internal listeners.
|
||||
mLauncher.getStateManager().setCurrentAnimation(anim);
|
||||
|
||||
Rect windowTargetBounds = getWindowTargetBounds(targets);
|
||||
boolean isAllOpeningTargetTrs = true;
|
||||
for (int i = 0; i < targets.length; i++) {
|
||||
RemoteAnimationTargetCompat target = targets[i];
|
||||
if (target.mode == MODE_OPENING) {
|
||||
isAllOpeningTargetTrs &= target.isTranslucent;
|
||||
}
|
||||
if (!isAllOpeningTargetTrs) break;
|
||||
}
|
||||
anim.play(getOpeningWindowAnimators(v, targets, windowTargetBounds,
|
||||
!isAllOpeningTargetTrs));
|
||||
if (launcherClosing) {
|
||||
Pair<AnimatorSet, Runnable> launcherContentAnimator =
|
||||
getLauncherContentAnimator(true /* isAppOpening */,
|
||||
new float[] {0, -mContentTransY});
|
||||
anim.play(launcherContentAnimator.first);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
launcherContentAnimator.second.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the window bounds of the opening target.
|
||||
* In multiwindow mode, we need to get the final size of the opening app window target to help
|
||||
* figure out where the floating view should animate to.
|
||||
*/
|
||||
private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] targets) {
|
||||
Rect bounds = new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
|
||||
if (mLauncher.isInMultiWindowMode()) {
|
||||
for (RemoteAnimationTargetCompat target : targets) {
|
||||
if (target.mode == MODE_OPENING) {
|
||||
bounds.set(target.sourceContainerBounds);
|
||||
bounds.offsetTo(target.position.x, target.position.y);
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider,
|
||||
CancellationSignal cancellationSignal) {
|
||||
mRemoteAnimationProvider = animationProvider;
|
||||
cancellationSignal.setOnCancelListener(() -> {
|
||||
if (animationProvider == mRemoteAnimationProvider) {
|
||||
mRemoteAnimationProvider = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Content is everything on screen except the background and the floating view (if any).
|
||||
*
|
||||
* @param isAppOpening True when this is called when an app is opening.
|
||||
* False when this is called when an app is closing.
|
||||
* @param trans Array that contains the start and end translation values for the content.
|
||||
*/
|
||||
private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
|
||||
float[] trans) {
|
||||
AnimatorSet launcherAnimator = new AnimatorSet();
|
||||
Runnable endListener;
|
||||
|
||||
float[] alphas = isAppOpening
|
||||
? new float[] {1, 0}
|
||||
: new float[] {0, 1};
|
||||
|
||||
if (mLauncher.isInState(ALL_APPS)) {
|
||||
// All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
|
||||
final View appsView = mLauncher.getAppsView();
|
||||
final float startAlpha = appsView.getAlpha();
|
||||
final float startY = appsView.getTranslationY();
|
||||
appsView.setAlpha(alphas[0]);
|
||||
appsView.setTranslationY(trans[0]);
|
||||
|
||||
ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
|
||||
alpha.setDuration(CONTENT_ALPHA_DURATION);
|
||||
alpha.setInterpolator(LINEAR);
|
||||
appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
alpha.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
appsView.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
});
|
||||
ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans);
|
||||
transY.setInterpolator(AGGRESSIVE_EASE);
|
||||
transY.setDuration(CONTENT_TRANSLATION_DURATION);
|
||||
|
||||
launcherAnimator.play(alpha);
|
||||
launcherAnimator.play(transY);
|
||||
|
||||
endListener = () -> {
|
||||
appsView.setAlpha(startAlpha);
|
||||
appsView.setTranslationY(startY);
|
||||
appsView.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
};
|
||||
} else if (mLauncher.isInState(OVERVIEW)) {
|
||||
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
|
||||
launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
|
||||
allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN));
|
||||
endListener = composeViewContentAnimator(launcherAnimator, alphas, trans);
|
||||
} else {
|
||||
mDragLayerAlpha.setValue(alphas[0]);
|
||||
ObjectAnimator alpha =
|
||||
ObjectAnimator.ofFloat(mDragLayerAlpha, MultiValueAlpha.VALUE, alphas);
|
||||
alpha.setDuration(CONTENT_ALPHA_DURATION);
|
||||
alpha.setInterpolator(LINEAR);
|
||||
launcherAnimator.play(alpha);
|
||||
|
||||
mDragLayer.setTranslationY(trans[0]);
|
||||
ObjectAnimator transY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, trans);
|
||||
transY.setInterpolator(AGGRESSIVE_EASE);
|
||||
transY.setDuration(CONTENT_TRANSLATION_DURATION);
|
||||
launcherAnimator.play(transY);
|
||||
|
||||
mDragLayer.getScrim().hideSysUiScrim(true);
|
||||
// Pause page indicator animations as they lead to layer trashing.
|
||||
mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
|
||||
mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
|
||||
endListener = this::resetContentView;
|
||||
}
|
||||
return new Pair<>(launcherAnimator, endListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose recents view alpha and translation Y animation when launcher opens/closes apps.
|
||||
*
|
||||
* @param anim the animator set to add to
|
||||
* @param alphas the alphas to animate to over time
|
||||
* @param trans the translation Y values to animator to over time
|
||||
* @return listener to run when the animation ends
|
||||
*/
|
||||
protected abstract Runnable composeViewContentAnimator(@NonNull AnimatorSet anim,
|
||||
float[] alphas, float[] trans);
|
||||
|
||||
/**
|
||||
* @return Animator that controls the window of the opening targets.
|
||||
*/
|
||||
private ValueAnimator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] targets,
|
||||
Rect windowTargetBounds, boolean toggleVisibility) {
|
||||
RectF bounds = new RectF();
|
||||
FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
|
||||
toggleVisibility, bounds, true /* isOpening */);
|
||||
Rect crop = new Rect();
|
||||
Matrix matrix = new Matrix();
|
||||
|
||||
RemoteAnimationTargetSet openingTargets = new RemoteAnimationTargetSet(targets,
|
||||
MODE_OPENING);
|
||||
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
|
||||
new SyncRtSurfaceTransactionApplierCompat(floatingView);
|
||||
openingTargets.addDependentTransactionApplier(surfaceApplier);
|
||||
|
||||
// Scale the app icon to take up the entire screen. This simplifies the math when
|
||||
// animating the app window position / scale.
|
||||
float smallestSize = Math.min(windowTargetBounds.height(), windowTargetBounds.width());
|
||||
float maxScaleX = smallestSize / bounds.width();
|
||||
float maxScaleY = smallestSize / bounds.height();
|
||||
float scale = Math.max(maxScaleX, maxScaleY);
|
||||
float startScale = 1f;
|
||||
if (v instanceof BubbleTextView && !(v.getParent() instanceof DeepShortcutView)) {
|
||||
Drawable dr = ((BubbleTextView) v).getIcon();
|
||||
if (dr instanceof FastBitmapDrawable) {
|
||||
startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
|
||||
}
|
||||
}
|
||||
final float initialStartScale = startScale;
|
||||
|
||||
int[] dragLayerBounds = new int[2];
|
||||
mDragLayer.getLocationOnScreen(dragLayerBounds);
|
||||
|
||||
// Animate the app icon to the center of the window bounds in screen coordinates.
|
||||
float centerX = windowTargetBounds.centerX() - dragLayerBounds[0];
|
||||
float centerY = windowTargetBounds.centerY() - dragLayerBounds[1];
|
||||
|
||||
float dX = centerX - bounds.centerX();
|
||||
float dY = centerY - bounds.centerY();
|
||||
|
||||
boolean useUpwardAnimation = bounds.top > centerY
|
||||
|| Math.abs(dY) < mLauncher.getDeviceProfile().cellHeightPx;
|
||||
final long xDuration = useUpwardAnimation ? APP_LAUNCH_CURVED_DURATION
|
||||
: APP_LAUNCH_DOWN_DURATION;
|
||||
final long yDuration = useUpwardAnimation ? APP_LAUNCH_DURATION
|
||||
: APP_LAUNCH_DOWN_CURVED_DURATION;
|
||||
final long alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION
|
||||
: APP_LAUNCH_ALPHA_DOWN_DURATION;
|
||||
|
||||
RectF targetBounds = new RectF(windowTargetBounds);
|
||||
RectF currentBounds = new RectF();
|
||||
RectF temp = new RectF();
|
||||
|
||||
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
|
||||
appAnimator.setDuration(APP_LAUNCH_DURATION);
|
||||
appAnimator.setInterpolator(LINEAR);
|
||||
appAnimator.addListener(floatingView);
|
||||
appAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (v instanceof BubbleTextView) {
|
||||
((BubbleTextView) v).setStayPressed(false);
|
||||
}
|
||||
openingTargets.release();
|
||||
}
|
||||
});
|
||||
|
||||
float shapeRevealDuration = APP_LAUNCH_DURATION * SHAPE_PROGRESS_DURATION;
|
||||
|
||||
final float startCrop;
|
||||
final float endCrop;
|
||||
if (mDeviceProfile.isVerticalBarLayout()) {
|
||||
startCrop = windowTargetBounds.height();
|
||||
endCrop = windowTargetBounds.width();
|
||||
} else {
|
||||
startCrop = windowTargetBounds.width();
|
||||
endCrop = windowTargetBounds.height();
|
||||
}
|
||||
|
||||
final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources())
|
||||
? startCrop / 2f : 0f;
|
||||
final float windowRadius = mDeviceProfile.isMultiWindowMode
|
||||
? 0 : getWindowCornerRadius(mLauncher.getResources());
|
||||
appAnimator.addUpdateListener(new MultiValueUpdateListener() {
|
||||
FloatProp mDx = new FloatProp(0, dX, 0, xDuration, AGGRESSIVE_EASE);
|
||||
FloatProp mDy = new FloatProp(0, dY, 0, yDuration, AGGRESSIVE_EASE);
|
||||
FloatProp mIconScale = new FloatProp(initialStartScale, scale, 0, APP_LAUNCH_DURATION,
|
||||
EXAGGERATED_EASE);
|
||||
FloatProp mIconAlpha = new FloatProp(1f, 0f, APP_LAUNCH_ALPHA_START_DELAY,
|
||||
alphaDuration, LINEAR);
|
||||
FloatProp mCroppedSize = new FloatProp(startCrop, endCrop, 0, CROP_DURATION,
|
||||
EXAGGERATED_EASE);
|
||||
FloatProp mWindowRadius = new FloatProp(initialWindowRadius, windowRadius, 0,
|
||||
RADIUS_DURATION, EXAGGERATED_EASE);
|
||||
|
||||
@Override
|
||||
public void onUpdate(float percent) {
|
||||
// Calculate app icon size.
|
||||
float iconWidth = bounds.width() * mIconScale.value;
|
||||
float iconHeight = bounds.height() * mIconScale.value;
|
||||
|
||||
// Animate the window crop so that it starts off as a square.
|
||||
final int windowWidth;
|
||||
final int windowHeight;
|
||||
if (mDeviceProfile.isVerticalBarLayout()) {
|
||||
windowWidth = (int) mCroppedSize.value;
|
||||
windowHeight = windowTargetBounds.height();
|
||||
} else {
|
||||
windowWidth = windowTargetBounds.width();
|
||||
windowHeight = (int) mCroppedSize.value;
|
||||
}
|
||||
crop.set(0, 0, windowWidth, windowHeight);
|
||||
|
||||
// Scale the app window to match the icon size.
|
||||
float scaleX = iconWidth / windowWidth;
|
||||
float scaleY = iconHeight / windowHeight;
|
||||
float scale = Math.min(1f, Math.max(scaleX, scaleY));
|
||||
|
||||
float scaledWindowWidth = windowWidth * scale;
|
||||
float scaledWindowHeight = windowHeight * scale;
|
||||
|
||||
float offsetX = (scaledWindowWidth - iconWidth) / 2;
|
||||
float offsetY = (scaledWindowHeight - iconHeight) / 2;
|
||||
|
||||
// Calculate the window position
|
||||
temp.set(bounds);
|
||||
temp.offset(dragLayerBounds[0], dragLayerBounds[1]);
|
||||
temp.offset(mDx.value, mDy.value);
|
||||
Utilities.scaleRectFAboutCenter(temp, mIconScale.value);
|
||||
float transX0 = temp.left - offsetX;
|
||||
float transY0 = temp.top - offsetY;
|
||||
|
||||
float croppedHeight = (windowTargetBounds.height() - crop.height()) * scale;
|
||||
float croppedWidth = (windowTargetBounds.width() - crop.width()) * scale;
|
||||
SurfaceParams[] params = new SurfaceParams[targets.length];
|
||||
for (int i = targets.length - 1; i >= 0; i--) {
|
||||
RemoteAnimationTargetCompat target = targets[i];
|
||||
Rect targetCrop;
|
||||
final float alpha;
|
||||
final float cornerRadius;
|
||||
if (target.mode == MODE_OPENING) {
|
||||
matrix.setScale(scale, scale);
|
||||
matrix.postTranslate(transX0, transY0);
|
||||
targetCrop = crop;
|
||||
alpha = 1f - mIconAlpha.value;
|
||||
cornerRadius = mWindowRadius.value;
|
||||
matrix.mapRect(currentBounds, targetBounds);
|
||||
if (mDeviceProfile.isVerticalBarLayout()) {
|
||||
currentBounds.right -= croppedWidth;
|
||||
} else {
|
||||
currentBounds.bottom -= croppedHeight;
|
||||
}
|
||||
floatingView.update(currentBounds, mIconAlpha.value, percent, 0f,
|
||||
cornerRadius * scale, true /* isOpening */);
|
||||
} else {
|
||||
matrix.setTranslate(target.position.x, target.position.y);
|
||||
targetCrop = target.sourceContainerBounds;
|
||||
alpha = 1f;
|
||||
cornerRadius = 0;
|
||||
}
|
||||
|
||||
params[i] = new SurfaceParams(target.leash, alpha, matrix, targetCrop,
|
||||
RemoteAnimationProvider.getLayer(target, MODE_OPENING),
|
||||
cornerRadius);
|
||||
}
|
||||
surfaceApplier.scheduleApply(params);
|
||||
}
|
||||
});
|
||||
return appAnimator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers remote animations used when closing apps to home screen.
|
||||
*/
|
||||
private void registerRemoteAnimations() {
|
||||
// Unregister this
|
||||
if (hasControlRemoteAppTransitionPermission()) {
|
||||
RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
|
||||
definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
|
||||
WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
|
||||
new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(false /* fromUnlock */),
|
||||
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
|
||||
new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
|
||||
return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Runner that plays when user goes to Launcher
|
||||
* ie. pressing home, swiping up from nav bar.
|
||||
*/
|
||||
RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
|
||||
return new WallpaperOpenLauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */,
|
||||
fromUnlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animator that controls the transformations of the windows when unlocking the device.
|
||||
*/
|
||||
private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] targets) {
|
||||
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
|
||||
new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
|
||||
ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
|
||||
unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
|
||||
float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
|
||||
QuickStepContract.getWindowCornerRadius(mLauncher.getResources());
|
||||
unlockAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
SurfaceParams[] params = new SurfaceParams[targets.length];
|
||||
for (int i = targets.length - 1; i >= 0; i--) {
|
||||
RemoteAnimationTargetCompat target = targets[i];
|
||||
params[i] = new SurfaceParams(target.leash, 1f, null,
|
||||
target.sourceContainerBounds,
|
||||
RemoteAnimationProvider.getLayer(target, MODE_OPENING), cornerRadius);
|
||||
}
|
||||
surfaceApplier.scheduleApply(params);
|
||||
}
|
||||
});
|
||||
return unlockAnimator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animator that controls the transformations of the windows the targets that are closing.
|
||||
*/
|
||||
private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) {
|
||||
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
|
||||
new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
|
||||
Matrix matrix = new Matrix();
|
||||
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
|
||||
int duration = CLOSING_TRANSITION_DURATION_MS;
|
||||
float windowCornerRadius = mDeviceProfile.isMultiWindowMode
|
||||
? 0 : getWindowCornerRadius(mLauncher.getResources());
|
||||
closingAnimator.setDuration(duration);
|
||||
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
|
||||
FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
|
||||
FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7);
|
||||
FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
|
||||
|
||||
@Override
|
||||
public void onUpdate(float percent) {
|
||||
SurfaceParams[] params = new SurfaceParams[targets.length];
|
||||
for (int i = targets.length - 1; i >= 0; i--) {
|
||||
RemoteAnimationTargetCompat target = targets[i];
|
||||
final float alpha;
|
||||
final float cornerRadius;
|
||||
if (target.mode == MODE_CLOSING) {
|
||||
matrix.setScale(mScale.value, mScale.value,
|
||||
target.sourceContainerBounds.centerX(),
|
||||
target.sourceContainerBounds.centerY());
|
||||
matrix.postTranslate(0, mDy.value);
|
||||
matrix.postTranslate(target.position.x, target.position.y);
|
||||
alpha = mAlpha.value;
|
||||
cornerRadius = windowCornerRadius;
|
||||
} else {
|
||||
matrix.setTranslate(target.position.x, target.position.y);
|
||||
alpha = 1f;
|
||||
cornerRadius = 0f;
|
||||
}
|
||||
params[i] = new SurfaceParams(target.leash, alpha, matrix,
|
||||
target.sourceContainerBounds,
|
||||
RemoteAnimationProvider.getLayer(target, MODE_CLOSING),
|
||||
cornerRadius);
|
||||
}
|
||||
surfaceApplier.scheduleApply(params);
|
||||
}
|
||||
});
|
||||
|
||||
return closingAnimator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an animator that modifies Launcher as a result from {@link #getWallpaperOpenRunner}.
|
||||
*/
|
||||
private void createLauncherResumeAnimation(AnimatorSet anim) {
|
||||
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
|
||||
Pair<AnimatorSet, Runnable> contentAnimator =
|
||||
getLauncherContentAnimator(false /* isAppOpening */,
|
||||
new float[] {-mContentTransY, 0});
|
||||
contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
|
||||
anim.play(contentAnimator.first);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
contentAnimator.second.run();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
AnimatorSet workspaceAnimator = new AnimatorSet();
|
||||
|
||||
mDragLayer.setTranslationY(-mWorkspaceTransY);;
|
||||
workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
|
||||
-mWorkspaceTransY, 0));
|
||||
|
||||
mDragLayerAlpha.setValue(0);
|
||||
workspaceAnimator.play(ObjectAnimator.ofFloat(
|
||||
mDragLayerAlpha, MultiValueAlpha.VALUE, 0, 1f));
|
||||
|
||||
workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
|
||||
workspaceAnimator.setDuration(333);
|
||||
workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
|
||||
|
||||
mDragLayer.getScrim().hideSysUiScrim(true);
|
||||
|
||||
// Pause page indicator animations as they lead to layer trashing.
|
||||
mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
|
||||
mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
|
||||
workspaceAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
resetContentView();
|
||||
}
|
||||
});
|
||||
anim.play(workspaceAnimator);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetContentView() {
|
||||
mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
|
||||
mDragLayerAlpha.setValue(1f);
|
||||
mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
mDragLayer.setTranslationY(0f);
|
||||
mDragLayer.getScrim().hideSysUiScrim(false);
|
||||
}
|
||||
|
||||
private boolean hasControlRemoteAppTransitionPermission() {
|
||||
return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote animation runner for animation from the app to Launcher, including recents.
|
||||
*/
|
||||
class WallpaperOpenLauncherAnimationRunner extends LauncherAnimationRunner {
|
||||
private final boolean mFromUnlock;
|
||||
|
||||
public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue,
|
||||
boolean fromUnlock) {
|
||||
super(handler, startAtFrontOfQueue);
|
||||
mFromUnlock = fromUnlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
|
||||
LauncherAnimationRunner.AnimationResult result) {
|
||||
if (!mLauncher.hasBeenResumed()) {
|
||||
// If launcher is not resumed, wait until new async-frame after resume
|
||||
mLauncher.addOnResumeCallback(() ->
|
||||
postAsyncCallback(mHandler, () ->
|
||||
onCreateAnimation(targetCompats, result)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) {
|
||||
mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS);
|
||||
mLauncher.getStateManager().moveToRestState();
|
||||
}
|
||||
|
||||
AnimatorSet anim = null;
|
||||
RemoteAnimationProvider provider = mRemoteAnimationProvider;
|
||||
if (provider != null) {
|
||||
anim = provider.createWindowAnimation(targetCompats);
|
||||
}
|
||||
|
||||
if (anim == null) {
|
||||
anim = new AnimatorSet();
|
||||
anim.play(mFromUnlock
|
||||
? getUnlockWindowAnimator(targetCompats)
|
||||
: getClosingWindowAnimators(targetCompats));
|
||||
|
||||
// Normally, we run the launcher content animation when we are transitioning
|
||||
// home, but if home is already visible, then we don't want to animate the
|
||||
// contents of launcher unless we know that we are animating home as a result
|
||||
// of the home button press with quickstep, which will result in launcher being
|
||||
// started on touch down, prior to the animation home (and won't be in the
|
||||
// targets list because it is already visible). In that case, we force
|
||||
// invisibility on touch down, and only reset it after the animation to home
|
||||
// is initialized.
|
||||
if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
|
||||
|| mLauncher.isForceInvisible()) {
|
||||
// Only register the content animation for cancellation when state changes
|
||||
mLauncher.getStateManager().setCurrentAnimation(anim);
|
||||
if (mFromUnlock) {
|
||||
Pair<AnimatorSet, Runnable> contentAnimator =
|
||||
getLauncherContentAnimator(false /* isAppOpening */,
|
||||
new float[] {mContentTransY, 0});
|
||||
contentAnimator.first.setStartDelay(0);
|
||||
anim.play(contentAnimator.first);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
contentAnimator.second.run();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
createLauncherResumeAnimation(anim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
|
||||
result.setAnimation(anim);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.proxy;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
public class ProxyActivityStarter extends Activity {
|
||||
|
||||
private static final String TAG = "ProxyActivityStarter";
|
||||
|
||||
public static final String EXTRA_PARAMS = "start-activity-params";
|
||||
|
||||
private StartActivityParams mParams;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setVisible(false);
|
||||
|
||||
mParams = getIntent().getParcelableExtra(EXTRA_PARAMS);
|
||||
if (mParams == null) {
|
||||
Log.d(TAG, "Proxy activity started without params");
|
||||
finishAndRemoveTask();
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
// Already started the activity. Just wait for the result.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mParams.intent != null) {
|
||||
startActivityForResult(mParams.intent, mParams.requestCode, mParams.options);
|
||||
return;
|
||||
} else if (mParams.intentSender != null) {
|
||||
try {
|
||||
startIntentSenderForResult(mParams.intentSender, mParams.requestCode,
|
||||
mParams.fillInIntent, mParams.flagsMask, mParams.flagsValues,
|
||||
mParams.extraFlags,
|
||||
mParams.options);
|
||||
return;
|
||||
} catch (SendIntentException e) {
|
||||
mParams.deliverResult(this, RESULT_CANCELED, null);
|
||||
}
|
||||
}
|
||||
finishAndRemoveTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == mParams.requestCode) {
|
||||
mParams.deliverResult(this, resultCode, data);
|
||||
}
|
||||
finishAndRemoveTask();
|
||||
}
|
||||
|
||||
public static Intent getLaunchIntent(Context context, StartActivityParams params) {
|
||||
return new Intent(context, ProxyActivityStarter.class)
|
||||
.putExtra(EXTRA_PARAMS, params)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.proxy;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.PendingIntent.CanceledException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
public class StartActivityParams implements Parcelable {
|
||||
|
||||
private static final String TAG = "StartActivityParams";
|
||||
|
||||
private final PendingIntent mPICallback;
|
||||
public final int requestCode;
|
||||
|
||||
public Intent intent;
|
||||
|
||||
public IntentSender intentSender;
|
||||
public Intent fillInIntent;
|
||||
public int flagsMask;
|
||||
public int flagsValues;
|
||||
public int extraFlags;
|
||||
public Bundle options;
|
||||
|
||||
public StartActivityParams(Activity activity, int requestCode) {
|
||||
this(activity.createPendingResult(requestCode, new Intent(),
|
||||
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT), requestCode);
|
||||
}
|
||||
|
||||
public StartActivityParams(PendingIntent pendingIntent, int requestCode) {
|
||||
this.mPICallback = pendingIntent;
|
||||
this.requestCode = requestCode;
|
||||
}
|
||||
|
||||
private StartActivityParams(Parcel parcel) {
|
||||
mPICallback = parcel.readTypedObject(PendingIntent.CREATOR);
|
||||
requestCode = parcel.readInt();
|
||||
intent = parcel.readTypedObject(Intent.CREATOR);
|
||||
|
||||
intentSender = parcel.readTypedObject(IntentSender.CREATOR);
|
||||
fillInIntent = parcel.readTypedObject(Intent.CREATOR);
|
||||
flagsMask = parcel.readInt();
|
||||
flagsValues = parcel.readInt();
|
||||
extraFlags = parcel.readInt();
|
||||
options = parcel.readBundle();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeTypedObject(mPICallback, flags);
|
||||
parcel.writeInt(requestCode);
|
||||
parcel.writeTypedObject(intent, flags);
|
||||
|
||||
parcel.writeTypedObject(intentSender, flags);
|
||||
parcel.writeTypedObject(fillInIntent, flags);
|
||||
parcel.writeInt(flagsMask);
|
||||
parcel.writeInt(flagsValues);
|
||||
parcel.writeInt(extraFlags);
|
||||
parcel.writeBundle(options);
|
||||
}
|
||||
|
||||
public void deliverResult(Context context, int resultCode, Intent data) {
|
||||
try {
|
||||
if (mPICallback != null) {
|
||||
mPICallback.send(context, resultCode, data);
|
||||
}
|
||||
} catch (CanceledException e) {
|
||||
Log.e(TAG, "Unable to send back result", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<StartActivityParams> CREATOR =
|
||||
new Parcelable.Creator<StartActivityParams>() {
|
||||
public StartActivityParams createFromParcel(Parcel source) {
|
||||
return new StartActivityParams(source);
|
||||
}
|
||||
|
||||
public StartActivityParams[] newArray(int size) {
|
||||
return new StartActivityParams[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.LauncherStateManager;
|
||||
import com.android.launcher3.anim.AnimatorSetBuilder;
|
||||
import com.android.quickstep.OverviewInteractionState;
|
||||
|
||||
public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler {
|
||||
|
||||
private static final String TAG = "BackButtonAlphaHandler";
|
||||
|
||||
private final Launcher mLauncher;
|
||||
private final OverviewInteractionState mOverviewInteractionState;
|
||||
|
||||
public BackButtonAlphaHandler(Launcher launcher) {
|
||||
mLauncher = launcher;
|
||||
mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(mLauncher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(LauncherState state) {
|
||||
UiFactory.onLauncherStateOrFocusChanged(mLauncher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStateWithAnimation(LauncherState toState,
|
||||
AnimatorSetBuilder builder, LauncherStateManager.AnimationConfig config) {
|
||||
if (!config.playNonAtomicComponent()) {
|
||||
return;
|
||||
}
|
||||
float fromAlpha = mOverviewInteractionState.getBackButtonAlpha();
|
||||
float toAlpha = toState.hideBackButton ? 0 : 1;
|
||||
if (Float.compare(fromAlpha, toAlpha) != 0) {
|
||||
ValueAnimator anim = ValueAnimator.ofFloat(fromAlpha, toAlpha);
|
||||
anim.setDuration(config.duration);
|
||||
anim.addUpdateListener(valueAnimator -> {
|
||||
final float alpha = (float) valueAnimator.getAnimatedValue();
|
||||
mOverviewInteractionState.setBackButtonAlpha(alpha, false);
|
||||
});
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
// Reapply the final alpha in case some state (e.g. window focus) changed.
|
||||
UiFactory.onLauncherStateOrFocusChanged(mLauncher);
|
||||
}
|
||||
});
|
||||
builder.play(anim);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
|
||||
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
|
||||
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
|
||||
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
|
||||
import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
|
||||
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
|
||||
import android.util.FloatProperty;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.LauncherState.ScaleAndTranslation;
|
||||
import com.android.launcher3.LauncherStateManager.AnimationConfig;
|
||||
import com.android.launcher3.LauncherStateManager.StateHandler;
|
||||
import com.android.launcher3.anim.AnimatorSetBuilder;
|
||||
import com.android.launcher3.anim.PropertySetter;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* State handler for recents view. Manages UI changes and animations for recents view based off the
|
||||
* current {@link LauncherState}.
|
||||
*
|
||||
* @param <T> the recents view
|
||||
*/
|
||||
public abstract class BaseRecentsViewStateController<T extends View>
|
||||
implements StateHandler {
|
||||
protected final T mRecentsView;
|
||||
protected final Launcher mLauncher;
|
||||
|
||||
public BaseRecentsViewStateController(@NonNull Launcher launcher) {
|
||||
mLauncher = launcher;
|
||||
mRecentsView = launcher.getOverviewPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(@NonNull LauncherState state) {
|
||||
ScaleAndTranslation scaleAndTranslation = state
|
||||
.getOverviewScaleAndTranslation(mLauncher);
|
||||
SCALE_PROPERTY.set(mRecentsView, scaleAndTranslation.scale);
|
||||
float translationX = scaleAndTranslation.translationX;
|
||||
if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
|
||||
translationX = -translationX;
|
||||
}
|
||||
mRecentsView.setTranslationX(translationX);
|
||||
mRecentsView.setTranslationY(scaleAndTranslation.translationY);
|
||||
getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setStateWithAnimation(@NonNull final LauncherState toState,
|
||||
@NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
|
||||
boolean playAtomicOverviewComponent = config.playAtomicOverviewScaleComponent()
|
||||
|| config.playAtomicOverviewPeekComponent();
|
||||
if (!playAtomicOverviewComponent) {
|
||||
// The entire recents animation is played atomically.
|
||||
return;
|
||||
}
|
||||
if (builder.hasFlag(FLAG_DONT_ANIMATE_OVERVIEW)) {
|
||||
return;
|
||||
}
|
||||
setStateWithAnimationInternal(toState, builder, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Core logic for animating the recents view UI.
|
||||
*
|
||||
* @param toState state to animate to
|
||||
* @param builder animator set builder
|
||||
* @param config current animation config
|
||||
*/
|
||||
void setStateWithAnimationInternal(@NonNull final LauncherState toState,
|
||||
@NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
|
||||
PropertySetter setter = config.getPropertySetter(builder);
|
||||
ScaleAndTranslation scaleAndTranslation = toState.getOverviewScaleAndTranslation(mLauncher);
|
||||
Interpolator scaleInterpolator = builder.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
|
||||
setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndTranslation.scale, scaleInterpolator);
|
||||
Interpolator translateXInterpolator = builder.getInterpolator(
|
||||
ANIM_OVERVIEW_TRANSLATE_X, LINEAR);
|
||||
Interpolator translateYInterpolator = builder.getInterpolator(
|
||||
ANIM_OVERVIEW_TRANSLATE_Y, LINEAR);
|
||||
float translationX = scaleAndTranslation.translationX;
|
||||
if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
|
||||
translationX = -translationX;
|
||||
}
|
||||
setter.setFloat(mRecentsView, View.TRANSLATION_X, translationX, translateXInterpolator);
|
||||
setter.setFloat(mRecentsView, View.TRANSLATION_Y, scaleAndTranslation.translationY,
|
||||
translateYInterpolator);
|
||||
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
|
||||
builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property for content alpha for the recents view.
|
||||
*
|
||||
* @return the float property for the view's content alpha
|
||||
*/
|
||||
abstract FloatProperty getContentAlphaProperty();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
import com.android.systemui.shared.system.RotationWatcher;
|
||||
|
||||
/**
|
||||
* Utility class for listening for rotation changes
|
||||
*/
|
||||
public class DisplayRotationListener extends RotationWatcher {
|
||||
|
||||
private final Runnable mCallback;
|
||||
private Handler mHandler;
|
||||
|
||||
public DisplayRotationListener(Context context, Runnable callback) {
|
||||
super(context);
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
if (mHandler == null) {
|
||||
mHandler = new Handler();
|
||||
}
|
||||
super.enable();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRotationChanged(int i) {
|
||||
mHandler.post(mCallback);
|
||||
}
|
||||
}
|
||||
247
quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
Normal file
247
quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
Normal file
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* 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 android.app.Activity.RESULT_CANCELED;
|
||||
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
|
||||
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT;
|
||||
import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT;
|
||||
import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
|
||||
import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT;
|
||||
import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.LauncherState.ScaleAndTranslation;
|
||||
import com.android.launcher3.LauncherStateManager;
|
||||
import com.android.launcher3.LauncherStateManager.StateHandler;
|
||||
import com.android.launcher3.QuickstepAppTransitionManagerImpl;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.proxy.ProxyActivityStarter;
|
||||
import com.android.launcher3.proxy.StartActivityParams;
|
||||
import com.android.quickstep.OverviewInteractionState;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
|
||||
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
|
||||
import com.android.systemui.shared.system.ActivityCompat;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
public class UiFactory extends RecentsUiFactory {
|
||||
|
||||
public static Runnable enableLiveUIChanges(Launcher launcher) {
|
||||
NavigationModeChangeListener listener = m -> {
|
||||
launcher.getDragLayer().recreateControllers();
|
||||
launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON);
|
||||
};
|
||||
SysUINavigationMode mode = SysUINavigationMode.INSTANCE.get(launcher);
|
||||
SysUINavigationMode.Mode m = mode.addModeChangeListener(listener);
|
||||
launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON);
|
||||
return () -> mode.removeModeChangeListener(listener);
|
||||
}
|
||||
|
||||
public static StateHandler[] getStateHandler(Launcher launcher) {
|
||||
return new StateHandler[] {
|
||||
launcher.getAllAppsController(),
|
||||
launcher.getWorkspace(),
|
||||
createRecentsViewStateController(launcher),
|
||||
new BackButtonAlphaHandler(launcher)};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the back button visibility based on the current state/window focus.
|
||||
*/
|
||||
public static void onLauncherStateOrFocusChanged(Launcher launcher) {
|
||||
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.getTopOpenViewWithType(launcher,
|
||||
TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
|
||||
}
|
||||
OverviewInteractionState.INSTANCE.get(launcher)
|
||||
.setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
|
||||
if (launcher != null && launcher.getDragLayer() != null) {
|
||||
launcher.getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onCreate(Launcher launcher) {
|
||||
if (!launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) {
|
||||
launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
|
||||
@Override
|
||||
public void onStateTransitionStart(LauncherState toState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateTransitionComplete(LauncherState finalState) {
|
||||
boolean swipeUpEnabled = SysUINavigationMode.INSTANCE.get(launcher).getMode()
|
||||
.hasGestures;
|
||||
LauncherState prevState = launcher.getStateManager().getLastState();
|
||||
|
||||
if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
|
||||
&& finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT <=
|
||||
launcher.getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) {
|
||||
launcher.getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply();
|
||||
launcher.getStateManager().removeStateListener(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) {
|
||||
launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
|
||||
@Override
|
||||
public void onStateTransitionStart(LauncherState toState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateTransitionComplete(LauncherState finalState) {
|
||||
LauncherState prevState = launcher.getStateManager().getLastState();
|
||||
|
||||
if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT <=
|
||||
launcher.getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) {
|
||||
launcher.getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply();
|
||||
launcher.getStateManager().removeStateListener(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void onEnterAnimationComplete(Context context) {
|
||||
// After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
|
||||
// as a part of quickstep, so that high-res thumbnails can load the next time we enter
|
||||
// overview
|
||||
RecentsModel.INSTANCE.get(context).getThumbnailCache()
|
||||
.getHighResLoadingState().setVisible(true);
|
||||
}
|
||||
|
||||
public static void onTrimMemory(Context context, int level) {
|
||||
RecentsModel model = RecentsModel.INSTANCE.get(context);
|
||||
if (model != null) {
|
||||
model.onTrimMemory(level);
|
||||
}
|
||||
}
|
||||
|
||||
public static void useFadeOutAnimationForLauncherStart(Launcher launcher,
|
||||
CancellationSignal cancellationSignal) {
|
||||
QuickstepAppTransitionManagerImpl appTransitionManager =
|
||||
(QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
|
||||
appTransitionManager.setRemoteAnimationProvider((targets) -> {
|
||||
|
||||
// On the first call clear the reference.
|
||||
cancellationSignal.cancel();
|
||||
|
||||
ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
|
||||
fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(targets));
|
||||
AnimatorSet anim = new AnimatorSet();
|
||||
anim.play(fadeAnimation);
|
||||
return anim;
|
||||
}, cancellationSignal);
|
||||
}
|
||||
|
||||
public static boolean dumpActivity(Activity activity, PrintWriter writer) {
|
||||
if (!Utilities.IS_DEBUG_DEVICE) {
|
||||
return false;
|
||||
}
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
if (!(new ActivityCompat(activity).encodeViewHierarchy(out))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Deflater deflater = new Deflater();
|
||||
deflater.setInput(out.toByteArray());
|
||||
deflater.finish();
|
||||
|
||||
out.reset();
|
||||
byte[] buffer = new byte[1024];
|
||||
while (!deflater.finished()) {
|
||||
int count = deflater.deflate(buffer); // returns the generated code... index
|
||||
out.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
writer.println("--encoded-view-dump-v0--");
|
||||
writer.println(Base64.encodeToString(
|
||||
out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean startIntentSenderForResult(Activity activity, IntentSender intent,
|
||||
int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
|
||||
Bundle options) {
|
||||
StartActivityParams params = new StartActivityParams(activity, requestCode);
|
||||
params.intentSender = intent;
|
||||
params.fillInIntent = fillInIntent;
|
||||
params.flagsMask = flagsMask;
|
||||
params.flagsValues = flagsValues;
|
||||
params.extraFlags = extraFlags;
|
||||
params.options = options;
|
||||
((Context) activity).startActivity(ProxyActivityStarter.getLaunchIntent(activity, params));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode,
|
||||
Bundle options) {
|
||||
StartActivityParams params = new StartActivityParams(activity, requestCode);
|
||||
params.intent = intent;
|
||||
params.options = options;
|
||||
activity.startActivity(ProxyActivityStarter.getLaunchIntent(activity, params));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any active ProxyActivityStarter task and sends RESULT_CANCELED to Launcher.
|
||||
*
|
||||
* ProxyActivityStarter is started with clear task to reset the task after which it removes the
|
||||
* task itself.
|
||||
*/
|
||||
public static void resetPendingActivityResults(Launcher launcher, int requestCode) {
|
||||
launcher.onActivityResult(requestCode, RESULT_CANCELED, null);
|
||||
launcher.startActivity(ProxyActivityStarter.getLaunchIntent(launcher, null));
|
||||
}
|
||||
|
||||
public static ScaleAndTranslation getOverviewScaleAndTranslationForNormalState(Launcher l) {
|
||||
if (SysUINavigationMode.getMode(l) == Mode.NO_BUTTON) {
|
||||
float offscreenTranslationX = l.getDeviceProfile().widthPx
|
||||
- l.getOverviewPanel().getPaddingStart();
|
||||
return new ScaleAndTranslation(1f, offscreenTranslationX, 0f);
|
||||
}
|
||||
return new ScaleAndTranslation(1.1f, 0f, 0f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 android.app.WallpaperManager.FLAG_SYSTEM;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.WallpaperColors;
|
||||
import android.app.WallpaperManager;
|
||||
import android.app.WallpaperManager.OnColorsChangedListener;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.android.systemui.shared.system.TonalCompat;
|
||||
import com.android.systemui.shared.system.TonalCompat.ExtractionInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public class WallpaperColorInfo implements OnColorsChangedListener {
|
||||
|
||||
private static final int MAIN_COLOR_LIGHT = 0xffdadce0;
|
||||
private static final int MAIN_COLOR_DARK = 0xff202124;
|
||||
private static final int MAIN_COLOR_REGULAR = 0xff000000;
|
||||
|
||||
private static final Object sInstanceLock = new Object();
|
||||
private static WallpaperColorInfo sInstance;
|
||||
|
||||
public static WallpaperColorInfo getInstance(Context context) {
|
||||
synchronized (sInstanceLock) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new WallpaperColorInfo(context.getApplicationContext());
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
}
|
||||
|
||||
private final ArrayList<OnChangeListener> mListeners = new ArrayList<>();
|
||||
private final WallpaperManager mWallpaperManager;
|
||||
private final TonalCompat mTonalCompat;
|
||||
|
||||
private ExtractionInfo mExtractionInfo;
|
||||
|
||||
private OnChangeListener[] mTempListeners = new OnChangeListener[0];
|
||||
|
||||
private WallpaperColorInfo(Context context) {
|
||||
mWallpaperManager = context.getSystemService(WallpaperManager.class);
|
||||
mTonalCompat = new TonalCompat(context);
|
||||
|
||||
mWallpaperManager.addOnColorsChangedListener(this, new Handler(Looper.getMainLooper()));
|
||||
update(mWallpaperManager.getWallpaperColors(FLAG_SYSTEM));
|
||||
}
|
||||
|
||||
public int getMainColor() {
|
||||
return mExtractionInfo.mainColor;
|
||||
}
|
||||
|
||||
public int getSecondaryColor() {
|
||||
return mExtractionInfo.secondaryColor;
|
||||
}
|
||||
|
||||
public boolean isDark() {
|
||||
return mExtractionInfo.supportsDarkTheme;
|
||||
}
|
||||
|
||||
public boolean supportsDarkText() {
|
||||
return mExtractionInfo.supportsDarkText;
|
||||
}
|
||||
|
||||
public boolean isMainColorDark() {
|
||||
return mExtractionInfo.mainColor == MAIN_COLOR_DARK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorsChanged(WallpaperColors colors, int which) {
|
||||
if ((which & FLAG_SYSTEM) != 0) {
|
||||
update(colors);
|
||||
notifyChange();
|
||||
}
|
||||
}
|
||||
|
||||
private void update(WallpaperColors wallpaperColors) {
|
||||
mExtractionInfo = mTonalCompat.extractDarkColors(wallpaperColors);
|
||||
}
|
||||
|
||||
public void addOnChangeListener(OnChangeListener listener) {
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeOnChangeListener(OnChangeListener listener) {
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
|
||||
private void notifyChange() {
|
||||
// Create a new array to avoid concurrent modification when the activity destroys itself.
|
||||
mTempListeners = mListeners.toArray(mTempListeners);
|
||||
for (OnChangeListener listener : mTempListeners) {
|
||||
if (listener != null) {
|
||||
listener.onExtractedColorsChanged(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnChangeListener {
|
||||
void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.plugins;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.systemui.shared.plugins.PluginEnabler;
|
||||
|
||||
import androidx.preference.PreferenceDataStore;
|
||||
|
||||
public class PluginEnablerImpl extends PreferenceDataStore implements PluginEnabler {
|
||||
|
||||
private static final String PREFIX_PLUGIN_ENABLED = "PLUGIN_ENABLED_";
|
||||
|
||||
final private SharedPreferences mSharedPrefs;
|
||||
|
||||
public PluginEnablerImpl(Context context) {
|
||||
mSharedPrefs = Utilities.getDevicePrefs(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(ComponentName component) {
|
||||
setState(component, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisabled(ComponentName component, int reason) {
|
||||
setState(component, reason == ENABLED);
|
||||
}
|
||||
|
||||
private void setState(ComponentName component, boolean enabled) {
|
||||
putBoolean(pluginEnabledKey(component), enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(ComponentName component) {
|
||||
return getBoolean(pluginEnabledKey(component), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDisableReason(ComponentName componentName) {
|
||||
return isEnabled(componentName) ? ENABLED : DISABLED_MANUALLY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putBoolean(String key, boolean value) {
|
||||
mSharedPrefs.edit().putBoolean(key, value).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String key, boolean defValue) {
|
||||
return mSharedPrefs.getBoolean(key, defValue);
|
||||
}
|
||||
|
||||
static String pluginEnabledKey(ComponentName cn) {
|
||||
return PREFIX_PLUGIN_ENABLED + cn.flattenToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.plugins;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.systemui.shared.plugins.PluginInitializer;
|
||||
|
||||
public class PluginInitializerImpl implements PluginInitializer {
|
||||
@Override
|
||||
public Looper getBgLooper() {
|
||||
return LauncherModel.getWorkerLooper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginManagerInit() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getWhitelistedPlugins(Context context) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginEnablerImpl getPluginEnabler(Context context) {
|
||||
return new PluginEnablerImpl(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleWtfs() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.plugins;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.systemui.plugins.Plugin;
|
||||
import com.android.systemui.plugins.PluginListener;
|
||||
import com.android.systemui.shared.plugins.PluginManager;
|
||||
import com.android.systemui.shared.plugins.PluginManagerImpl;
|
||||
import com.android.systemui.shared.plugins.PluginPrefs;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class PluginManagerWrapper {
|
||||
|
||||
public static final MainThreadInitializedObject<PluginManagerWrapper> INSTANCE =
|
||||
new MainThreadInitializedObject<>(PluginManagerWrapper::new);
|
||||
|
||||
public static final String PLUGIN_CHANGED = PluginManager.PLUGIN_CHANGED;
|
||||
|
||||
private final Context mContext;
|
||||
private final PluginManager mPluginManager;
|
||||
private final PluginEnablerImpl mPluginEnabler;
|
||||
|
||||
private PluginManagerWrapper(Context c) {
|
||||
mContext = c;
|
||||
PluginInitializerImpl pluginInitializer = new PluginInitializerImpl();
|
||||
mPluginManager = new PluginManagerImpl(c, pluginInitializer);
|
||||
mPluginEnabler = pluginInitializer.getPluginEnabler(c);
|
||||
}
|
||||
|
||||
public PluginEnablerImpl getPluginEnabler() {
|
||||
return mPluginEnabler;
|
||||
}
|
||||
|
||||
public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
|
||||
addPluginListener(listener, pluginClass, false);
|
||||
}
|
||||
|
||||
public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
|
||||
boolean allowMultiple) {
|
||||
mPluginManager.addPluginListener(listener, pluginClass, allowMultiple);
|
||||
}
|
||||
|
||||
public void removePluginListener(PluginListener<? extends Plugin> listener) {
|
||||
mPluginManager.removePluginListener(listener);
|
||||
}
|
||||
|
||||
public Set<String> getPluginActions() {
|
||||
return new PluginPrefs(mContext).getPluginList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string key used to store plugin enabled/disabled setting
|
||||
*/
|
||||
public static String pluginEnabledKey(ComponentName cn) {
|
||||
return PluginEnablerImpl.pluginEnabledKey(cn);
|
||||
}
|
||||
|
||||
public static boolean hasPlugins(Context context) {
|
||||
return PluginPrefs.hasPlugins(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.states;
|
||||
|
||||
import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.allapps.AllAppsContainerView;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
|
||||
/**
|
||||
* Definition for AllApps state
|
||||
*/
|
||||
public class AllAppsState extends LauncherState {
|
||||
|
||||
private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
|
||||
|
||||
private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
|
||||
@Override
|
||||
public float getPageAlpha(int pageIndex) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
public AllAppsState(int id) {
|
||||
super(id, ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, STATE_FLAGS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateEnabled(Launcher launcher) {
|
||||
AbstractFloatingView.closeAllOpenViews(launcher);
|
||||
dispatchWindowStateChanged(launcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(Launcher launcher) {
|
||||
AllAppsContainerView appsView = launcher.getAppsView();
|
||||
return appsView.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVerticalProgress(Launcher launcher) {
|
||||
return 0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
|
||||
ScaleAndTranslation scaleAndTranslation = LauncherState.OVERVIEW
|
||||
.getWorkspaceScaleAndTranslation(launcher);
|
||||
if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
|
||||
float normalScale = 1;
|
||||
// Scale down halfway to where we'd be in overview, to prepare for a potential pause.
|
||||
scaleAndTranslation.scale = (scaleAndTranslation.scale + normalScale) / 2;
|
||||
} else {
|
||||
scaleAndTranslation.scale = 1;
|
||||
}
|
||||
return scaleAndTranslation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
|
||||
return PAGE_ALPHA_PROVIDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisibleElements(Launcher launcher) {
|
||||
return ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
|
||||
float slightParallax = -launcher.getDeviceProfile().allAppsCellHeightPx * 0.3f;
|
||||
return new ScaleAndTranslation(0.9f, 0f, slightParallax);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LauncherState getHistoryForState(LauncherState previousState) {
|
||||
return previousState == OVERVIEW ? OVERVIEW : NORMAL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.android.launcher3.uioverrides.touchcontrollers;
|
||||
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.Utilities.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.LauncherStateManager.AnimationComponents;
|
||||
import com.android.launcher3.touch.AbstractStateChangeTouchController;
|
||||
import com.android.launcher3.touch.SwipeDetector;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
|
||||
/**
|
||||
* Touch controller for handling edge swipes in landscape/seascape UI
|
||||
*/
|
||||
public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchController {
|
||||
|
||||
private static final String TAG = "LandscapeEdgeSwipeCtrl";
|
||||
|
||||
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 LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
|
||||
boolean draggingFromNav = mLauncher.getDeviceProfile().isSeascape() == isDragTowardPositive;
|
||||
return draggingFromNav ? OVERVIEW : NORMAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLogContainerTypeForNormalState() {
|
||||
return LauncherLogProto.ContainerType.NAVBAR;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getShiftRange() {
|
||||
return mLauncher.getDragLayer().getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float initCurrentAnimation(@AnimationComponents int animComponent) {
|
||||
float range = getShiftRange();
|
||||
long maxAccuracy = (long) (2 * range);
|
||||
mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
|
||||
maxAccuracy, animComponent);
|
||||
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 (mStartState == NORMAL && targetState == OVERVIEW) {
|
||||
RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* 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.touchcontrollers;
|
||||
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
|
||||
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_ALL_APPS_FADE;
|
||||
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
|
||||
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
|
||||
|
||||
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.LauncherStateManager.AnimationComponents;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.AnimatorSetBuilder;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.touch.AbstractStateChangeTouchController;
|
||||
import com.android.launcher3.touch.SwipeDetector;
|
||||
import com.android.launcher3.uioverrides.states.OverviewState;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
|
||||
import com.android.quickstep.OverviewInteractionState;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.TouchInteractionService;
|
||||
import com.android.quickstep.util.LayoutUtils;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
|
||||
/**
|
||||
* Touch controller for handling various state transitions in portrait UI.
|
||||
*/
|
||||
public class PortraitStatesTouchController extends AbstractStateChangeTouchController {
|
||||
|
||||
private static final String TAG = "PortraitStatesTouchCtrl";
|
||||
|
||||
/**
|
||||
* The progress at which all apps content will be fully visible when swiping up from overview.
|
||||
*/
|
||||
private static final float ALL_APPS_CONTENT_FADE_THRESHOLD = 0.08f;
|
||||
|
||||
/**
|
||||
* The progress at which recents will begin fading out when swiping up from overview.
|
||||
*/
|
||||
private static final float RECENTS_FADE_THRESHOLD = 0.88f;
|
||||
|
||||
private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper;
|
||||
|
||||
private final InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
|
||||
|
||||
private final boolean mAllowDragToOverview;
|
||||
|
||||
// If true, we will finish the current animation instantly on second touch.
|
||||
private boolean mFinishFastOnSecondTouch;
|
||||
|
||||
public PortraitStatesTouchController(Launcher l, boolean allowDragToOverview) {
|
||||
super(l, SwipeDetector.VERTICAL);
|
||||
mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l);
|
||||
mAllowDragToOverview = allowDragToOverview;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canInterceptTouch(MotionEvent ev) {
|
||||
if (mCurrentAnimation != null) {
|
||||
if (mFinishFastOnSecondTouch) {
|
||||
// TODO: Animate to finish instead.
|
||||
mCurrentAnimation.skipToEnd();
|
||||
}
|
||||
|
||||
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
|
||||
if (ev.getY() >= allAppsController.getShiftRange() * allAppsController.getProgress()) {
|
||||
// If we are already animating from a previous state, we can intercept as long as
|
||||
// the touch is below the current all apps progress (to allow for double swipe).
|
||||
return true;
|
||||
}
|
||||
// Otherwise, make sure everything is settled and don't intercept so they can scroll
|
||||
// recents, dismiss a task, etc.
|
||||
if (mAtomicAnim != null) {
|
||||
mAtomicAnim.end();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (mLauncher.isInState(ALL_APPS)) {
|
||||
// In all-apps only listen if the container cannot scroll itself
|
||||
if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
|
||||
return false;
|
||||
}
|
||||
} else if (mLauncher.isInState(OVERVIEW)) {
|
||||
if (!mOverviewPortraitStateTouchHelper.canInterceptTouch(ev)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// If we are swiping to all apps instead of overview, allow it from anywhere.
|
||||
boolean interceptAnywhere = mLauncher.isInState(NORMAL) && !mAllowDragToOverview;
|
||||
// For all other states, only listen if the event originated below the hotseat height
|
||||
if (!interceptAnywhere && !isTouchOverHotseat(mLauncher, ev)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, TYPE_ACCESSIBLE) != null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
|
||||
if (fromState == ALL_APPS && !isDragTowardPositive) {
|
||||
// Should swipe down go to OVERVIEW instead?
|
||||
return TouchInteractionService.isConnected() ?
|
||||
mLauncher.getStateManager().getLastState() : NORMAL;
|
||||
} else if (fromState == OVERVIEW) {
|
||||
return isDragTowardPositive ? ALL_APPS : NORMAL;
|
||||
} else if (fromState == NORMAL && isDragTowardPositive) {
|
||||
int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher)
|
||||
.getSystemUiStateFlags();
|
||||
return mAllowDragToOverview && TouchInteractionService.isConnected()
|
||||
&& (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0
|
||||
? OVERVIEW : ALL_APPS;
|
||||
}
|
||||
return fromState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLogContainerTypeForNormalState() {
|
||||
return ContainerType.HOTSEAT;
|
||||
}
|
||||
|
||||
private AnimatorSetBuilder getNormalToOverviewAnimation() {
|
||||
mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
|
||||
|
||||
AnimatorSetBuilder builder = new AnimatorSetBuilder();
|
||||
builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static AnimatorSetBuilder getOverviewToAllAppsAnimation() {
|
||||
AnimatorSetBuilder builder = new AnimatorSetBuilder();
|
||||
builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
|
||||
0, ALL_APPS_CONTENT_FADE_THRESHOLD));
|
||||
builder.setInterpolator(ANIM_OVERVIEW_FADE, Interpolators.clampToProgress(DEACCEL,
|
||||
RECENTS_FADE_THRESHOLD, 1));
|
||||
return builder;
|
||||
}
|
||||
|
||||
private AnimatorSetBuilder getAllAppsToOverviewAnimation() {
|
||||
AnimatorSetBuilder builder = new AnimatorSetBuilder();
|
||||
builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
|
||||
1 - ALL_APPS_CONTENT_FADE_THRESHOLD, 1));
|
||||
builder.setInterpolator(ANIM_OVERVIEW_FADE, Interpolators.clampToProgress(ACCEL,
|
||||
0f, 1 - RECENTS_FADE_THRESHOLD));
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
|
||||
LauncherState toState) {
|
||||
AnimatorSetBuilder builder = new AnimatorSetBuilder();
|
||||
if (fromState == NORMAL && toState == OVERVIEW) {
|
||||
builder = getNormalToOverviewAnimation();
|
||||
} else if (fromState == OVERVIEW && toState == ALL_APPS) {
|
||||
builder = getOverviewToAllAppsAnimation();
|
||||
} else if (fromState == ALL_APPS && toState == OVERVIEW) {
|
||||
builder = getAllAppsToOverviewAnimation();
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float initCurrentAnimation(@AnimationComponents int animComponents) {
|
||||
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 = totalShift == 0 ? new AnimatorSetBuilder()
|
||||
: getAnimatorSetBuilderForStates(mFromState, mToState);
|
||||
updateAnimatorBuilderOnReinit(builder);
|
||||
|
||||
cancelPendingAnim();
|
||||
|
||||
if (mFromState == OVERVIEW && mToState == NORMAL
|
||||
&& mOverviewPortraitStateTouchHelper.shouldSwipeDownReturnToApp()) {
|
||||
// Reset the state manager, when changing the interaction mode
|
||||
mLauncher.getStateManager().goToState(OVERVIEW, false /* animate */);
|
||||
mPendingAnimation = mOverviewPortraitStateTouchHelper
|
||||
.createSwipeDownToTaskAppAnimation(maxAccuracy);
|
||||
mPendingAnimation.anim.setInterpolator(Interpolators.LINEAR);
|
||||
|
||||
Runnable onCancelRunnable = () -> {
|
||||
cancelPendingAnim();
|
||||
clearState();
|
||||
};
|
||||
mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy,
|
||||
onCancelRunnable);
|
||||
mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
|
||||
totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
|
||||
mLauncher.getDeviceProfile());
|
||||
} else {
|
||||
mCurrentAnimation = mLauncher.getStateManager()
|
||||
.createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState,
|
||||
animComponents);
|
||||
}
|
||||
|
||||
if (totalShift == 0) {
|
||||
totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
|
||||
* OverviewState.getDefaultSwipeHeight(mLauncher);
|
||||
}
|
||||
return 1 / totalShift;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give subclasses the chance to update the animation when we re-initialize towards a new state.
|
||||
*/
|
||||
protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
|
||||
}
|
||||
|
||||
private void cancelPendingAnim() {
|
||||
if (mPendingAnimation != null) {
|
||||
mPendingAnimation.finish(false, Touch.SWIPE);
|
||||
mPendingAnimation = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
|
||||
LauncherState targetState, float velocity, boolean isFling) {
|
||||
super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
|
||||
velocity, isFling);
|
||||
handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling);
|
||||
}
|
||||
|
||||
private void handleFirstSwipeToOverview(final ValueAnimator animator,
|
||||
final long expectedDuration, final LauncherState targetState, final float velocity,
|
||||
final boolean isFling) {
|
||||
if (QUICKSTEP_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
|
||||
&& targetState == OVERVIEW) {
|
||||
mFinishFastOnSecondTouch = true;
|
||||
} else if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
|
||||
mFinishFastOnSecondTouch = true;
|
||||
if (isFling && expectedDuration != 0) {
|
||||
// Update all apps interpolator to add a bit of overshoot starting from currFraction
|
||||
final float currFraction = mCurrentAnimation.getProgressFraction();
|
||||
mAllAppsInterpolatorWrapper.baseInterpolator = Interpolators.clampToProgress(
|
||||
Interpolators.overshootInterpolatorForVelocity(velocity), currFraction, 1);
|
||||
animator.setDuration(Math.min(expectedDuration, ATOMIC_DURATION))
|
||||
.setInterpolator(LINEAR);
|
||||
}
|
||||
} else {
|
||||
mFinishFastOnSecondTouch = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
|
||||
super.onSwipeInteractionCompleted(targetState, logAction);
|
||||
if (mStartState == NORMAL && targetState == OVERVIEW) {
|
||||
RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the motion event is over the hotseat.
|
||||
*
|
||||
* @param launcher the launcher activity
|
||||
* @param ev the event to check
|
||||
* @return true if the event is over the hotseat
|
||||
*/
|
||||
static boolean isTouchOverHotseat(Launcher launcher, MotionEvent ev) {
|
||||
DeviceProfile dp = launcher.getDeviceProfile();
|
||||
int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
|
||||
return (ev.getY() >= (launcher.getDragLayer().getHeight() - hotseatHeight));
|
||||
}
|
||||
|
||||
private static class InterpolatorWrapper implements Interpolator {
|
||||
|
||||
public TimeInterpolator baseInterpolator = LINEAR;
|
||||
|
||||
@Override
|
||||
public float getInterpolation(float v) {
|
||||
return baseInterpolator.getInterpolation(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.touchcontrollers;
|
||||
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_MOVE;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.touch.TouchEventTranslator;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* TouchController for handling touch events that get sent to the StatusBar. Once the
|
||||
* Once the event delta y passes the touch slop, the events start getting forwarded.
|
||||
* All events are offset by initial Y value of the pointer.
|
||||
*/
|
||||
public class StatusBarTouchController implements TouchController {
|
||||
|
||||
private static final String TAG = "StatusBarController";
|
||||
|
||||
protected final Launcher mLauncher;
|
||||
protected final TouchEventTranslator mTranslator;
|
||||
private final float mTouchSlop;
|
||||
private ISystemUiProxy mSysUiProxy;
|
||||
private int mLastAction;
|
||||
|
||||
/* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/
|
||||
private boolean mCanIntercept;
|
||||
|
||||
public StatusBarTouchController(Launcher l) {
|
||||
mLauncher = l;
|
||||
// Guard against TAPs by increasing the touch slop.
|
||||
mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop();
|
||||
mTranslator = new TouchEventTranslator((MotionEvent ev)-> dispatchTouchEvent(ev));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(String prefix, PrintWriter writer) {
|
||||
writer.println(prefix + "mCanIntercept:" + mCanIntercept);
|
||||
writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction));
|
||||
writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null));
|
||||
|
||||
}
|
||||
|
||||
private void dispatchTouchEvent(MotionEvent ev) {
|
||||
try {
|
||||
if (mSysUiProxy != null) {
|
||||
mLastAction = ev.getActionMasked();
|
||||
mSysUiProxy.onStatusBarMotionEvent(ev);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Remote exception on sysUiProxy.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
||||
int action = ev.getActionMasked();
|
||||
if (action == ACTION_DOWN) {
|
||||
mCanIntercept = canInterceptTouch(ev);
|
||||
if (!mCanIntercept) {
|
||||
return false;
|
||||
}
|
||||
mTranslator.reset();
|
||||
mTranslator.setDownParameters(0, ev);
|
||||
} else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
|
||||
// Check!! should only set it only when threshold is not entered.
|
||||
mTranslator.setDownParameters(ev.getActionIndex(), ev);
|
||||
}
|
||||
if (!mCanIntercept) {
|
||||
return false;
|
||||
}
|
||||
if (action == ACTION_MOVE) {
|
||||
float dy = ev.getY() - mTranslator.getDownY();
|
||||
float dx = ev.getX() - mTranslator.getDownX();
|
||||
if (dy > mTouchSlop && dy > Math.abs(dx)) {
|
||||
mTranslator.dispatchDownEvents(ev);
|
||||
mTranslator.processMotionEvent(ev);
|
||||
return true;
|
||||
}
|
||||
if (Math.abs(dx) > mTouchSlop) {
|
||||
mCanIntercept = false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean onControllerTouchEvent(MotionEvent ev) {
|
||||
mTranslator.processMotionEvent(ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean canInterceptTouch(MotionEvent ev) {
|
||||
if (!mLauncher.isInState(LauncherState.NORMAL) ||
|
||||
AbstractFloatingView.getTopOpenViewWithType(mLauncher,
|
||||
AbstractFloatingView.TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW) != null) {
|
||||
return false;
|
||||
} else {
|
||||
// For NORMAL state, only listen if the event originated above the navbar height
|
||||
DeviceProfile dp = mLauncher.getDeviceProfile();
|
||||
if (ev.getY() > (mLauncher.getDragLayer().getHeight() - dp.getInsets().bottom)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy();
|
||||
return mSysUiProxy != null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user