update:2021.03.19
fix:重新提交 add:
This commit is contained in:
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.uiuios;
|
||||
|
||||
import static com.android.uiuios.Utilities.SINGLE_FRAME_MS;
|
||||
import static com.android.uiuios.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.uiuios;
|
||||
|
||||
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.uiuios.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);
|
||||
}
|
||||
}
|
||||
@@ -1,828 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.uiuios;
|
||||
|
||||
import static com.android.uiuios.BaseActivity.INVISIBLE_ALL;
|
||||
import static com.android.uiuios.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
|
||||
import static com.android.uiuios.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
|
||||
import static com.android.uiuios.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
|
||||
import static com.android.uiuios.LauncherState.ALL_APPS;
|
||||
import static com.android.uiuios.LauncherState.OVERVIEW;
|
||||
import static com.android.uiuios.Utilities.postAsyncCallback;
|
||||
import static com.android.uiuios.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
|
||||
import static com.android.uiuios.anim.Interpolators.AGGRESSIVE_EASE;
|
||||
import static com.android.uiuios.anim.Interpolators.DEACCEL_1_7;
|
||||
import static com.android.uiuios.anim.Interpolators.EXAGGERATED_EASE;
|
||||
import static com.android.uiuios.anim.Interpolators.LINEAR;
|
||||
import static com.android.uiuios.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
|
||||
import static com.android.uiuios.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.uiuios.DeviceProfile.OnDeviceProfileChangeListener;
|
||||
import com.android.uiuios.allapps.AllAppsTransitionController;
|
||||
import com.android.uiuios.anim.Interpolators;
|
||||
import com.android.uiuios.config.FeatureFlags;
|
||||
import com.android.uiuios.dragndrop.DragLayer;
|
||||
import com.android.uiuios.shortcuts.DeepShortcutView;
|
||||
import com.android.uiuios.util.MultiValueAlpha;
|
||||
import com.android.uiuios.util.MultiValueAlpha.AlphaProperty;
|
||||
import com.android.uiuios.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.uiuios.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);
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.uiuios.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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.uiuios.uioverrides;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
|
||||
import com.android.uiuios.Launcher;
|
||||
import com.android.uiuios.LauncherState;
|
||||
import com.android.uiuios.LauncherStateManager;
|
||||
import com.android.uiuios.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.uiuios.uioverrides;
|
||||
|
||||
import static com.android.uiuios.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.uiuios.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
|
||||
import static com.android.uiuios.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
|
||||
import static com.android.uiuios.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
|
||||
import static com.android.uiuios.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
|
||||
import static com.android.uiuios.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
|
||||
import static com.android.uiuios.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
|
||||
import static com.android.uiuios.anim.Interpolators.LINEAR;
|
||||
|
||||
import android.util.FloatProperty;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.android.uiuios.Launcher;
|
||||
import com.android.uiuios.LauncherState;
|
||||
import com.android.uiuios.LauncherState.ScaleAndTranslation;
|
||||
import com.android.uiuios.LauncherStateManager.AnimationConfig;
|
||||
import com.android.uiuios.LauncherStateManager.StateHandler;
|
||||
import com.android.uiuios.anim.AnimatorSetBuilder;
|
||||
import com.android.uiuios.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();
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.uiuios.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);
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.uiuios.uioverrides;
|
||||
|
||||
import static android.app.Activity.RESULT_CANCELED;
|
||||
|
||||
import static com.android.uiuios.AbstractFloatingView.TYPE_ALL;
|
||||
import static com.android.uiuios.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
|
||||
import static com.android.uiuios.LauncherState.ALL_APPS;
|
||||
import static com.android.uiuios.LauncherState.NORMAL;
|
||||
import static com.android.uiuios.LauncherState.OVERVIEW;
|
||||
import static com.android.uiuios.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT;
|
||||
import static com.android.uiuios.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT;
|
||||
import static com.android.uiuios.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
|
||||
import static com.android.uiuios.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT;
|
||||
import static com.android.uiuios.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.uiuios.AbstractFloatingView;
|
||||
import com.android.uiuios.Launcher;
|
||||
import com.android.uiuios.LauncherState;
|
||||
import com.android.uiuios.LauncherState.ScaleAndTranslation;
|
||||
import com.android.uiuios.LauncherStateManager;
|
||||
import com.android.uiuios.LauncherStateManager.StateHandler;
|
||||
import com.android.uiuios.QuickstepAppTransitionManagerImpl;
|
||||
import com.android.uiuios.Utilities;
|
||||
import com.android.uiuios.proxy.ProxyActivityStarter;
|
||||
import com.android.uiuios.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);
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.uiuios.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);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.uiuios.uioverrides.plugins;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.android.uiuios.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();
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.uiuios.uioverrides.plugins;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.android.uiuios.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() {
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.uiuios.uioverrides.plugins;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.uiuios.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);
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.uiuios.uioverrides.states;
|
||||
|
||||
import static com.android.uiuios.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
|
||||
import static com.android.uiuios.anim.Interpolators.DEACCEL_2;
|
||||
|
||||
import com.android.uiuios.AbstractFloatingView;
|
||||
import com.android.uiuios.Launcher;
|
||||
import com.android.uiuios.LauncherState;
|
||||
import com.android.uiuios.allapps.AllAppsContainerView;
|
||||
import com.android.uiuios.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;
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package com.android.uiuios.uioverrides.touchcontrollers;
|
||||
|
||||
import static com.android.uiuios.LauncherState.NORMAL;
|
||||
import static com.android.uiuios.LauncherState.OVERVIEW;
|
||||
import static com.android.uiuios.Utilities.EDGE_NAV_BAR;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.uiuios.AbstractFloatingView;
|
||||
import com.android.uiuios.Launcher;
|
||||
import com.android.uiuios.LauncherState;
|
||||
import com.android.uiuios.LauncherStateManager.AnimationComponents;
|
||||
import com.android.uiuios.touch.AbstractStateChangeTouchController;
|
||||
import com.android.uiuios.touch.SwipeDetector;
|
||||
import com.android.uiuios.userevent.nano.LauncherLogProto;
|
||||
import com.android.uiuios.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,313 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.uiuios.uioverrides.touchcontrollers;
|
||||
|
||||
import static com.android.uiuios.AbstractFloatingView.TYPE_ACCESSIBLE;
|
||||
import static com.android.uiuios.LauncherState.ALL_APPS;
|
||||
import static com.android.uiuios.LauncherState.NORMAL;
|
||||
import static com.android.uiuios.LauncherState.OVERVIEW;
|
||||
import static com.android.uiuios.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
|
||||
import static com.android.uiuios.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
|
||||
import static com.android.uiuios.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
|
||||
import static com.android.uiuios.anim.Interpolators.ACCEL;
|
||||
import static com.android.uiuios.anim.Interpolators.DEACCEL;
|
||||
import static com.android.uiuios.anim.Interpolators.LINEAR;
|
||||
import static com.android.uiuios.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.uiuios.AbstractFloatingView;
|
||||
import com.android.uiuios.DeviceProfile;
|
||||
import com.android.uiuios.Launcher;
|
||||
import com.android.uiuios.LauncherState;
|
||||
import com.android.uiuios.LauncherStateManager.AnimationComponents;
|
||||
import com.android.uiuios.allapps.AllAppsTransitionController;
|
||||
import com.android.uiuios.anim.AnimatorPlaybackController;
|
||||
import com.android.uiuios.anim.AnimatorSetBuilder;
|
||||
import com.android.uiuios.anim.Interpolators;
|
||||
import com.android.uiuios.touch.AbstractStateChangeTouchController;
|
||||
import com.android.uiuios.touch.SwipeDetector;
|
||||
import com.android.uiuios.uioverrides.states.OverviewState;
|
||||
import com.android.uiuios.userevent.nano.LauncherLogProto.Action.Touch;
|
||||
import com.android.uiuios.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.uiuios.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.uiuios.AbstractFloatingView;
|
||||
import com.android.uiuios.DeviceProfile;
|
||||
import com.android.uiuios.Launcher;
|
||||
import com.android.uiuios.LauncherState;
|
||||
import com.android.uiuios.touch.TouchEventTranslator;
|
||||
import com.android.uiuios.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;
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.uiuios.BaseDraggingActivity;
|
||||
import com.android.uiuios.DeviceProfile;
|
||||
import com.android.uiuios.anim.AnimatorPlaybackController;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
import com.android.quickstep.util.RemoteAnimationTargetSet;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public interface ActivityControlHelper<T extends BaseDraggingActivity> {
|
||||
|
||||
void onTransitionCancelled(T activity, boolean activityVisible);
|
||||
|
||||
int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect);
|
||||
|
||||
void onSwipeUpToRecentsComplete(T activity);
|
||||
|
||||
default void onSwipeUpToHomeComplete(T activity) { }
|
||||
void onAssistantVisibilityChanged(float visibility);
|
||||
|
||||
@NonNull HomeAnimationFactory prepareHomeUI(T activity);
|
||||
|
||||
AnimationFactory prepareRecentsUI(T activity, boolean activityVisible,
|
||||
boolean animateActivity, Consumer<AnimatorPlaybackController> callback);
|
||||
|
||||
ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
|
||||
|
||||
@Nullable
|
||||
T getCreatedActivity();
|
||||
|
||||
default boolean isResumed() {
|
||||
BaseDraggingActivity activity = getCreatedActivity();
|
||||
return activity != null && activity.hasBeenResumed();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Nullable
|
||||
<T extends View> T getVisibleRecentsView();
|
||||
|
||||
@UiThread
|
||||
boolean switchToRecentsIfVisible(Runnable onCompleteCallback);
|
||||
|
||||
Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target);
|
||||
|
||||
boolean shouldMinimizeSplitScreen();
|
||||
|
||||
default boolean deferStartingActivity(Region activeNavBarRegion, MotionEvent ev) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for containerType in {@link com.android.uiuios.logging.UserEventDispatcher}
|
||||
*/
|
||||
int getContainerType();
|
||||
|
||||
boolean isInLiveTileMode();
|
||||
|
||||
void onLaunchTaskFailed(T activity);
|
||||
|
||||
void onLaunchTaskSuccess(T activity);
|
||||
|
||||
interface ActivityInitListener {
|
||||
|
||||
void register();
|
||||
|
||||
void unregister();
|
||||
|
||||
void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
|
||||
Context context, Handler handler, long duration);
|
||||
}
|
||||
|
||||
interface AnimationFactory {
|
||||
|
||||
enum ShelfAnimState {
|
||||
HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
|
||||
|
||||
ShelfAnimState(boolean shouldPreformHaptic) {
|
||||
this.shouldPreformHaptic = shouldPreformHaptic;
|
||||
}
|
||||
|
||||
public final boolean shouldPreformHaptic;
|
||||
}
|
||||
|
||||
default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { }
|
||||
|
||||
void createActivityController(long transitionLength);
|
||||
|
||||
default void adjustActivityControllerInterpolators() { }
|
||||
|
||||
default void onTransitionCancelled() { }
|
||||
|
||||
default void setShelfState(ShelfAnimState animState, Interpolator interpolator,
|
||||
long duration) { }
|
||||
|
||||
/**
|
||||
* @param attached Whether to show RecentsView alongside the app window. If false, recents
|
||||
* will be hidden by some property we can animate, e.g. alpha.
|
||||
* @param animate Whether to animate recents to/from its new attached state.
|
||||
*/
|
||||
default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
|
||||
}
|
||||
|
||||
interface HomeAnimationFactory {
|
||||
|
||||
/** Return the floating view that will animate in sync with the closing window. */
|
||||
default @Nullable View getFloatingView() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull RectF getWindowTargetRect();
|
||||
|
||||
@NonNull AnimatorPlaybackController createActivityAnimationToHome();
|
||||
|
||||
default void playAtomicAnimation(float velocity) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.util.FloatProperty;
|
||||
|
||||
/**
|
||||
* A mutable float which allows animating the value
|
||||
*/
|
||||
public class AnimatedFloat {
|
||||
|
||||
public static FloatProperty<AnimatedFloat> VALUE = new FloatProperty<AnimatedFloat>("value") {
|
||||
@Override
|
||||
public void setValue(AnimatedFloat obj, float v) {
|
||||
obj.updateValue(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(AnimatedFloat obj) {
|
||||
return obj.value;
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mUpdateCallback;
|
||||
private ObjectAnimator mValueAnimator;
|
||||
|
||||
public float value;
|
||||
|
||||
public AnimatedFloat(Runnable updateCallback) {
|
||||
mUpdateCallback = updateCallback;
|
||||
}
|
||||
|
||||
public ObjectAnimator animateToValue(float start, float end) {
|
||||
cancelAnimation();
|
||||
mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, start, end);
|
||||
mValueAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
if (mValueAnimator == animator) {
|
||||
mValueAnimator = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
return mValueAnimator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the value and calls the callback.
|
||||
* Note that the value can be directly accessed as well to avoid notifying the callback.
|
||||
*/
|
||||
public void updateValue(float v) {
|
||||
if (Float.compare(v, value) != 0) {
|
||||
value = v;
|
||||
mUpdateCallback.run();
|
||||
}
|
||||
}
|
||||
|
||||
public void cancelAnimation() {
|
||||
if (mValueAnimator != null) {
|
||||
mValueAnimator.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void finishAnimation() {
|
||||
if (mValueAnimator != null && mValueAnimator.isRunning()) {
|
||||
mValueAnimator.end();
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectAnimator getCurrentAnimation() {
|
||||
return mValueAnimator;
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
|
||||
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.uiuios.AbstractFloatingView;
|
||||
import com.android.uiuios.BaseDraggingActivity;
|
||||
import com.android.uiuios.DeviceProfile;
|
||||
import com.android.uiuios.InvariantDeviceProfile;
|
||||
import com.android.uiuios.R;
|
||||
import com.android.uiuios.uioverrides.UiFactory;
|
||||
import com.android.uiuios.util.SystemUiController;
|
||||
import com.android.uiuios.util.Themes;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* A base fallback recents activity that provides support for device profile changes, activity
|
||||
* lifecycle tracking, and basic input handling from recents.
|
||||
*
|
||||
* This class is only used as a fallback in case the default launcher does not have a recents
|
||||
* implementation.
|
||||
*/
|
||||
public abstract class BaseRecentsActivity extends BaseDraggingActivity {
|
||||
|
||||
private Configuration mOldConfig;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mOldConfig = new Configuration(getResources().getConfiguration());
|
||||
initDeviceProfile();
|
||||
initViews();
|
||||
|
||||
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
|
||||
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
|
||||
RecentsActivityTracker.onRecentsActivityCreate(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init drag layer and overview panel views.
|
||||
*/
|
||||
abstract protected void initViews();
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
int diff = newConfig.diff(mOldConfig);
|
||||
if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
|
||||
onHandleConfigChanged();
|
||||
}
|
||||
mOldConfig.setTo(newConfig);
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic for when device configuration changes (rotation, screen size change, multi-window,
|
||||
* etc.)
|
||||
*/
|
||||
protected void onHandleConfigChanged() {
|
||||
mUserEventDispatcher = null;
|
||||
initDeviceProfile();
|
||||
|
||||
AbstractFloatingView.closeOpenViews(this, true,
|
||||
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
|
||||
dispatchDeviceProfileChanged();
|
||||
|
||||
reapplyUi();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize/update the device profile.
|
||||
*/
|
||||
private void initDeviceProfile() {
|
||||
mDeviceProfile = createDeviceProfile();
|
||||
onDeviceProfileInitiated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the device profile to use in this activity.
|
||||
* @return device profile
|
||||
*/
|
||||
protected DeviceProfile createDeviceProfile() {
|
||||
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
|
||||
|
||||
// In case we are reusing IDP, create a copy so that we don't conflict with Launcher
|
||||
// activity.
|
||||
return dp.copy(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
// Workaround for b/78520668, explicitly trim memory once UI is hidden
|
||||
onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnterAnimationComplete() {
|
||||
super.onEnterAnimationComplete();
|
||||
UiFactory.onEnterAnimationComplete(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
UiFactory.onTrimMemory(this, level);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
RecentsActivityTracker.onRecentsActivityNewIntent(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
RecentsActivityTracker.onRecentsActivityDestroy(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// TODO: Launch the task we came from
|
||||
startHome();
|
||||
}
|
||||
|
||||
public void startHome() {
|
||||
startActivity(new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||
super.dump(prefix, fd, writer, args);
|
||||
writer.println(prefix + "Misc:");
|
||||
dumpMisc(writer);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import com.android.uiuios.AppInfo;
|
||||
import com.android.uiuios.util.InstantAppResolver;
|
||||
|
||||
/**
|
||||
* Implementation of InstantAppResolver using platform APIs
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class InstantAppResolverImpl extends InstantAppResolver {
|
||||
|
||||
private static final String TAG = "InstantAppResolverImpl";
|
||||
public static final String COMPONENT_CLASS_MARKER = "@instantapp";
|
||||
|
||||
private final PackageManager mPM;
|
||||
|
||||
public InstantAppResolverImpl(Context context) {
|
||||
mPM = context.getPackageManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstantApp(ApplicationInfo info) {
|
||||
return info.isInstantApp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstantApp(AppInfo info) {
|
||||
ComponentName cn = info.getTargetComponent();
|
||||
return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.os.Build;
|
||||
import android.provider.SearchIndexablesContract.XmlResource;
|
||||
import android.provider.SearchIndexablesProvider;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.uiuios.R;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
|
||||
import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS;
|
||||
import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public class LauncherSearchIndexablesProvider extends SearchIndexablesProvider {
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryXmlResources(String[] strings) {
|
||||
MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
|
||||
ResolveInfo settingsActivity = getContext().getPackageManager().resolveActivity(
|
||||
new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
|
||||
.setPackage(getContext().getPackageName()), 0);
|
||||
cursor.newRow()
|
||||
.add(XmlResource.COLUMN_XML_RESID, R.xml.indexable_launcher_prefs)
|
||||
.add(XmlResource.COLUMN_INTENT_ACTION, Intent.ACTION_APPLICATION_PREFERENCES)
|
||||
.add(XmlResource.COLUMN_INTENT_TARGET_PACKAGE, getContext().getPackageName())
|
||||
.add(XmlResource.COLUMN_INTENT_TARGET_CLASS, settingsActivity.activityInfo.name);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryRawData(String[] projection) {
|
||||
return new MatrixCursor(INDEXABLES_RAW_COLUMNS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryNonIndexableKeys(String[] projection) {
|
||||
MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
|
||||
if (!getContext().getSystemService(LauncherApps.class).hasShortcutHostPermission()) {
|
||||
// We are not the current launcher. Hide all preferences
|
||||
try (XmlResourceParser parser = getContext().getResources()
|
||||
.getXml(R.xml.indexable_launcher_prefs)) {
|
||||
final int depth = parser.getDepth();
|
||||
final int[] attrs = new int[] { android.R.attr.key };
|
||||
int type;
|
||||
while (((type = parser.next()) != XmlPullParser.END_TAG ||
|
||||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
|
||||
if (type == XmlPullParser.START_TAG) {
|
||||
TypedArray a = getContext().obtainStyledAttributes(
|
||||
Xml.asAttributeSet(parser), attrs);
|
||||
cursor.addRow(new String[] {a.getString(0)});
|
||||
a.recycle();
|
||||
}
|
||||
}
|
||||
} catch (IOException |XmlPullParserException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager.TaskDescription;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.UserHandle;
|
||||
import android.util.LruCache;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.uiuios.FastBitmapDrawable;
|
||||
import com.android.uiuios.icons.BitmapInfo;
|
||||
import com.android.uiuios.graphics.DrawableFactory;
|
||||
import com.android.uiuios.icons.LauncherIcons;
|
||||
import com.android.systemui.shared.recents.model.IconLoader;
|
||||
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
|
||||
|
||||
/**
|
||||
* Extension of {@link IconLoader} with icon normalization support
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public class NormalizedIconLoader extends IconLoader {
|
||||
|
||||
private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
|
||||
private final DrawableFactory mDrawableFactory;
|
||||
private final boolean mDisableColorExtraction;
|
||||
|
||||
public NormalizedIconLoader(Context context, TaskKeyLruCache<Drawable> iconCache,
|
||||
LruCache<ComponentName, ActivityInfo> activityInfoCache,
|
||||
boolean disableColorExtraction) {
|
||||
super(context, iconCache, activityInfoCache);
|
||||
mDrawableFactory = DrawableFactory.INSTANCE.get(context);
|
||||
mDisableColorExtraction = disableColorExtraction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getDefaultIcon(int userId) {
|
||||
synchronized (mDefaultIcons) {
|
||||
BitmapInfo info = mDefaultIcons.get(userId);
|
||||
if (info == null) {
|
||||
info = getBitmapInfo(Resources.getSystem()
|
||||
.getDrawable(android.R.drawable.sym_def_app_icon), userId, 0, false);
|
||||
mDefaultIcons.put(userId, info);
|
||||
}
|
||||
|
||||
return new FastBitmapDrawable(info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Drawable createBadgedDrawable(Drawable drawable, int userId, TaskDescription desc) {
|
||||
return new FastBitmapDrawable(getBitmapInfo(drawable, userId, desc.getPrimaryColor(),
|
||||
false));
|
||||
}
|
||||
|
||||
private BitmapInfo getBitmapInfo(Drawable drawable, int userId,
|
||||
int primaryColor, boolean isInstantApp) {
|
||||
try (LauncherIcons la = LauncherIcons.obtain(mContext)) {
|
||||
if (mDisableColorExtraction) {
|
||||
la.disableColorExtraction();
|
||||
}
|
||||
la.setWrapperBackgroundColor(primaryColor);
|
||||
|
||||
// User version code O, so that the icon is always wrapped in an adaptive icon container
|
||||
return la.createBadgedIconBitmap(drawable, UserHandle.of(userId),
|
||||
Build.VERSION_CODES.O, isInstantApp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId,
|
||||
TaskDescription desc) {
|
||||
BitmapInfo bitmapInfo = getBitmapInfo(
|
||||
activityInfo.loadUnbadgedIcon(mContext.getPackageManager()),
|
||||
userId,
|
||||
desc.getPrimaryColor(),
|
||||
activityInfo.applicationInfo.isInstantApp());
|
||||
return mDrawableFactory.newIcon(mContext, bitmapInfo, activityInfo);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.uiuios.R;
|
||||
import com.android.uiuios.util.Preconditions;
|
||||
import com.android.uiuios.util.ResourceBasedOverride;
|
||||
|
||||
/**
|
||||
* Callbacks related to overview/quicksteps.
|
||||
*/
|
||||
public class OverviewCallbacks implements ResourceBasedOverride {
|
||||
|
||||
private static OverviewCallbacks sInstance;
|
||||
|
||||
public static OverviewCallbacks get(Context context) {
|
||||
Preconditions.assertUIThread();
|
||||
if (sInstance == null) {
|
||||
sInstance = Overrides.getObject(OverviewCallbacks.class,
|
||||
context.getApplicationContext(), R.string.overview_callbacks_class);
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void onInitOverviewTransition() { }
|
||||
|
||||
public void closeAllWindows() { }
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
||||
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
|
||||
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
|
||||
|
||||
import static com.android.uiuios.util.PackageManagerHelper.getPackageFilter;
|
||||
import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ResolveInfo;
|
||||
|
||||
import com.android.systemui.shared.system.PackageManagerWrapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Class to keep track of the current overview component based off user preferences and app updates
|
||||
* and provide callers the relevant classes.
|
||||
*/
|
||||
public final class OverviewComponentObserver {
|
||||
private final BroadcastReceiver mUserPreferenceChangeReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
updateOverviewTargets();
|
||||
}
|
||||
};
|
||||
private final BroadcastReceiver mOtherHomeAppUpdateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
updateOverviewTargets();
|
||||
}
|
||||
};
|
||||
private final Context mContext;
|
||||
private final ComponentName mMyHomeComponent;
|
||||
private String mUpdateRegisteredPackage;
|
||||
private ActivityControlHelper mActivityControlHelper;
|
||||
private Intent mOverviewIntent;
|
||||
private Intent mHomeIntent;
|
||||
private int mSystemUiStateFlags;
|
||||
private boolean mIsHomeAndOverviewSame;
|
||||
|
||||
public OverviewComponentObserver(Context context) {
|
||||
mContext = context;
|
||||
|
||||
Intent myHomeIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
.setPackage(mContext.getPackageName());
|
||||
ResolveInfo info = context.getPackageManager().resolveActivity(myHomeIntent, 0);
|
||||
mMyHomeComponent = new ComponentName(context.getPackageName(), info.activityInfo.name);
|
||||
|
||||
mContext.registerReceiver(mUserPreferenceChangeReceiver,
|
||||
new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
|
||||
updateOverviewTargets();
|
||||
}
|
||||
|
||||
public void onSystemUiStateChanged(int stateFlags) {
|
||||
boolean homeDisabledChanged = (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED)
|
||||
!= (stateFlags & SYSUI_STATE_HOME_DISABLED);
|
||||
mSystemUiStateFlags = stateFlags;
|
||||
if (homeDisabledChanged) {
|
||||
updateOverviewTargets();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update overview intent and {@link ActivityControlHelper} based off the current launcher home
|
||||
* component.
|
||||
*/
|
||||
private void updateOverviewTargets() {
|
||||
ComponentName defaultHome = PackageManagerWrapper.getInstance()
|
||||
.getHomeActivities(new ArrayList<>());
|
||||
|
||||
final String overviewIntentCategory;
|
||||
ComponentName overviewComponent;
|
||||
mHomeIntent = null;
|
||||
|
||||
if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 &&
|
||||
(defaultHome == null || mMyHomeComponent.equals(defaultHome))) {
|
||||
// User default home is same as out home app. Use Overview integrated in Launcher.
|
||||
overviewComponent = mMyHomeComponent;
|
||||
mActivityControlHelper = new LauncherActivityControllerHelper();
|
||||
mIsHomeAndOverviewSame = true;
|
||||
overviewIntentCategory = Intent.CATEGORY_HOME;
|
||||
|
||||
if (mUpdateRegisteredPackage != null) {
|
||||
// Remove any update listener as we don't care about other packages.
|
||||
mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
|
||||
mUpdateRegisteredPackage = null;
|
||||
}
|
||||
} else {
|
||||
// The default home app is a different launcher. Use the fallback Overview instead.
|
||||
overviewComponent = new ComponentName(mContext, RecentsActivity.class);
|
||||
mActivityControlHelper = new FallbackActivityControllerHelper();
|
||||
mIsHomeAndOverviewSame = false;
|
||||
overviewIntentCategory = Intent.CATEGORY_DEFAULT;
|
||||
|
||||
mHomeIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
.setComponent(defaultHome);
|
||||
// User's default home app can change as a result of package updates of this app (such
|
||||
// as uninstalling the app or removing the "Launcher" feature in an update).
|
||||
// Listen for package updates of this app (and remove any previously attached
|
||||
// package listener).
|
||||
if (defaultHome == null) {
|
||||
if (mUpdateRegisteredPackage != null) {
|
||||
mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
|
||||
}
|
||||
} else if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) {
|
||||
if (mUpdateRegisteredPackage != null) {
|
||||
mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
|
||||
}
|
||||
|
||||
mUpdateRegisteredPackage = defaultHome.getPackageName();
|
||||
mContext.registerReceiver(mOtherHomeAppUpdateReceiver, getPackageFilter(
|
||||
mUpdateRegisteredPackage, ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED,
|
||||
ACTION_PACKAGE_REMOVED));
|
||||
}
|
||||
}
|
||||
|
||||
mOverviewIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(overviewIntentCategory)
|
||||
.setComponent(overviewComponent)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (mHomeIntent == null) {
|
||||
mHomeIntent = mOverviewIntent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up any registered receivers.
|
||||
*/
|
||||
public void onDestroy() {
|
||||
mContext.unregisterReceiver(mUserPreferenceChangeReceiver);
|
||||
|
||||
if (mUpdateRegisteredPackage != null) {
|
||||
mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
|
||||
mUpdateRegisteredPackage = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current intent for going to the overview activity.
|
||||
*
|
||||
* @return the overview intent
|
||||
*/
|
||||
public Intent getOverviewIntent() {
|
||||
return mOverviewIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current intent for going to the home activity.
|
||||
*/
|
||||
public Intent getHomeIntent() {
|
||||
return mHomeIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if home and overview are same activity.
|
||||
*/
|
||||
public boolean isHomeAndOverviewSame() {
|
||||
return mIsHomeAndOverviewSame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current activity control helper for managing interactions to the overview activity.
|
||||
*
|
||||
* @return the current activity control helper
|
||||
*/
|
||||
public ActivityControlHelper getActivityControlHelper() {
|
||||
return mActivityControlHelper;
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.uiuios.Utilities;
|
||||
import com.android.uiuios.allapps.DiscoveryBounce;
|
||||
import com.android.uiuios.util.MainThreadInitializedObject;
|
||||
import com.android.uiuios.util.UiThreadHelper;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
/**
|
||||
* Sets alpha for the back button
|
||||
*/
|
||||
public class OverviewInteractionState {
|
||||
|
||||
private static final String TAG = "OverviewFlags";
|
||||
|
||||
private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
|
||||
|
||||
// We do not need any synchronization for this variable as its only written on UI thread.
|
||||
public static final MainThreadInitializedObject<OverviewInteractionState> INSTANCE =
|
||||
new MainThreadInitializedObject<>(OverviewInteractionState::new);
|
||||
|
||||
private static final int MSG_SET_PROXY = 200;
|
||||
private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
|
||||
|
||||
private final Context mContext;
|
||||
private final Handler mUiHandler;
|
||||
private final Handler mBgHandler;
|
||||
|
||||
// These are updated on the background thread
|
||||
private ISystemUiProxy mISystemUiProxy;
|
||||
private float mBackButtonAlpha = 1;
|
||||
|
||||
private int mSystemUiStateFlags;
|
||||
|
||||
private OverviewInteractionState(Context context) {
|
||||
mContext = context;
|
||||
|
||||
// Data posted to the uihandler will be sent to the bghandler. Data is sent to uihandler
|
||||
// because of its high send frequency and data may be very different than the previous value
|
||||
// For example, send back alpha on uihandler to avoid flickering when setting its visibility
|
||||
mUiHandler = new Handler(this::handleUiMessage);
|
||||
mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
|
||||
|
||||
onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context)
|
||||
.addModeChangeListener(this::onNavigationModeChanged));
|
||||
}
|
||||
|
||||
public float getBackButtonAlpha() {
|
||||
return mBackButtonAlpha;
|
||||
}
|
||||
|
||||
public void setBackButtonAlpha(float alpha, boolean animate) {
|
||||
if (!modeSupportsGestures()) {
|
||||
alpha = 1;
|
||||
}
|
||||
mUiHandler.removeMessages(MSG_SET_BACK_BUTTON_ALPHA);
|
||||
mUiHandler.obtainMessage(MSG_SET_BACK_BUTTON_ALPHA, animate ? 1 : 0, 0, alpha)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
public void setSystemUiProxy(ISystemUiProxy proxy) {
|
||||
mBgHandler.obtainMessage(MSG_SET_PROXY, proxy).sendToTarget();
|
||||
}
|
||||
|
||||
public void setSystemUiStateFlags(int stateFlags) {
|
||||
mSystemUiStateFlags = stateFlags;
|
||||
}
|
||||
|
||||
public int getSystemUiStateFlags() {
|
||||
return mSystemUiStateFlags;
|
||||
}
|
||||
|
||||
private boolean handleUiMessage(Message msg) {
|
||||
if (msg.what == MSG_SET_BACK_BUTTON_ALPHA) {
|
||||
mBackButtonAlpha = (float) msg.obj;
|
||||
}
|
||||
mBgHandler.obtainMessage(msg.what, msg.arg1, msg.arg2, msg.obj).sendToTarget();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleBgMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_SET_PROXY:
|
||||
mISystemUiProxy = (ISystemUiProxy) msg.obj;
|
||||
break;
|
||||
case MSG_SET_BACK_BUTTON_ALPHA:
|
||||
applyBackButtonAlpha((float) msg.obj, msg.arg1 == 1);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void applyBackButtonAlpha(float alpha, boolean animate) {
|
||||
if (mISystemUiProxy == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mISystemUiProxy.setBackButtonAlpha(alpha, animate);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Unable to update overview back button alpha", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void onNavigationModeChanged(SysUINavigationMode.Mode mode) {
|
||||
resetHomeBounceSeenOnQuickstepEnabledFirstTime();
|
||||
}
|
||||
|
||||
private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
|
||||
if (modeSupportsGestures() && !Utilities.getPrefs(mContext).getBoolean(
|
||||
HAS_ENABLED_QUICKSTEP_ONCE, true)) {
|
||||
Utilities.getPrefs(mContext).edit()
|
||||
.putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
|
||||
.putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean modeSupportsGestures() {
|
||||
return SysUINavigationMode.getMode(mContext).hasGestures;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.uiuios.BuildConfig;
|
||||
import com.android.uiuios.MainProcessInitializer;
|
||||
import com.android.uiuios.Utilities;
|
||||
import com.android.systemui.shared.system.ThreadedRendererCompat;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class QuickstepProcessInitializer extends MainProcessInitializer {
|
||||
|
||||
private static final String TAG = "QuickstepProcessInitializer";
|
||||
private static final int HEAP_LIMIT_MB = 250;
|
||||
|
||||
public QuickstepProcessInitializer(Context context) { }
|
||||
|
||||
@Override
|
||||
protected void init(Context context) {
|
||||
if (Utilities.IS_DEBUG_DEVICE) {
|
||||
try {
|
||||
// Trigger a heap dump if the PSS reaches beyond the target heap limit
|
||||
final ActivityManager am = context.getSystemService(ActivityManager.class);
|
||||
am.setWatchHeapLimit(HEAP_LIMIT_MB * 1024 * 1024);
|
||||
} catch (SecurityException e) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for b/120550382, an external app can cause the launcher process to start for
|
||||
// a work profile user which we do not support. Disable the application immediately when we
|
||||
// detect this to be the case.
|
||||
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
|
||||
if (um.isManagedProfile()) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
pm.setApplicationEnabledSetting(context.getPackageName(),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0 /* flags */);
|
||||
Log.w(TAG, "Disabling " + BuildConfig.APPLICATION_ID
|
||||
+ ", unable to run in a managed profile");
|
||||
return;
|
||||
}
|
||||
|
||||
super.init(context);
|
||||
|
||||
// Elevate GPU priority for Quickstep and Remote animations.
|
||||
ThreadedRendererCompat.setContextPriority(ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.uiuios.testing.TestInformationHandler;
|
||||
import com.android.uiuios.testing.TestProtocol;
|
||||
import com.android.uiuios.uioverrides.states.OverviewState;
|
||||
import com.android.quickstep.util.LayoutUtils;
|
||||
|
||||
public class QuickstepTestInformationHandler extends TestInformationHandler {
|
||||
|
||||
public QuickstepTestInformationHandler(Context context) { }
|
||||
|
||||
@Override
|
||||
public Bundle call(String method) {
|
||||
final Bundle response = new Bundle();
|
||||
switch (method) {
|
||||
case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
|
||||
final float swipeHeight =
|
||||
OverviewState.getDefaultSwipeHeight(mDeviceProfile);
|
||||
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
|
||||
final float swipeHeight =
|
||||
LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile);
|
||||
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
return super.call(method);
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.util.SparseBooleanArray;
|
||||
import com.android.uiuios.MainThreadExecutor;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.BackgroundExecutor;
|
||||
import com.android.systemui.shared.system.KeyguardManagerCompat;
|
||||
import com.android.systemui.shared.system.RecentTaskInfoCompat;
|
||||
import com.android.systemui.shared.system.TaskDescriptionCompat;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Manages the recent task list from the system, caching it as necessary.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public class RecentTasksList extends TaskStackChangeListener {
|
||||
|
||||
private final KeyguardManagerCompat mKeyguardManager;
|
||||
private final MainThreadExecutor mMainThreadExecutor;
|
||||
private final BackgroundExecutor mBgThreadExecutor;
|
||||
|
||||
// The list change id, increments as the task list changes in the system
|
||||
private int mChangeId;
|
||||
// The last change id when the list was last loaded completely, must be <= the list change id
|
||||
private int mLastLoadedId;
|
||||
// The last change id was loaded with keysOnly = true
|
||||
private boolean mLastLoadHadKeysOnly;
|
||||
|
||||
ArrayList<Task> mTasks = new ArrayList<>();
|
||||
|
||||
public RecentTasksList(Context context) {
|
||||
mMainThreadExecutor = new MainThreadExecutor();
|
||||
mBgThreadExecutor = BackgroundExecutor.get();
|
||||
mKeyguardManager = new KeyguardManagerCompat(context);
|
||||
mChangeId = 1;
|
||||
ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the task keys skipping any local cache.
|
||||
*/
|
||||
public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
|
||||
// Kick off task loading in the background
|
||||
mBgThreadExecutor.submit(() -> {
|
||||
ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */);
|
||||
mMainThreadExecutor.execute(() -> callback.accept(tasks));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously fetches the list of recent tasks, reusing cached list if available.
|
||||
*
|
||||
* @param loadKeysOnly Whether to load other associated task data, or just the key
|
||||
* @param callback The callback to receive the list of recent tasks
|
||||
* @return The change id of the current task list
|
||||
*/
|
||||
public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
|
||||
final int requestLoadId = mChangeId;
|
||||
Runnable resultCallback = callback == null
|
||||
? () -> { }
|
||||
: () -> callback.accept(copyOf(mTasks));
|
||||
|
||||
if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
|
||||
// The list is up to date, callback with the same list
|
||||
mMainThreadExecutor.execute(resultCallback);
|
||||
return requestLoadId;
|
||||
}
|
||||
|
||||
// Kick off task loading in the background
|
||||
mBgThreadExecutor.submit(() -> {
|
||||
ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly);
|
||||
|
||||
mMainThreadExecutor.execute(() -> {
|
||||
mTasks = tasks;
|
||||
mLastLoadedId = requestLoadId;
|
||||
mLastLoadHadKeysOnly = loadKeysOnly;
|
||||
resultCallback.run();
|
||||
});
|
||||
});
|
||||
|
||||
return requestLoadId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the provided {@param changeId} is the latest recent tasks list id.
|
||||
*/
|
||||
public synchronized boolean isTaskListValid(int changeId) {
|
||||
return mChangeId == changeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onTaskStackChanged() {
|
||||
mChangeId++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(int taskId) {
|
||||
for (int i = mTasks.size() - 1; i >= 0; i--) {
|
||||
if (mTasks.get(i).key.id == taskId) {
|
||||
mTasks.remove(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onActivityPinned(String packageName, int userId, int taskId,
|
||||
int stackId) {
|
||||
mChangeId++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onActivityUnpinned() {
|
||||
mChangeId++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and creates a list of all the recent tasks.
|
||||
*/
|
||||
private ArrayList<Task> loadTasksInBackground(int numTasks,
|
||||
boolean loadKeysOnly) {
|
||||
int currentUserId = Process.myUserHandle().getIdentifier();
|
||||
ArrayList<Task> allTasks = new ArrayList<>();
|
||||
List<ActivityManager.RecentTaskInfo> rawTasks =
|
||||
ActivityManagerWrapper.getInstance().getRecentTasks(numTasks, currentUserId);
|
||||
// The raw tasks are given in most-recent to least-recent order, we need to reverse it
|
||||
Collections.reverse(rawTasks);
|
||||
|
||||
SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() {
|
||||
@Override
|
||||
public boolean get(int key) {
|
||||
if (indexOfKey(key) < 0) {
|
||||
// Fill the cached locked state as we fetch
|
||||
put(key, mKeyguardManager.isDeviceLocked(key));
|
||||
}
|
||||
return super.get(key);
|
||||
}
|
||||
};
|
||||
|
||||
int taskCount = rawTasks.size();
|
||||
for (int i = 0; i < taskCount; i++) {
|
||||
ActivityManager.RecentTaskInfo rawTask = rawTasks.get(i);
|
||||
RecentTaskInfoCompat t = new RecentTaskInfoCompat(rawTask);
|
||||
Task.TaskKey taskKey = new Task.TaskKey(rawTask);
|
||||
Task task;
|
||||
if (!loadKeysOnly) {
|
||||
ActivityManager.TaskDescription rawTd = t.getTaskDescription();
|
||||
TaskDescriptionCompat td = new TaskDescriptionCompat(rawTd);
|
||||
boolean isLocked = tmpLockedUsers.get(t.getUserId());
|
||||
task = new Task(taskKey, td.getPrimaryColor(), td.getBackgroundColor(),
|
||||
t.supportsSplitScreenMultiWindow(), isLocked, rawTd, t.getTopActivity());
|
||||
} else {
|
||||
task = new Task(taskKey);
|
||||
}
|
||||
allTasks.add(task);
|
||||
}
|
||||
|
||||
return allTasks;
|
||||
}
|
||||
|
||||
private ArrayList<Task> copyOf(ArrayList<Task> tasks) {
|
||||
ArrayList<Task> newTasks = new ArrayList<>();
|
||||
for (int i = 0; i < tasks.size(); i++) {
|
||||
Task t = tasks.get(i);
|
||||
newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable,
|
||||
t.isLocked, t.taskDescription, t.topActivity));
|
||||
}
|
||||
return newTasks;
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
||||
import com.android.uiuios.MainThreadExecutor;
|
||||
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
/**
|
||||
* Utility class to track create/destroy for some {@link BaseRecentsActivity}.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public class RecentsActivityTracker<T extends BaseRecentsActivity> implements ActivityInitListener {
|
||||
|
||||
private static WeakReference<BaseRecentsActivity> sCurrentActivity =
|
||||
new WeakReference<>(null);
|
||||
private static final Scheduler sScheduler = new Scheduler();
|
||||
|
||||
private final BiPredicate<T, Boolean> mOnInitListener;
|
||||
|
||||
public RecentsActivityTracker(BiPredicate<T, Boolean> onInitListener) {
|
||||
mOnInitListener = onInitListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register() {
|
||||
sScheduler.schedule(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
sScheduler.clearReference(this);
|
||||
}
|
||||
|
||||
private boolean init(T activity, boolean visible) {
|
||||
return mOnInitListener.test(activity, visible);
|
||||
}
|
||||
|
||||
public static <T extends BaseRecentsActivity> T getCurrentActivity() {
|
||||
return (T) sCurrentActivity.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
|
||||
Context context, Handler handler, long duration) {
|
||||
register();
|
||||
|
||||
Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
|
||||
context.startActivity(intent, options);
|
||||
}
|
||||
|
||||
public static void onRecentsActivityCreate(BaseRecentsActivity activity) {
|
||||
sCurrentActivity = new WeakReference<>(activity);
|
||||
sScheduler.initIfPending(activity, false);
|
||||
}
|
||||
|
||||
|
||||
public static void onRecentsActivityNewIntent(BaseRecentsActivity activity) {
|
||||
sScheduler.initIfPending(activity, activity.isStarted());
|
||||
}
|
||||
|
||||
public static void onRecentsActivityDestroy(BaseRecentsActivity activity) {
|
||||
if (sCurrentActivity.get() == activity) {
|
||||
sCurrentActivity.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Scheduler implements Runnable {
|
||||
|
||||
private WeakReference<RecentsActivityTracker> mPendingTracker = new WeakReference<>(null);
|
||||
private MainThreadExecutor mMainThreadExecutor;
|
||||
|
||||
public synchronized void schedule(RecentsActivityTracker tracker) {
|
||||
mPendingTracker = new WeakReference<>(tracker);
|
||||
if (mMainThreadExecutor == null) {
|
||||
mMainThreadExecutor = new MainThreadExecutor();
|
||||
}
|
||||
mMainThreadExecutor.execute(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
BaseRecentsActivity activity = sCurrentActivity.get();
|
||||
if (activity != null) {
|
||||
initIfPending(activity, activity.isStarted());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean initIfPending(BaseRecentsActivity activity,
|
||||
boolean alreadyOnHome) {
|
||||
RecentsActivityTracker tracker = mPendingTracker.get();
|
||||
if (tracker != null) {
|
||||
if (!tracker.init(activity, alreadyOnHome)) {
|
||||
mPendingTracker.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public synchronized boolean clearReference(RecentsActivityTracker tracker) {
|
||||
if (mPendingTracker.get() == tracker) {
|
||||
mPendingTracker.clear();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.uiuios.util.MainThreadInitializedObject;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Singleton class to load and manage recents model.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public class RecentsModel extends TaskStackChangeListener {
|
||||
|
||||
private static final String TAG = "RecentsModel";
|
||||
|
||||
// We do not need any synchronization for this variable as its only written on UI thread.
|
||||
public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
|
||||
new MainThreadInitializedObject<>(c -> new RecentsModel(c));
|
||||
|
||||
private final List<TaskThumbnailChangeListener> mThumbnailChangeListeners = new ArrayList<>();
|
||||
private final Context mContext;
|
||||
|
||||
private ISystemUiProxy mSystemUiProxy;
|
||||
|
||||
private final RecentTasksList mTaskList;
|
||||
private final TaskIconCache mIconCache;
|
||||
private final TaskThumbnailCache mThumbnailCache;
|
||||
|
||||
private RecentsModel(Context context) {
|
||||
mContext = context;
|
||||
HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache",
|
||||
Process.THREAD_PRIORITY_BACKGROUND);
|
||||
loaderThread.start();
|
||||
mTaskList = new RecentTasksList(context);
|
||||
mIconCache = new TaskIconCache(context, loaderThread.getLooper());
|
||||
mThumbnailCache = new TaskThumbnailCache(context, loaderThread.getLooper());
|
||||
ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
|
||||
}
|
||||
|
||||
public TaskIconCache getIconCache() {
|
||||
return mIconCache;
|
||||
}
|
||||
|
||||
public TaskThumbnailCache getThumbnailCache() {
|
||||
return mThumbnailCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the list of recent tasks.
|
||||
*
|
||||
* @param callback The callback to receive the task plan once its complete or null. This is
|
||||
* always called on the UI thread.
|
||||
* @return the request id associated with this call.
|
||||
*/
|
||||
public int getTasks(Consumer<ArrayList<Task>> callback) {
|
||||
return mTaskList.getTasks(false /* loadKeysOnly */, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The task id of the running task, or -1 if there is no current running task.
|
||||
*/
|
||||
public static int getRunningTaskId() {
|
||||
ActivityManager.RunningTaskInfo runningTask =
|
||||
ActivityManagerWrapper.getInstance().getRunningTask();
|
||||
return runningTask != null ? runningTask.id : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the provided {@param changeId} is the latest recent tasks list id.
|
||||
*/
|
||||
public boolean isTaskListValid(int changeId) {
|
||||
return mTaskList.isTaskListValid(changeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the task key associated with the given task id.
|
||||
*
|
||||
* @param callback The callback to receive the task key if it is found or null. This is always
|
||||
* called on the UI thread.
|
||||
*/
|
||||
public void findTaskWithId(int taskId, Consumer<Task.TaskKey> callback) {
|
||||
mTaskList.getTasks(true /* loadKeysOnly */, (tasks) -> {
|
||||
for (Task task : tasks) {
|
||||
if (task.key.id == taskId) {
|
||||
callback.accept(task.key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
callback.accept(null);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskStackChangedBackground() {
|
||||
if (!mThumbnailCache.isPreloadingEnabled()) {
|
||||
// Skip if we aren't preloading
|
||||
return;
|
||||
}
|
||||
|
||||
int currentUserId = Process.myUserHandle().getIdentifier();
|
||||
if (!checkCurrentOrManagedUserId(currentUserId, mContext)) {
|
||||
// Skip if we are not the current user
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep the cache up to date with the latest thumbnails
|
||||
int runningTaskId = RecentsModel.getRunningTaskId();
|
||||
mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> {
|
||||
for (Task task : tasks) {
|
||||
if (task.key.id == runningTaskId) {
|
||||
// Skip the running task, it's not going to have an up-to-date snapshot by the
|
||||
// time the user next enters overview
|
||||
continue;
|
||||
}
|
||||
mThumbnailCache.updateThumbnailInCache(task);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
|
||||
mThumbnailCache.updateTaskSnapShot(taskId, snapshot);
|
||||
|
||||
for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {
|
||||
Task task = mThumbnailChangeListeners.get(i).onTaskThumbnailChanged(taskId, snapshot);
|
||||
if (task != null) {
|
||||
task.thumbnail = snapshot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(int taskId) {
|
||||
Task.TaskKey dummyKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
|
||||
mThumbnailCache.remove(dummyKey);
|
||||
}
|
||||
|
||||
public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
|
||||
mSystemUiProxy = systemUiProxy;
|
||||
}
|
||||
|
||||
public ISystemUiProxy getSystemUiProxy() {
|
||||
return mSystemUiProxy;
|
||||
}
|
||||
|
||||
public void onTrimMemory(int level) {
|
||||
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
|
||||
mThumbnailCache.getHighResLoadingState().setVisible(false);
|
||||
}
|
||||
if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
|
||||
// Clear everything once we reach a low-mem situation
|
||||
mThumbnailCache.clear();
|
||||
mIconCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void onOverviewShown(boolean fromHome, String tag) {
|
||||
if (mSystemUiProxy == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mSystemUiProxy.onOverviewShown(fromHome);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(tag,
|
||||
"Failed to notify SysUI of overview shown from " + (fromHome ? "home" : "app")
|
||||
+ ": ", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) {
|
||||
mThumbnailChangeListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeThumbnailChangeListener(TaskThumbnailChangeListener listener) {
|
||||
mThumbnailChangeListeners.remove(listener);
|
||||
}
|
||||
|
||||
public interface TaskThumbnailChangeListener {
|
||||
|
||||
Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RemoteRunnable {
|
||||
|
||||
void run() throws RemoteException;
|
||||
|
||||
static void executeSafely(RemoteRunnable r) {
|
||||
try {
|
||||
r.run();
|
||||
} catch (final RemoteException e) {
|
||||
Log.e("RemoteRunnable", "Error calling remote method", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.uiuios.util.PackageManagerHelper.getPackageFilter;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.uiuios.util.MainThreadInitializedObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Observer for the resource config that specifies the navigation bar mode.
|
||||
*/
|
||||
public class SysUINavigationMode {
|
||||
|
||||
public enum Mode {
|
||||
THREE_BUTTONS(false, 0),
|
||||
TWO_BUTTONS(true, 1),
|
||||
NO_BUTTON(true, 2);
|
||||
|
||||
public final boolean hasGestures;
|
||||
public final int resValue;
|
||||
|
||||
Mode(boolean hasGestures, int resValue) {
|
||||
this.hasGestures = hasGestures;
|
||||
this.resValue = resValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static Mode getMode(Context context) {
|
||||
return INSTANCE.get(context).getMode();
|
||||
}
|
||||
|
||||
public static MainThreadInitializedObject<SysUINavigationMode> INSTANCE =
|
||||
new MainThreadInitializedObject<>(SysUINavigationMode::new);
|
||||
|
||||
private static final String TAG = "SysUINavigationMode";
|
||||
|
||||
private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
|
||||
private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
|
||||
"config_navBarInteractionMode";
|
||||
|
||||
private final Context mContext;
|
||||
private Mode mMode;
|
||||
|
||||
private final List<NavigationModeChangeListener> mChangeListeners = new ArrayList<>();
|
||||
|
||||
public SysUINavigationMode(Context context) {
|
||||
mContext = context;
|
||||
initializeMode();
|
||||
|
||||
mContext.registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Mode oldMode = mMode;
|
||||
initializeMode();
|
||||
if (mMode != oldMode) {
|
||||
dispatchModeChange();
|
||||
}
|
||||
}
|
||||
}, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
|
||||
}
|
||||
|
||||
private void initializeMode() {
|
||||
int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME);
|
||||
for(Mode m : Mode.values()) {
|
||||
if (m.resValue == modeInt) {
|
||||
mMode = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchModeChange() {
|
||||
for (NavigationModeChangeListener listener : mChangeListeners) {
|
||||
listener.onNavigationModeChanged(mMode);
|
||||
}
|
||||
}
|
||||
|
||||
public Mode addModeChangeListener(NavigationModeChangeListener listener) {
|
||||
mChangeListeners.add(listener);
|
||||
return mMode;
|
||||
}
|
||||
|
||||
public void removeModeChangeListener(NavigationModeChangeListener listener) {
|
||||
mChangeListeners.remove(listener);
|
||||
}
|
||||
|
||||
public Mode getMode() {
|
||||
return mMode;
|
||||
}
|
||||
|
||||
private static int getSystemIntegerRes(Context context, String resName) {
|
||||
Resources res = context.getResources();
|
||||
int resId = res.getIdentifier(resName, "integer", "android");
|
||||
|
||||
if (resId != 0) {
|
||||
return res.getInteger(resId);
|
||||
} else {
|
||||
Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public interface NavigationModeChangeListener {
|
||||
|
||||
void onNavigationModeChanged(Mode newMode);
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.uiuios.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.LruCache;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import com.android.uiuios.MainThreadExecutor;
|
||||
import com.android.uiuios.R;
|
||||
import com.android.uiuios.Utilities;
|
||||
import com.android.uiuios.icons.cache.HandlerRunnable;
|
||||
import com.android.uiuios.uioverrides.RecentsUiFactory;
|
||||
import com.android.uiuios.util.Preconditions;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Manages the caching of task icons and related data.
|
||||
* TODO: This class should later be merged into IconCache.
|
||||
*/
|
||||
public class TaskIconCache {
|
||||
|
||||
private final Handler mBackgroundHandler;
|
||||
private final MainThreadExecutor mMainThreadExecutor;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
|
||||
private final NormalizedIconLoader mIconLoader;
|
||||
|
||||
private final TaskKeyLruCache<Drawable> mIconCache;
|
||||
private final TaskKeyLruCache<String> mContentDescriptionCache;
|
||||
private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
|
||||
|
||||
private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
|
||||
new TaskKeyLruCache.EvictionCallback() {
|
||||
@Override
|
||||
public void onEntryEvicted(Task.TaskKey key) {
|
||||
if (key != null) {
|
||||
mActivityInfoCache.remove(key.getComponent());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public TaskIconCache(Context context, Looper backgroundLooper) {
|
||||
mBackgroundHandler = new Handler(backgroundLooper);
|
||||
mMainThreadExecutor = new MainThreadExecutor();
|
||||
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
|
||||
|
||||
Resources res = context.getResources();
|
||||
int cacheSize = res.getInteger(R.integer.recentsIconCacheSize);
|
||||
mIconCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction);
|
||||
mContentDescriptionCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction);
|
||||
mActivityInfoCache = new LruCache<>(cacheSize);
|
||||
mIconLoader = new NormalizedIconLoader(context, mIconCache, mActivityInfoCache,
|
||||
true /* disableColorExtraction */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously fetches the icon and other task data.
|
||||
*
|
||||
* @param task The task to fetch the data for
|
||||
* @param callback The callback to receive the task after its data has been populated.
|
||||
* @return A cancelable handle to the request
|
||||
*/
|
||||
public IconLoadRequest updateIconInBackground(Task task, Consumer<Task> callback) {
|
||||
Preconditions.assertUIThread();
|
||||
if (task.icon != null) {
|
||||
// Nothing to load, the icon is already loaded
|
||||
callback.accept(task);
|
||||
return null;
|
||||
}
|
||||
|
||||
IconLoadRequest request = new IconLoadRequest(mBackgroundHandler) {
|
||||
@Override
|
||||
public void run() {
|
||||
Drawable icon = mIconLoader.getIcon(task);
|
||||
String contentDescription = loadContentDescriptionInBackground(task);
|
||||
if (isCanceled()) {
|
||||
// We don't call back to the provided callback in this case
|
||||
return;
|
||||
}
|
||||
mMainThreadExecutor.execute(() -> {
|
||||
task.icon = icon;
|
||||
task.titleDescription = contentDescription;
|
||||
callback.accept(task);
|
||||
onEnd();
|
||||
});
|
||||
}
|
||||
};
|
||||
Utilities.postAsyncCallback(mBackgroundHandler, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mIconCache.evictAll();
|
||||
mContentDescriptionCache.evictAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the content description for the given {@param task}.
|
||||
*/
|
||||
private String loadContentDescriptionInBackground(Task task) {
|
||||
// Return the cached content description if it exists
|
||||
String label = mContentDescriptionCache.getAndInvalidateIfModified(task.key);
|
||||
if (label != null) {
|
||||
return label;
|
||||
}
|
||||
|
||||
// Skip loading content descriptions if accessibility is disabled unless low RAM recents
|
||||
// is enabled.
|
||||
if (!GO_LOW_RAM_RECENTS_ENABLED && !mAccessibilityManager.isEnabled()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Skip loading the content description if the activity no longer exists
|
||||
ActivityInfo activityInfo = mIconLoader.getAndUpdateActivityInfo(task.key);
|
||||
if (activityInfo == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Load the label otherwise
|
||||
label = ActivityManagerWrapper.getInstance().getBadgedContentDescription(activityInfo,
|
||||
task.key.userId, task.taskDescription);
|
||||
mContentDescriptionCache.put(task.key, label);
|
||||
return label;
|
||||
}
|
||||
|
||||
public static abstract class IconLoadRequest extends HandlerRunnable {
|
||||
IconLoadRequest(Handler handler) {
|
||||
super(handler, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import com.android.uiuios.MainThreadExecutor;
|
||||
import com.android.uiuios.R;
|
||||
import com.android.uiuios.Utilities;
|
||||
import com.android.uiuios.icons.cache.HandlerRunnable;
|
||||
import com.android.uiuios.util.Preconditions;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.Task.TaskKey;
|
||||
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class TaskThumbnailCache {
|
||||
|
||||
private final Handler mBackgroundHandler;
|
||||
private final MainThreadExecutor mMainThreadExecutor;
|
||||
|
||||
private final int mCacheSize;
|
||||
private final ThumbnailCache mCache;
|
||||
private final HighResLoadingState mHighResLoadingState;
|
||||
|
||||
public static class HighResLoadingState {
|
||||
private boolean mIsLowRamDevice;
|
||||
private boolean mVisible;
|
||||
private boolean mFlingingFast;
|
||||
private boolean mHighResLoadingEnabled;
|
||||
private ArrayList<HighResLoadingStateChangedCallback> mCallbacks = new ArrayList<>();
|
||||
|
||||
public interface HighResLoadingStateChangedCallback {
|
||||
void onHighResLoadingStateChanged(boolean enabled);
|
||||
}
|
||||
|
||||
private HighResLoadingState(Context context) {
|
||||
ActivityManager activityManager =
|
||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
mIsLowRamDevice = activityManager.isLowRamDevice();
|
||||
}
|
||||
|
||||
public void addCallback(HighResLoadingStateChangedCallback callback) {
|
||||
mCallbacks.add(callback);
|
||||
}
|
||||
|
||||
public void removeCallback(HighResLoadingStateChangedCallback callback) {
|
||||
mCallbacks.remove(callback);
|
||||
}
|
||||
|
||||
public void setVisible(boolean visible) {
|
||||
mVisible = visible;
|
||||
updateState();
|
||||
}
|
||||
|
||||
public void setFlingingFast(boolean flingingFast) {
|
||||
mFlingingFast = flingingFast;
|
||||
updateState();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return mHighResLoadingEnabled;
|
||||
}
|
||||
|
||||
private void updateState() {
|
||||
boolean prevState = mHighResLoadingEnabled;
|
||||
mHighResLoadingEnabled = !mIsLowRamDevice && mVisible && !mFlingingFast;
|
||||
if (prevState != mHighResLoadingEnabled) {
|
||||
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
|
||||
mCallbacks.get(i).onHighResLoadingStateChanged(mHighResLoadingEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TaskThumbnailCache(Context context, Looper backgroundLooper) {
|
||||
mBackgroundHandler = new Handler(backgroundLooper);
|
||||
mMainThreadExecutor = new MainThreadExecutor();
|
||||
mHighResLoadingState = new HighResLoadingState(context);
|
||||
|
||||
Resources res = context.getResources();
|
||||
mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize);
|
||||
mCache = new ThumbnailCache(mCacheSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously fetches the thumbnail for the given {@param task} and puts it in the cache.
|
||||
*/
|
||||
public void updateThumbnailInCache(Task task) {
|
||||
Preconditions.assertUIThread();
|
||||
// Fetch the thumbnail for this task and put it in the cache
|
||||
if (task.thumbnail == null) {
|
||||
updateThumbnailInBackground(task.key, true /* reducedResolution */,
|
||||
t -> task.thumbnail = t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously updates the thumbnail in the cache if it is already there.
|
||||
*/
|
||||
public void updateTaskSnapShot(int taskId, ThumbnailData thumbnail) {
|
||||
Preconditions.assertUIThread();
|
||||
mCache.updateIfAlreadyInCache(taskId, thumbnail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously fetches the icon and other task data for the given {@param task}.
|
||||
*
|
||||
* @param callback The callback to receive the task after its data has been populated.
|
||||
* @return A cancelable handle to the request
|
||||
*/
|
||||
public ThumbnailLoadRequest updateThumbnailInBackground(
|
||||
Task task, Consumer<ThumbnailData> callback) {
|
||||
Preconditions.assertUIThread();
|
||||
|
||||
boolean reducedResolution = !mHighResLoadingState.isEnabled();
|
||||
if (task.thumbnail != null && (!task.thumbnail.reducedResolution || reducedResolution)) {
|
||||
// Nothing to load, the thumbnail is already high-resolution or matches what the
|
||||
// request, so just callback
|
||||
callback.accept(task.thumbnail);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return updateThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), t -> {
|
||||
task.thumbnail = t;
|
||||
callback.accept(t);
|
||||
});
|
||||
}
|
||||
|
||||
private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean reducedResolution,
|
||||
Consumer<ThumbnailData> callback) {
|
||||
Preconditions.assertUIThread();
|
||||
|
||||
ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
|
||||
if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || reducedResolution)) {
|
||||
// Already cached, lets use that thumbnail
|
||||
callback.accept(cachedThumbnail);
|
||||
return null;
|
||||
}
|
||||
|
||||
ThumbnailLoadRequest request = new ThumbnailLoadRequest(mBackgroundHandler,
|
||||
reducedResolution) {
|
||||
@Override
|
||||
public void run() {
|
||||
ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(
|
||||
key.id, reducedResolution);
|
||||
if (isCanceled()) {
|
||||
// We don't call back to the provided callback in this case
|
||||
return;
|
||||
}
|
||||
mMainThreadExecutor.execute(() -> {
|
||||
mCache.put(key, thumbnail);
|
||||
callback.accept(thumbnail);
|
||||
onEnd();
|
||||
});
|
||||
}
|
||||
};
|
||||
Utilities.postAsyncCallback(mBackgroundHandler, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cache.
|
||||
*/
|
||||
public void clear() {
|
||||
mCache.evictAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the cached thumbnail for the given task.
|
||||
*/
|
||||
public void remove(Task.TaskKey key) {
|
||||
mCache.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The cache size.
|
||||
*/
|
||||
public int getCacheSize() {
|
||||
return mCacheSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The mutable high-res loading state.
|
||||
*/
|
||||
public HighResLoadingState getHighResLoadingState() {
|
||||
return mHighResLoadingState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether to enable background preloading of task thumbnails.
|
||||
*/
|
||||
public boolean isPreloadingEnabled() {
|
||||
return !mHighResLoadingState.mIsLowRamDevice && mHighResLoadingState.mVisible;
|
||||
}
|
||||
|
||||
public static abstract class ThumbnailLoadRequest extends HandlerRunnable {
|
||||
public final boolean reducedResolution;
|
||||
|
||||
ThumbnailLoadRequest(Handler handler, boolean reducedResolution) {
|
||||
super(handler, null);
|
||||
this.reducedResolution = reducedResolution;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ThumbnailCache extends TaskKeyLruCache<ThumbnailData> {
|
||||
|
||||
public ThumbnailCache(int cacheSize) {
|
||||
super(cacheSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cache entry if it is already present in the cache
|
||||
*/
|
||||
public void updateIfAlreadyInCache(int taskId, ThumbnailData thumbnailData) {
|
||||
ThumbnailData oldData = getCacheEntry(taskId);
|
||||
if (oldData != null) {
|
||||
putCacheEntry(taskId, thumbnailData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.uiuios.compat.LauncherAppsCompat;
|
||||
import com.android.uiuios.compat.UserManagerCompat;
|
||||
import com.android.uiuios.util.ComponentKey;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Contains helpful methods for retrieving data from {@link Task}s.
|
||||
*/
|
||||
public final class TaskUtils {
|
||||
|
||||
private static final String TAG = "TaskUtils";
|
||||
|
||||
private TaskUtils() {}
|
||||
|
||||
/**
|
||||
* TODO: remove this once we switch to getting the icon and label from IconCache.
|
||||
*/
|
||||
public static CharSequence getTitle(Context context, Task task) {
|
||||
LauncherAppsCompat launcherAppsCompat = LauncherAppsCompat.getInstance(context);
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
UserHandle user = UserHandle.of(task.key.userId);
|
||||
ApplicationInfo applicationInfo = launcherAppsCompat.getApplicationInfo(
|
||||
task.getTopComponent().getPackageName(), 0, user);
|
||||
if (applicationInfo == null) {
|
||||
Log.e(TAG, "Failed to get title for task " + task);
|
||||
return "";
|
||||
}
|
||||
return packageManager.getUserBadgedLabel(
|
||||
applicationInfo.loadLabel(packageManager), user);
|
||||
}
|
||||
|
||||
public static ComponentKey getLaunchComponentKeyForTask(Task.TaskKey taskKey) {
|
||||
final ComponentName cn = taskKey.sourceComponent != null
|
||||
? taskKey.sourceComponent
|
||||
: taskKey.getComponent();
|
||||
return new ComponentKey(cn, UserHandle.of(taskKey.userId));
|
||||
}
|
||||
|
||||
|
||||
public static boolean taskIsATargetWithMode(RemoteAnimationTargetCompat[] targets,
|
||||
int taskId, int mode) {
|
||||
for (RemoteAnimationTargetCompat target : targets) {
|
||||
if (target.mode == mode && target.taskId == taskId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean checkCurrentOrManagedUserId(int currentUserId, Context context) {
|
||||
if (currentUserId == UserHandle.myUserId()) {
|
||||
return true;
|
||||
}
|
||||
List<UserHandle> allUsers = UserManagerCompat.getInstance(context).getUserProfiles();
|
||||
for (int i = allUsers.size() - 1; i >= 0; i--) {
|
||||
if (currentUserId == allUsers.get(i).getIdentifier()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep.logging;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.stats.launcher.nano.LauncherExtension;
|
||||
import android.stats.launcher.nano.LauncherTarget;
|
||||
|
||||
import static android.stats.launcher.nano.Launcher.ALLAPPS;
|
||||
import static android.stats.launcher.nano.Launcher.HOME;
|
||||
import static android.stats.launcher.nano.Launcher.LAUNCH_APP;
|
||||
import static android.stats.launcher.nano.Launcher.LAUNCH_TASK;
|
||||
import static android.stats.launcher.nano.Launcher.BACKGROUND;
|
||||
import static android.stats.launcher.nano.Launcher.OVERVIEW;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.android.uiuios.ItemInfo;
|
||||
import com.android.uiuios.logging.StatsLogManager;
|
||||
import com.android.uiuios.logging.StatsLogUtils;
|
||||
import com.android.uiuios.userevent.nano.LauncherLogProto.Target;
|
||||
import com.android.uiuios.util.ComponentKey;
|
||||
import com.android.systemui.shared.system.StatsLogCompat;
|
||||
import com.google.protobuf.nano.MessageNano;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This method calls the StatsLog hidden method until they are made available public.
|
||||
*
|
||||
* To see if the logs are properly sent to statsd, execute following command.
|
||||
* $ adb root && adb shell statsd
|
||||
* $ adb shell cmd stats print-logs
|
||||
* $ adb logcat | grep statsd OR $ adb logcat -b stats
|
||||
*/
|
||||
public class StatsLogCompatManager extends StatsLogManager {
|
||||
|
||||
private static final int SUPPORTED_TARGET_DEPTH = 2;
|
||||
|
||||
public StatsLogCompatManager(Context context) { }
|
||||
|
||||
@Override
|
||||
public void logAppLaunch(View v, Intent intent) {
|
||||
LauncherExtension ext = new LauncherExtension();
|
||||
ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
|
||||
int srcState = mStateProvider.getCurrentState();
|
||||
fillInLauncherExtension(v, ext);
|
||||
StatsLogCompat.write(LAUNCH_APP, srcState, BACKGROUND /* dstState */,
|
||||
MessageNano.toByteArray(ext), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logTaskLaunch(View v, ComponentKey componentKey) {
|
||||
LauncherExtension ext = new LauncherExtension();
|
||||
ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
|
||||
int srcState = OVERVIEW;
|
||||
fillInLauncherExtension(v, ext);
|
||||
StatsLogCompat.write(LAUNCH_TASK, srcState, BACKGROUND /* dstState */,
|
||||
MessageNano.toByteArray(ext), true);
|
||||
}
|
||||
|
||||
public static boolean fillInLauncherExtension(View v, LauncherExtension extension) {
|
||||
StatsLogUtils.LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
|
||||
if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
|
||||
return false;
|
||||
}
|
||||
ItemInfo itemInfo = (ItemInfo) v.getTag();
|
||||
Target child = new Target();
|
||||
Target parent = new Target();
|
||||
provider.fillInLogContainerData(v, itemInfo, child, parent);
|
||||
copy(child, extension.srcTarget[0]);
|
||||
copy(parent, extension.srcTarget[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void copy(Target src, LauncherTarget dst) {
|
||||
// fill in
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify() {
|
||||
if(!(StatsLogUtils.LAUNCHER_STATE_ALLAPPS == ALLAPPS &&
|
||||
StatsLogUtils.LAUNCHER_STATE_BACKGROUND == BACKGROUND &&
|
||||
StatsLogUtils.LAUNCHER_STATE_OVERVIEW == OVERVIEW &&
|
||||
StatsLogUtils.LAUNCHER_STATE_HOME == HOME)) {
|
||||
throw new IllegalStateException(
|
||||
"StatsLogUtil constants doesn't match enums in launcher.proto");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep.logging;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import static com.android.uiuios.logging.LoggerUtils.newLauncherEvent;
|
||||
import static com.android.uiuios.userevent.nano.LauncherLogProto.ControlType.CANCEL_TARGET;
|
||||
import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
|
||||
import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
|
||||
import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
|
||||
import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
|
||||
|
||||
import com.android.uiuios.logging.UserEventDispatcher;
|
||||
import com.android.uiuios.userevent.nano.LauncherLogProto;
|
||||
import com.android.systemui.shared.system.MetricsLoggerCompat;
|
||||
|
||||
/**
|
||||
* This class handles AOSP MetricsLogger function calls and logging around
|
||||
* quickstep interactions.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class UserEventDispatcherExtension extends UserEventDispatcher {
|
||||
|
||||
public static final int ALL_APPS_PREDICTION_TIPS = 2;
|
||||
|
||||
private static final String TAG = "UserEventDispatcher";
|
||||
|
||||
public UserEventDispatcherExtension(Context context) { }
|
||||
|
||||
public void logStateChangeAction(int action, int dir, int downX, int downY,
|
||||
int srcChildTargetType, int srcParentContainerType,
|
||||
int dstContainerType, int pageIndex) {
|
||||
new MetricsLoggerCompat().visibility(MetricsLoggerCompat.OVERVIEW_ACTIVITY,
|
||||
dstContainerType == LauncherLogProto.ContainerType.TASKSWITCHER);
|
||||
super.logStateChangeAction(action, dir, downX, downY, srcChildTargetType,
|
||||
srcParentContainerType, dstContainerType, pageIndex);
|
||||
}
|
||||
|
||||
public void logActionTip(int actionType, int viewType) {
|
||||
LauncherLogProto.Action action = new LauncherLogProto.Action();
|
||||
LauncherLogProto.Target target = new LauncherLogProto.Target();
|
||||
switch(actionType) {
|
||||
case VISIBLE:
|
||||
action.type = LauncherLogProto.Action.Type.TIP;
|
||||
target.type = LauncherLogProto.Target.Type.CONTAINER;
|
||||
target.containerType = LauncherLogProto.ContainerType.TIP;
|
||||
break;
|
||||
case DISMISS:
|
||||
action.type = LauncherLogProto.Action.Type.TOUCH;
|
||||
action.touch = LauncherLogProto.Action.Touch.TAP;
|
||||
target.type = LauncherLogProto.Target.Type.CONTROL;
|
||||
target.controlType = CANCEL_TARGET;
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unexpected action type = " + actionType);
|
||||
}
|
||||
|
||||
switch(viewType) {
|
||||
case RECENTS_QUICK_SCRUB_ONBOARDING_TIP:
|
||||
target.tipType = LauncherLogProto.TipType.QUICK_SCRUB_TEXT;
|
||||
break;
|
||||
case RECENTS_SWIPE_UP_ONBOARDING_TIP:
|
||||
target.tipType = LauncherLogProto.TipType.SWIPE_UP_TEXT;
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unexpected viewType = " + viewType);
|
||||
}
|
||||
LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
|
||||
dispatchUserEvent(event, null);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep.util;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.uiuios.config.FeatureFlags;
|
||||
|
||||
/**
|
||||
* Utility class to test and check binder calls during development.
|
||||
*/
|
||||
public class BinderTracker {
|
||||
|
||||
private static final String TAG = "BinderTracker";
|
||||
|
||||
public static void start() {
|
||||
if (!FeatureFlags.IS_DOGFOOD_BUILD) {
|
||||
Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
Binder.setProxyTransactListener(new Tracker());
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
if (!FeatureFlags.IS_DOGFOOD_BUILD) {
|
||||
Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
|
||||
return;
|
||||
}
|
||||
Binder.setProxyTransactListener(null);
|
||||
}
|
||||
|
||||
private static class Tracker implements Binder.ProxyTransactListener {
|
||||
|
||||
@Override
|
||||
public Object onTransactStarted(IBinder iBinder, int code) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
Log.e(TAG, "Binder call on ui thread", new Exception());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactEnded(Object session) { }
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep.util;
|
||||
|
||||
import static com.android.systemui.shared.system.InputChannelCompat.mergeMotionEvent;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Utility class to dispatch touch events to a different class. It stores the events locally
|
||||
* until a valid dispatcher is available.
|
||||
*/
|
||||
public class CachedEventDispatcher {
|
||||
|
||||
private Consumer<MotionEvent> mConsumer;
|
||||
|
||||
private ArrayList<MotionEvent> mCache;
|
||||
private MotionEvent mLastEvent;
|
||||
|
||||
public void dispatchEvent(MotionEvent event) {
|
||||
if (mConsumer != null) {
|
||||
mConsumer.accept(event);
|
||||
} else {
|
||||
if (mLastEvent == null || !mergeMotionEvent(event, mLastEvent)) {
|
||||
// Queue event.
|
||||
if (mCache == null) {
|
||||
mCache = new ArrayList<>();
|
||||
}
|
||||
mLastEvent = MotionEvent.obtain(event);
|
||||
mCache.add(mLastEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setConsumer(Consumer<MotionEvent> consumer) {
|
||||
if (consumer == null) {
|
||||
return;
|
||||
}
|
||||
mConsumer = consumer;
|
||||
int cacheCount = mCache == null ? 0 : mCache.size();
|
||||
for (int i = 0; i < cacheCount; i++) {
|
||||
MotionEvent ev = mCache.get(i);
|
||||
mConsumer.accept(ev);
|
||||
ev.recycle();
|
||||
}
|
||||
mCache = null;
|
||||
mLastEvent = null;
|
||||
}
|
||||
|
||||
public boolean hasConsumer() {
|
||||
return mConsumer != null;
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep.util;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import com.android.uiuios.DeviceProfile;
|
||||
import com.android.uiuios.R;
|
||||
import com.android.uiuios.config.FeatureFlags;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
public class LayoutUtils {
|
||||
|
||||
private static final int MULTI_WINDOW_STRATEGY_HALF_SCREEN = 1;
|
||||
private static final int MULTI_WINDOW_STRATEGY_DEVICE_PROFILE = 2;
|
||||
|
||||
@Retention(SOURCE)
|
||||
@IntDef({MULTI_WINDOW_STRATEGY_HALF_SCREEN, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE})
|
||||
private @interface MultiWindowStrategy {}
|
||||
|
||||
public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) {
|
||||
float extraSpace;
|
||||
if (dp.isVerticalBarLayout()) {
|
||||
extraSpace = 0;
|
||||
} else {
|
||||
extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx;
|
||||
}
|
||||
calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
|
||||
}
|
||||
|
||||
public static void calculateFallbackTaskSize(Context context, DeviceProfile dp, Rect outRect) {
|
||||
calculateTaskSize(context, dp, 0, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE, outRect);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public static void calculateTaskSize(Context context, DeviceProfile dp,
|
||||
float extraVerticalSpace, @MultiWindowStrategy int multiWindowStrategy, Rect outRect) {
|
||||
float taskWidth, taskHeight, paddingHorz;
|
||||
Resources res = context.getResources();
|
||||
Rect insets = dp.getInsets();
|
||||
|
||||
if (dp.isMultiWindowMode) {
|
||||
if (multiWindowStrategy == MULTI_WINDOW_STRATEGY_HALF_SCREEN) {
|
||||
DeviceProfile fullDp = dp.getFullScreenProfile();
|
||||
// Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
|
||||
// account for system insets
|
||||
taskWidth = fullDp.availableWidthPx;
|
||||
taskHeight = fullDp.availableHeightPx;
|
||||
float halfDividerSize = res.getDimension(R.dimen.multi_window_task_divider_size)
|
||||
/ 2;
|
||||
|
||||
if (fullDp.isLandscape) {
|
||||
taskWidth = taskWidth / 2 - halfDividerSize;
|
||||
} else {
|
||||
taskHeight = taskHeight / 2 - halfDividerSize;
|
||||
}
|
||||
} else {
|
||||
// multiWindowStrategy == MULTI_WINDOW_STRATEGY_DEVICE_PROFILE
|
||||
taskWidth = dp.widthPx;
|
||||
taskHeight = dp.heightPx;
|
||||
}
|
||||
paddingHorz = res.getDimension(R.dimen.multi_window_task_card_horz_space);
|
||||
} else {
|
||||
taskWidth = dp.availableWidthPx;
|
||||
taskHeight = dp.availableHeightPx;
|
||||
paddingHorz = res.getDimension(dp.isVerticalBarLayout()
|
||||
? R.dimen.landscape_task_card_horz_space
|
||||
: R.dimen.portrait_task_card_horz_space);
|
||||
}
|
||||
|
||||
float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
|
||||
float paddingVert = res.getDimension(R.dimen.task_card_vert_space);
|
||||
|
||||
// Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
|
||||
// we override the insets ourselves.
|
||||
int launcherVisibleWidth = dp.widthPx - insets.left - insets.right;
|
||||
int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
|
||||
|
||||
float availableHeight = launcherVisibleHeight
|
||||
- topIconMargin - extraVerticalSpace - paddingVert;
|
||||
float availableWidth = launcherVisibleWidth - paddingHorz;
|
||||
|
||||
float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
|
||||
float outWidth = scale * taskWidth;
|
||||
float outHeight = scale * taskHeight;
|
||||
|
||||
// Center in the visible space
|
||||
float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
|
||||
float y = insets.top + Math.max(topIconMargin,
|
||||
(launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
|
||||
outRect.set(Math.round(x), Math.round(y),
|
||||
Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
|
||||
}
|
||||
|
||||
public static int getShelfTrackingDistance(Context context, DeviceProfile dp) {
|
||||
// Track the bottom of the window.
|
||||
int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
|
||||
int spaceBetweenShelfAndRecents = (int) context.getResources().getDimension(
|
||||
R.dimen.task_card_vert_space);
|
||||
return shelfHeight + spaceBetweenShelfAndRecents;
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.uiuios.Alarm;
|
||||
import com.android.uiuios.R;
|
||||
import com.android.uiuios.compat.AccessibilityManagerCompat;
|
||||
|
||||
/**
|
||||
* Given positions along x- or y-axis, tracks velocity and acceleration and determines when there is
|
||||
* a pause in motion.
|
||||
*/
|
||||
public class MotionPauseDetector {
|
||||
|
||||
// The percentage of the previous speed that determines whether this is a rapid deceleration.
|
||||
// The bigger this number, the easier it is to trigger the first pause.
|
||||
private static final float RAPID_DECELERATION_FACTOR = 0.6f;
|
||||
|
||||
/** If no motion is added for this amount of time, assume the motion has paused. */
|
||||
private static final long FORCE_PAUSE_TIMEOUT = 300;
|
||||
|
||||
/**
|
||||
* After {@link #makePauseHarderToTrigger()}, must
|
||||
* move slowly for this long to trigger a pause.
|
||||
*/
|
||||
private static final long HARDER_TRIGGER_TIMEOUT = 400;
|
||||
|
||||
private final float mSpeedVerySlow;
|
||||
private final float mSpeedSlow;
|
||||
private final float mSpeedSomewhatFast;
|
||||
private final float mSpeedFast;
|
||||
private final Alarm mForcePauseTimeout;
|
||||
private final boolean mMakePauseHarderToTrigger;
|
||||
private final Context mContext;
|
||||
|
||||
private Long mPreviousTime = null;
|
||||
private Float mPreviousPosition = null;
|
||||
private Float mPreviousVelocity = null;
|
||||
|
||||
private Float mFirstPosition = null;
|
||||
|
||||
private OnMotionPauseListener mOnMotionPauseListener;
|
||||
private boolean mIsPaused;
|
||||
// Bias more for the first pause to make it feel extra responsive.
|
||||
private boolean mHasEverBeenPaused;
|
||||
/** @see #setDisallowPause(boolean) */
|
||||
private boolean mDisallowPause;
|
||||
// Time at which speed became < mSpeedSlow (only used if mMakePauseHarderToTrigger == true).
|
||||
private long mSlowStartTime;
|
||||
|
||||
public MotionPauseDetector(Context context) {
|
||||
this(context, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param makePauseHarderToTrigger Used for gestures that require a more explicit pause.
|
||||
*/
|
||||
public MotionPauseDetector(Context context, boolean makePauseHarderToTrigger) {
|
||||
mContext = context;
|
||||
Resources res = context.getResources();
|
||||
mSpeedVerySlow = res.getDimension(R.dimen.motion_pause_detector_speed_very_slow);
|
||||
mSpeedSlow = res.getDimension(R.dimen.motion_pause_detector_speed_slow);
|
||||
mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
|
||||
mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
|
||||
mForcePauseTimeout = new Alarm();
|
||||
mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
|
||||
mMakePauseHarderToTrigger = makePauseHarderToTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get callbacks for when motion pauses and resumes.
|
||||
*/
|
||||
public void setOnMotionPauseListener(OnMotionPauseListener listener) {
|
||||
mOnMotionPauseListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param disallowPause If true, we will not detect any pauses until this is set to false again.
|
||||
*/
|
||||
public void setDisallowPause(boolean disallowPause) {
|
||||
mDisallowPause = disallowPause;
|
||||
updatePaused(mIsPaused);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes velocity and acceleration to determine whether the motion is paused.
|
||||
* @param position The x or y component of the motion being tracked.
|
||||
*
|
||||
* TODO: Use historical positions as well, e.g. {@link MotionEvent#getHistoricalY(int, int)}.
|
||||
*/
|
||||
public void addPosition(float position, long time) {
|
||||
if (mFirstPosition == null) {
|
||||
mFirstPosition = position;
|
||||
}
|
||||
mForcePauseTimeout.setAlarm(mMakePauseHarderToTrigger
|
||||
? HARDER_TRIGGER_TIMEOUT
|
||||
: FORCE_PAUSE_TIMEOUT);
|
||||
if (mPreviousTime != null && mPreviousPosition != null) {
|
||||
long changeInTime = Math.max(1, time - mPreviousTime);
|
||||
float changeInPosition = position - mPreviousPosition;
|
||||
float velocity = changeInPosition / changeInTime;
|
||||
if (mPreviousVelocity != null) {
|
||||
checkMotionPaused(velocity, mPreviousVelocity, time);
|
||||
}
|
||||
mPreviousVelocity = velocity;
|
||||
}
|
||||
mPreviousTime = time;
|
||||
mPreviousPosition = position;
|
||||
}
|
||||
|
||||
private void checkMotionPaused(float velocity, float prevVelocity, long time) {
|
||||
float speed = Math.abs(velocity);
|
||||
float previousSpeed = Math.abs(prevVelocity);
|
||||
boolean isPaused;
|
||||
if (mIsPaused) {
|
||||
// Continue to be paused until moving at a fast speed.
|
||||
isPaused = speed < mSpeedFast || previousSpeed < mSpeedFast;
|
||||
} else {
|
||||
if (velocity < 0 != prevVelocity < 0) {
|
||||
// We're just changing directions, not necessarily stopping.
|
||||
isPaused = false;
|
||||
} else {
|
||||
isPaused = speed < mSpeedVerySlow && previousSpeed < mSpeedVerySlow;
|
||||
if (!isPaused && !mHasEverBeenPaused) {
|
||||
// We want to be more aggressive about detecting the first pause to ensure it
|
||||
// feels as responsive as possible; getting two very slow speeds back to back
|
||||
// takes too long, so also check for a rapid deceleration.
|
||||
boolean isRapidDeceleration = speed < previousSpeed * RAPID_DECELERATION_FACTOR;
|
||||
isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast;
|
||||
}
|
||||
if (mMakePauseHarderToTrigger) {
|
||||
if (speed < mSpeedSlow) {
|
||||
if (mSlowStartTime == 0) {
|
||||
mSlowStartTime = time;
|
||||
}
|
||||
isPaused = time - mSlowStartTime >= HARDER_TRIGGER_TIMEOUT;
|
||||
} else {
|
||||
mSlowStartTime = 0;
|
||||
isPaused = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
updatePaused(isPaused);
|
||||
}
|
||||
|
||||
private void updatePaused(boolean isPaused) {
|
||||
if (mDisallowPause) {
|
||||
isPaused = false;
|
||||
}
|
||||
if (mIsPaused != isPaused) {
|
||||
mIsPaused = isPaused;
|
||||
if (mIsPaused) {
|
||||
AccessibilityManagerCompat.sendPauseDetectedEventToTest(mContext);
|
||||
mHasEverBeenPaused = true;
|
||||
}
|
||||
if (mOnMotionPauseListener != null) {
|
||||
mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mPreviousTime = null;
|
||||
mPreviousPosition = null;
|
||||
mPreviousVelocity = null;
|
||||
mFirstPosition = null;
|
||||
setOnMotionPauseListener(null);
|
||||
mIsPaused = mHasEverBeenPaused = false;
|
||||
mSlowStartTime = 0;
|
||||
mForcePauseTimeout.cancelAlarm();
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return mIsPaused;
|
||||
}
|
||||
|
||||
public interface OnMotionPauseListener {
|
||||
void onMotionPauseChanged(boolean isPaused);
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep.util;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Utility class to update multiple values with different interpolators and durations during
|
||||
* the same animation.
|
||||
*/
|
||||
public abstract class MultiValueUpdateListener implements ValueAnimator.AnimatorUpdateListener {
|
||||
|
||||
private final ArrayList<FloatProp> mAllProperties = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public final void onAnimationUpdate(ValueAnimator animator) {
|
||||
final float percent = animator.getAnimatedFraction();
|
||||
final float currentPlayTime = percent * animator.getDuration();
|
||||
|
||||
for (int i = mAllProperties.size() - 1; i >= 0; i--) {
|
||||
FloatProp prop = mAllProperties.get(i);
|
||||
float time = Math.max(0, currentPlayTime - prop.mDelay);
|
||||
float newPercent = Math.min(1f, time / prop.mDuration);
|
||||
newPercent = prop.mInterpolator.getInterpolation(newPercent);
|
||||
prop.value = prop.mEnd * newPercent + prop.mStart * (1 - newPercent);
|
||||
}
|
||||
onUpdate(percent);
|
||||
}
|
||||
|
||||
public abstract void onUpdate(float percent);
|
||||
|
||||
public final class FloatProp {
|
||||
|
||||
public float value;
|
||||
|
||||
private final float mStart;
|
||||
private final float mEnd;
|
||||
private final float mDelay;
|
||||
private final float mDuration;
|
||||
private final Interpolator mInterpolator;
|
||||
|
||||
public FloatProp(float start, float end, float delay, float duration, Interpolator i) {
|
||||
value = mStart = start;
|
||||
mEnd = end;
|
||||
mDelay = delay;
|
||||
mDuration = duration;
|
||||
mInterpolator = i;
|
||||
|
||||
mAllProperties.add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep.util;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.app.ActivityOptions;
|
||||
import android.os.Handler;
|
||||
|
||||
import com.android.uiuios.LauncherAnimationRunner;
|
||||
import com.android.systemui.shared.system.ActivityOptionsCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
import com.android.systemui.shared.system.TransactionCompat;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RemoteAnimationProvider {
|
||||
|
||||
static final int Z_BOOST_BASE = 800570000;
|
||||
|
||||
AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targets);
|
||||
|
||||
default ActivityOptions toActivityOptions(Handler handler, long duration) {
|
||||
LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
|
||||
false /* startAtFrontOfQueue */) {
|
||||
|
||||
@Override
|
||||
public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
|
||||
AnimationResult result) {
|
||||
result.setAnimation(createWindowAnimation(targetCompats));
|
||||
}
|
||||
};
|
||||
return ActivityOptionsCompat.makeRemoteAnimation(
|
||||
new RemoteAnimationAdapterCompat(runner, duration, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the given {@param targets} for a remote animation, and should be called with the
|
||||
* transaction from the first frame of animation.
|
||||
*
|
||||
* @param boostModeTargets The mode indicating which targets to boost in z-order above other
|
||||
* targets.
|
||||
*/
|
||||
static void prepareTargetsForFirstFrame(RemoteAnimationTargetCompat[] targets,
|
||||
TransactionCompat t, int boostModeTargets) {
|
||||
for (RemoteAnimationTargetCompat target : targets) {
|
||||
t.setLayer(target.leash, getLayer(target, boostModeTargets));
|
||||
t.show(target.leash);
|
||||
}
|
||||
}
|
||||
|
||||
static int getLayer(RemoteAnimationTargetCompat target, int boostModeTarget) {
|
||||
return target.mode == boostModeTarget
|
||||
? Z_BOOST_BASE + target.prefixOrderIndex
|
||||
: target.prefixOrderIndex;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep.util;
|
||||
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* Holds a collection of RemoteAnimationTargets, filtered by different properties.
|
||||
*/
|
||||
public class RemoteAnimationTargetSet {
|
||||
|
||||
private final Queue<SyncRtSurfaceTransactionApplierCompat> mDependentTransactionAppliers =
|
||||
new ArrayDeque<>(1);
|
||||
|
||||
public final RemoteAnimationTargetCompat[] unfilteredApps;
|
||||
public final RemoteAnimationTargetCompat[] apps;
|
||||
public final int targetMode;
|
||||
public final boolean hasRecents;
|
||||
|
||||
public RemoteAnimationTargetSet(RemoteAnimationTargetCompat[] apps, int targetMode) {
|
||||
ArrayList<RemoteAnimationTargetCompat> filteredApps = new ArrayList<>();
|
||||
boolean hasRecents = false;
|
||||
if (apps != null) {
|
||||
for (RemoteAnimationTargetCompat target : apps) {
|
||||
if (target.mode == targetMode) {
|
||||
filteredApps.add(target);
|
||||
}
|
||||
|
||||
hasRecents |= target.activityType ==
|
||||
RemoteAnimationTargetCompat.ACTIVITY_TYPE_RECENTS;
|
||||
}
|
||||
}
|
||||
|
||||
this.unfilteredApps = apps;
|
||||
this.apps = filteredApps.toArray(new RemoteAnimationTargetCompat[filteredApps.size()]);
|
||||
this.targetMode = targetMode;
|
||||
this.hasRecents = hasRecents;
|
||||
}
|
||||
|
||||
public RemoteAnimationTargetCompat findTask(int taskId) {
|
||||
for (RemoteAnimationTargetCompat target : apps) {
|
||||
if (target.taskId == taskId) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isAnimatingHome() {
|
||||
for (RemoteAnimationTargetCompat target : apps) {
|
||||
if (target.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addDependentTransactionApplier(SyncRtSurfaceTransactionApplierCompat delay) {
|
||||
mDependentTransactionAppliers.add(delay);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
SyncRtSurfaceTransactionApplierCompat applier = mDependentTransactionAppliers.poll();
|
||||
if (applier == null) {
|
||||
for (RemoteAnimationTargetCompat target : unfilteredApps) {
|
||||
target.release();
|
||||
}
|
||||
} else {
|
||||
applier.addAfterApplyCallback(this::release);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep.util;
|
||||
|
||||
import static com.android.quickstep.util.RemoteAnimationProvider.prepareTargetsForFirstFrame;
|
||||
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
||||
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
import com.android.systemui.shared.system.TransactionCompat;
|
||||
|
||||
/**
|
||||
* Animation listener which fades out the closing targets
|
||||
*/
|
||||
public class RemoteFadeOutAnimationListener implements AnimatorUpdateListener {
|
||||
|
||||
private final RemoteAnimationTargetSet mTarget;
|
||||
private boolean mFirstFrame = true;
|
||||
|
||||
public RemoteFadeOutAnimationListener(RemoteAnimationTargetCompat[] targets) {
|
||||
mTarget = new RemoteAnimationTargetSet(targets, MODE_CLOSING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
TransactionCompat t = new TransactionCompat();
|
||||
if (mFirstFrame) {
|
||||
prepareTargetsForFirstFrame(mTarget.unfilteredApps, t, MODE_CLOSING);
|
||||
mFirstFrame = false;
|
||||
}
|
||||
|
||||
float alpha = 1 - valueAnimator.getAnimatedFraction();
|
||||
for (RemoteAnimationTargetCompat app : mTarget.apps) {
|
||||
t.setAlpha(app.leash, alpha);
|
||||
}
|
||||
t.apply();
|
||||
}
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep.views;
|
||||
|
||||
import static com.android.uiuios.LauncherState.ALL_APPS_HEADER_EXTRA;
|
||||
import static com.android.uiuios.LauncherState.BACKGROUND_APP;
|
||||
import static com.android.uiuios.LauncherState.OVERVIEW;
|
||||
import static com.android.uiuios.anim.Interpolators.ACCEL;
|
||||
import static com.android.uiuios.anim.Interpolators.ACCEL_2;
|
||||
import static com.android.uiuios.anim.Interpolators.LINEAR;
|
||||
import static com.android.uiuios.icons.GraphicsUtils.setColorAlphaBound;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Path.Direction;
|
||||
import android.graphics.Path.Op;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.android.uiuios.DeviceProfile;
|
||||
import com.android.uiuios.R;
|
||||
import com.android.uiuios.Utilities;
|
||||
import com.android.uiuios.anim.Interpolators;
|
||||
import com.android.uiuios.uioverrides.states.OverviewState;
|
||||
import com.android.uiuios.util.Themes;
|
||||
import com.android.uiuios.views.ScrimView;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
|
||||
|
||||
/**
|
||||
* Scrim used for all-apps and shelf in Overview
|
||||
* In transposed layout, it behaves as a simple color scrim.
|
||||
* In portrait layout, it draws a rounded rect such that
|
||||
* From normal state to overview state, the shelf just fades in and does not move
|
||||
* From overview state to all-apps state the shelf moves up and fades in to cover the screen
|
||||
*/
|
||||
public class ShelfScrimView extends ScrimView implements NavigationModeChangeListener {
|
||||
|
||||
// If the progress is more than this, shelf follows the finger, otherwise it moves faster to
|
||||
// cover the whole screen
|
||||
private static final float SCRIM_CATCHUP_THRESHOLD = 0.2f;
|
||||
|
||||
// Temporarily needed until android.R.attr.bottomDialogCornerRadius becomes public
|
||||
private static final float BOTTOM_CORNER_RADIUS_RATIO = 2f;
|
||||
|
||||
// In transposed layout, we simply draw a flat color.
|
||||
private boolean mDrawingFlatColor;
|
||||
|
||||
// For shelf mode
|
||||
private final int mEndAlpha;
|
||||
private final float mRadius;
|
||||
private final int mMaxScrimAlpha;
|
||||
private final Paint mPaint;
|
||||
|
||||
// Mid point where the alpha changes
|
||||
private int mMidAlpha;
|
||||
private float mMidProgress;
|
||||
|
||||
private Interpolator mBeforeMidProgressColorInterpolator = ACCEL;
|
||||
private Interpolator mAfterMidProgressColorInterpolator = ACCEL;
|
||||
|
||||
private float mShiftRange;
|
||||
|
||||
private final float mShelfOffset;
|
||||
private float mTopOffset;
|
||||
private float mShelfTop;
|
||||
private float mShelfTopAtThreshold;
|
||||
|
||||
private int mShelfColor;
|
||||
private int mRemainingScreenColor;
|
||||
|
||||
private final Path mTempPath = new Path();
|
||||
private final Path mRemainingScreenPath = new Path();
|
||||
private boolean mRemainingScreenPathValid = false;
|
||||
|
||||
private Mode mSysUINavigationMode;
|
||||
|
||||
public ShelfScrimView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mMaxScrimAlpha = Math.round(OVERVIEW.getWorkspaceScrimAlpha(mLauncher) * 255);
|
||||
|
||||
mEndAlpha = Color.alpha(mEndScrim);
|
||||
mRadius = BOTTOM_CORNER_RADIUS_RATIO * Themes.getDialogCornerRadius(context);
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
mShelfOffset = context.getResources().getDimension(R.dimen.shelf_surface_offset);
|
||||
// Just assume the easiest UI for now, until we have the proper layout information.
|
||||
mDrawingFlatColor = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
mRemainingScreenPathValid = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(getContext())
|
||||
.addModeChangeListener(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
SysUINavigationMode.INSTANCE.get(getContext()).removeModeChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNavigationModeChanged(Mode newMode) {
|
||||
mSysUINavigationMode = newMode;
|
||||
// Note that these interpolators are inverted because progress goes 1 to 0.
|
||||
if (mSysUINavigationMode == Mode.NO_BUTTON) {
|
||||
// Show the shelf more quickly before reaching overview progress.
|
||||
mBeforeMidProgressColorInterpolator = ACCEL_2;
|
||||
mAfterMidProgressColorInterpolator = ACCEL;
|
||||
} else {
|
||||
mBeforeMidProgressColorInterpolator = ACCEL;
|
||||
mAfterMidProgressColorInterpolator = Interpolators.clampToProgress(ACCEL, 0.5f, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reInitUi() {
|
||||
DeviceProfile dp = mLauncher.getDeviceProfile();
|
||||
mDrawingFlatColor = dp.isVerticalBarLayout();
|
||||
|
||||
if (!mDrawingFlatColor) {
|
||||
mRemainingScreenPathValid = false;
|
||||
mShiftRange = mLauncher.getAllAppsController().getShiftRange();
|
||||
|
||||
if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
|
||||
mMidProgress = 1;
|
||||
mMidAlpha = 0;
|
||||
} else {
|
||||
mMidAlpha = Themes.getAttrInteger(getContext(), R.attr.allAppsInterimScrimAlpha);
|
||||
Rect hotseatPadding = dp.getHotseatLayoutPadding();
|
||||
int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
|
||||
- hotseatPadding.bottom - hotseatPadding.top;
|
||||
float arrowTop = Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(dp));
|
||||
mMidProgress = 1 - (arrowTop / mShiftRange);
|
||||
|
||||
}
|
||||
mTopOffset = dp.getInsets().top - mShelfOffset;
|
||||
mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
|
||||
}
|
||||
updateColors();
|
||||
updateDragHandleAlpha();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateColors() {
|
||||
super.updateColors();
|
||||
if (mDrawingFlatColor) {
|
||||
mDragHandleOffset = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
mDragHandleOffset = mShelfOffset - mDragHandleSize;
|
||||
if (mProgress >= SCRIM_CATCHUP_THRESHOLD) {
|
||||
mShelfTop = mShiftRange * mProgress + mTopOffset;
|
||||
} else {
|
||||
mShelfTop = Utilities.mapRange(mProgress / SCRIM_CATCHUP_THRESHOLD, -mRadius,
|
||||
mShelfTopAtThreshold);
|
||||
}
|
||||
|
||||
if (mProgress >= 1) {
|
||||
mRemainingScreenColor = 0;
|
||||
mShelfColor = 0;
|
||||
if (mSysUINavigationMode == Mode.NO_BUTTON
|
||||
&& mLauncher.getStateManager().getState() == BACKGROUND_APP) {
|
||||
// Show the shelf background when peeking during swipe up.
|
||||
mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha);
|
||||
}
|
||||
} else if (mProgress >= mMidProgress) {
|
||||
mRemainingScreenColor = 0;
|
||||
|
||||
int alpha = Math.round(Utilities.mapToRange(
|
||||
mProgress, mMidProgress, 1, mMidAlpha, 0, mBeforeMidProgressColorInterpolator));
|
||||
mShelfColor = setColorAlphaBound(mEndScrim, alpha);
|
||||
} else {
|
||||
mDragHandleOffset += mShiftRange * (mMidProgress - mProgress);
|
||||
|
||||
// Note that these ranges and interpolators are inverted because progress goes 1 to 0.
|
||||
int alpha = Math.round(
|
||||
Utilities.mapToRange(mProgress, (float) 0, mMidProgress, (float) mEndAlpha,
|
||||
(float) mMidAlpha, mAfterMidProgressColorInterpolator));
|
||||
mShelfColor = setColorAlphaBound(mEndScrim, alpha);
|
||||
|
||||
int remainingScrimAlpha = Math.round(
|
||||
Utilities.mapToRange(mProgress, (float) 0, mMidProgress, mMaxScrimAlpha,
|
||||
(float) 0, LINEAR));
|
||||
mRemainingScreenColor = setColorAlphaBound(mScrimColor, remainingScrimAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
drawBackground(canvas);
|
||||
drawDragHandle(canvas);
|
||||
}
|
||||
|
||||
private void drawBackground(Canvas canvas) {
|
||||
if (mDrawingFlatColor) {
|
||||
if (mCurrentFlatColor != 0) {
|
||||
canvas.drawColor(mCurrentFlatColor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (Color.alpha(mShelfColor) == 0) {
|
||||
return;
|
||||
} else if (mProgress <= 0) {
|
||||
canvas.drawColor(mShelfColor);
|
||||
return;
|
||||
}
|
||||
|
||||
int height = getHeight();
|
||||
int width = getWidth();
|
||||
// Draw the scrim over the remaining screen if needed.
|
||||
if (mRemainingScreenColor != 0) {
|
||||
if (!mRemainingScreenPathValid) {
|
||||
mTempPath.reset();
|
||||
// Using a arbitrary '+10' in the bottom to avoid any left-overs at the
|
||||
// corners due to rounding issues.
|
||||
mTempPath.addRoundRect(0, height - mRadius, width, height + mRadius + 10,
|
||||
mRadius, mRadius, Direction.CW);
|
||||
mRemainingScreenPath.reset();
|
||||
mRemainingScreenPath.addRect(0, 0, width, height, Direction.CW);
|
||||
mRemainingScreenPath.op(mTempPath, Op.DIFFERENCE);
|
||||
}
|
||||
|
||||
float offset = height - mRadius - mShelfTop;
|
||||
canvas.translate(0, -offset);
|
||||
mPaint.setColor(mRemainingScreenColor);
|
||||
canvas.drawPath(mRemainingScreenPath, mPaint);
|
||||
canvas.translate(0, offset);
|
||||
}
|
||||
|
||||
mPaint.setColor(mShelfColor);
|
||||
canvas.drawRoundRect(0, mShelfTop, width, height + mRadius, mRadius, mRadius, mPaint);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user