Merging ub-launcher3-qt-dev, build 5565340
am: 662f12fe62
Change-Id: I33f9f08c63fda77f1c49a557b6e3ac98b92d0e54
This commit is contained in:
@@ -24,6 +24,7 @@ android_library {
|
||||
srcs: [
|
||||
"tests/tapl/**/*.java",
|
||||
"src/com/android/launcher3/util/SecureSettingsObserver.java",
|
||||
"src/com/android/launcher3/ResourceUtils.java",
|
||||
"src/com/android/launcher3/TestProtocol.java",
|
||||
],
|
||||
manifest: "tests/tapl/AndroidManifest.xml",
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
<dimen name="recents_list_width">480dp</dimen>
|
||||
|
||||
<dimen name="task_item_height">90dp</dimen>
|
||||
<dimen name="task_item_top_margin">16dp</dimen>
|
||||
<dimen name="task_thumbnail_icon_horiz_margin">20dp</dimen>
|
||||
<dimen name="task_item_top_margin">24dp</dimen>
|
||||
<dimen name="task_thumbnail_icon_horiz_margin">24dp</dimen>
|
||||
|
||||
<dimen name="task_thumbnail_corner_radius">4dp</dimen>
|
||||
|
||||
<dimen name="clear_all_item_view_height">48dp</dimen>
|
||||
<dimen name="clear_all_item_view_height">52dp</dimen>
|
||||
<dimen name="clear_all_item_view_top_margin">28dp</dimen>
|
||||
<dimen name="clear_all_item_view_bottom_margin">28dp</dimen>
|
||||
<dimen name="clear_all_button_width">140dp</dimen>
|
||||
<dimen name="clear_all_button_width">160dp</dimen>
|
||||
</resources>
|
||||
@@ -1,16 +1,20 @@
|
||||
package com.android.launcher3;
|
||||
|
||||
import static com.android.launcher3.Utilities.postAsyncCallback;
|
||||
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
|
||||
import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
|
||||
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.quickstep.views.IconRecentsView;
|
||||
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
|
||||
/**
|
||||
@@ -28,6 +32,12 @@ public final class GoLauncherAppTransitionManagerImpl extends QuickstepAppTransi
|
||||
return mLauncher.getStateManager().getState().overviewUi;
|
||||
}
|
||||
|
||||
@Override
|
||||
RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
|
||||
return new GoWallpaperOpenLauncherAnimationRunner(mHandler,
|
||||
false /* startAtFrontOfQueue */, fromUnlock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
|
||||
RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
|
||||
@@ -51,4 +61,34 @@ public final class GoLauncherAppTransitionManagerImpl extends QuickstepAppTransi
|
||||
|
||||
return mLauncher.getStateManager()::reapplyState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote animation runner for animation from app to Launcher. For Go, when going to recents,
|
||||
* we need to ensure that the recents view is ready for remote animation before starting.
|
||||
*/
|
||||
private final class GoWallpaperOpenLauncherAnimationRunner extends
|
||||
WallpaperOpenLauncherAnimationRunner {
|
||||
public GoWallpaperOpenLauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue,
|
||||
boolean fromUnlock) {
|
||||
super(handler, startAtFrontOfQueue, fromUnlock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
|
||||
AnimationResult result) {
|
||||
boolean isGoingToRecents =
|
||||
taskIsATargetWithMode(targetCompats, mLauncher.getTaskId(), MODE_OPENING)
|
||||
&& (mLauncher.getStateManager().getState() == LauncherState.OVERVIEW);
|
||||
if (isGoingToRecents) {
|
||||
IconRecentsView recentsView = mLauncher.getOverviewPanel();
|
||||
if (!recentsView.isReadyForRemoteAnim()) {
|
||||
recentsView.setOnReadyForRemoteAnimCallback(() ->
|
||||
postAsyncCallback(mHandler, () -> onCreateAnimation(targetCompats, result))
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.onCreateAnimation(targetCompats, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,12 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherStateManager.StateHandler;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.graphics.RotationMode;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
|
||||
@@ -102,4 +104,8 @@ public abstract class RecentsUiFactory {
|
||||
* @param launcher the launcher activity
|
||||
*/
|
||||
public static void onLauncherStateOrResumeChanged(Launcher launcher) {}
|
||||
|
||||
public static RotationMode getRotationMode(DeviceProfile dp) {
|
||||
return RotationMode.NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,30 +15,27 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
|
||||
import static com.android.launcher3.Utilities.postAsyncCallback;
|
||||
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
|
||||
import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
|
||||
import static com.android.quickstep.views.IconRecentsView.REMOTE_APP_TO_OVERVIEW_DURATION;
|
||||
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
|
||||
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
|
||||
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.app.ActivityOptions;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.quickstep.util.MultiValueUpdateListener;
|
||||
import com.android.launcher3.LauncherAnimationRunner;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
import com.android.quickstep.util.RemoteAnimationTargetSet;
|
||||
import com.android.quickstep.views.IconRecentsView;
|
||||
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.SyncRtSurfaceTransactionApplierCompat;
|
||||
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
|
||||
|
||||
/**
|
||||
* Provider for the atomic remote window animation from the app to the overview.
|
||||
@@ -47,20 +44,27 @@ import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.
|
||||
*/
|
||||
final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> implements
|
||||
RemoteAnimationProvider {
|
||||
|
||||
private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50;
|
||||
private static final long APP_SCALE_DOWN_DURATION = 400;
|
||||
private static final String TAG = "AppToOverviewAnimationProvider";
|
||||
|
||||
private final ActivityControlHelper<T> mHelper;
|
||||
private final int mTargetTaskId;
|
||||
private IconRecentsView mRecentsView;
|
||||
private AppToOverviewAnimationListener mAnimationReadyListener;
|
||||
|
||||
AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
|
||||
mHelper = helper;
|
||||
mTargetTaskId = targetTaskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener to various points in the animation preparing to animate.
|
||||
*
|
||||
* @param listener listener
|
||||
*/
|
||||
void setAnimationListener(AppToOverviewAnimationListener listener) {
|
||||
mAnimationReadyListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when the activity is ready/initialized.
|
||||
*
|
||||
@@ -68,6 +72,9 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
|
||||
* @param wasVisible true if it was visible before
|
||||
*/
|
||||
boolean onActivityReady(T activity, Boolean wasVisible) {
|
||||
if (mAnimationReadyListener != null) {
|
||||
mAnimationReadyListener.onActivityReady(activity);
|
||||
}
|
||||
ActivityControlHelper.AnimationFactory factory =
|
||||
mHelper.prepareRecentsUI(activity, wasVisible,
|
||||
false /* animate activity */, (controller) -> {
|
||||
@@ -92,6 +99,9 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
|
||||
*/
|
||||
@Override
|
||||
public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
|
||||
if (mAnimationReadyListener != null) {
|
||||
mAnimationReadyListener.onWindowAnimationCreated();
|
||||
}
|
||||
AnimatorSet anim = new AnimatorSet();
|
||||
if (mRecentsView == null) {
|
||||
if (Log.isLoggable(TAG, Log.WARN)) {
|
||||
@@ -131,98 +141,36 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
|
||||
return anim;
|
||||
}
|
||||
|
||||
View thumbnailView = mRecentsView.getBottomThumbnailView();
|
||||
if (thumbnailView == null) {
|
||||
// This can be null if there were previously 0 tasks and the recycler view has not had
|
||||
// enough time to take in the data change, bind a new view, and lay out the new view.
|
||||
// TODO: Have a fallback to animate to
|
||||
if (Log.isLoggable(TAG, Log.WARN)) {
|
||||
Log.w(TAG, "No thumbnail view for running task. Using stub animation.");
|
||||
}
|
||||
anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
|
||||
return anim;
|
||||
if (closingAppTarget.activityType == ACTIVITY_TYPE_HOME) {
|
||||
mRecentsView.playRemoteHomeToRecentsAnimation(anim, closingAppTarget, recentsTarget);
|
||||
} else {
|
||||
mRecentsView.playRemoteAppToRecentsAnimation(anim, closingAppTarget, recentsTarget);
|
||||
}
|
||||
|
||||
playAppScaleDownAnim(anim, closingAppTarget, recentsTarget, thumbnailView);
|
||||
|
||||
return anim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate a closing app to scale down to the location of the thumbnail view in recents.
|
||||
*
|
||||
* @param anim animator set
|
||||
* @param appTarget the app surface thats closing
|
||||
* @param recentsTarget the surface containing recents
|
||||
* @param thumbnailView the thumbnail view to animate to
|
||||
*/
|
||||
private void playAppScaleDownAnim(@NonNull AnimatorSet anim,
|
||||
@NonNull RemoteAnimationTargetCompat appTarget,
|
||||
@NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) {
|
||||
|
||||
// Identify where the entering remote app should animate to.
|
||||
Rect endRect = new Rect();
|
||||
thumbnailView.getGlobalVisibleRect(endRect);
|
||||
|
||||
Rect appBounds = appTarget.sourceContainerBounds;
|
||||
|
||||
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1);
|
||||
valueAnimator.setDuration(APP_SCALE_DOWN_DURATION);
|
||||
|
||||
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
|
||||
new SyncRtSurfaceTransactionApplierCompat(thumbnailView);
|
||||
|
||||
// Keep recents visible throughout the animation.
|
||||
SurfaceParams[] params = new SurfaceParams[2];
|
||||
// Closing app should stay on top.
|
||||
int boostedMode = MODE_CLOSING;
|
||||
params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
|
||||
null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */);
|
||||
|
||||
valueAnimator.addUpdateListener(new MultiValueUpdateListener() {
|
||||
private final FloatProp mScaleX;
|
||||
private final FloatProp mScaleY;
|
||||
private final FloatProp mTranslationX;
|
||||
private final FloatProp mTranslationY;
|
||||
private final FloatProp mAlpha;
|
||||
|
||||
{
|
||||
// Scale down and move to view location.
|
||||
float endScaleX = ((float) endRect.width()) / appBounds.width();
|
||||
mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION,
|
||||
ACCEL_DEACCEL);
|
||||
float endScaleY = ((float) endRect.height()) / appBounds.height();
|
||||
mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION,
|
||||
ACCEL_DEACCEL);
|
||||
float endTranslationX = endRect.left -
|
||||
(appBounds.width() - thumbnailView.getWidth()) / 2.0f;
|
||||
mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION,
|
||||
ACCEL_DEACCEL);
|
||||
float endTranslationY = endRect.top -
|
||||
(appBounds.height() - thumbnailView.getHeight()) / 2.0f;
|
||||
mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION,
|
||||
ACCEL_DEACCEL);
|
||||
|
||||
// Fade out quietly near the end to be replaced by the real view.
|
||||
mAlpha = new FloatProp(1.0f, 0,
|
||||
APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION,
|
||||
APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2);
|
||||
}
|
||||
@Override
|
||||
public ActivityOptions toActivityOptions(Handler handler, long duration) {
|
||||
LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
|
||||
false /* startAtFrontOfQueue */) {
|
||||
|
||||
@Override
|
||||
public void onUpdate(float percent) {
|
||||
Matrix m = new Matrix();
|
||||
m.setScale(mScaleX.value, mScaleY.value,
|
||||
appBounds.width() / 2.0f, appBounds.height() / 2.0f);
|
||||
m.postTranslate(mTranslationX.value, mTranslationY.value);
|
||||
|
||||
params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m,
|
||||
null /* windowCrop */, getLayer(appTarget, boostedMode),
|
||||
0 /* cornerRadius */);
|
||||
surfaceApplier.scheduleApply(params);
|
||||
public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
|
||||
AnimationResult result) {
|
||||
IconRecentsView recentsView = mRecentsView;
|
||||
if (!recentsView.isReadyForRemoteAnim()) {
|
||||
recentsView.setOnReadyForRemoteAnimCallback(() -> postAsyncCallback(handler,
|
||||
() -> onCreateAnimation(targetCompats, result))
|
||||
);
|
||||
return;
|
||||
}
|
||||
result.setAnimation(createWindowAnimation(targetCompats));
|
||||
}
|
||||
});
|
||||
anim.play(valueAnimator);
|
||||
};
|
||||
return ActivityOptionsCompat.makeRemoteAnimation(
|
||||
new RemoteAnimationAdapterCompat(runner, duration,
|
||||
0 /* statusBarTransitionDelay */));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,6 +179,23 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
|
||||
* @return duration of animation
|
||||
*/
|
||||
long getRecentsLaunchDuration() {
|
||||
return APP_SCALE_DOWN_DURATION;
|
||||
return REMOTE_APP_TO_OVERVIEW_DURATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for various points in the app to overview animation preparing to animate.
|
||||
*/
|
||||
interface AppToOverviewAnimationListener {
|
||||
/**
|
||||
* Logic for when activity we're animating to is ready
|
||||
*
|
||||
* @param activity activity to animate to
|
||||
*/
|
||||
void onActivityReady(BaseDraggingActivity activity);
|
||||
|
||||
/**
|
||||
* Logic for when we've created the app to recents animation.
|
||||
*/
|
||||
void onWindowAnimationCreated();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +180,7 @@ public final class ContentFillItemAnimator extends SimpleItemAnimator {
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f);
|
||||
dispatchChangeFinished(viewHolder, true /* oldItem */);
|
||||
mRunningAnims.remove(anim);
|
||||
dispatchFinishedWhenDone();
|
||||
@@ -215,46 +216,43 @@ public final class ContentFillItemAnimator extends SimpleItemAnimator {
|
||||
@Override
|
||||
public void endAnimation(@NonNull ViewHolder item) {
|
||||
for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
|
||||
PendingAnimation pendAnim = mPendingAnims.get(i);
|
||||
if (pendAnim.viewHolder == item) {
|
||||
mPendingAnims.remove(i);
|
||||
switch (pendAnim.animType) {
|
||||
case ANIM_TYPE_REMOVE:
|
||||
dispatchRemoveFinished(item);
|
||||
break;
|
||||
case ANIM_TYPE_CHANGE:
|
||||
dispatchChangeFinished(item, true /* oldItem */);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
endPendingAnimation(mPendingAnims.get(i));
|
||||
mPendingAnims.remove(i);
|
||||
}
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimations() {
|
||||
if (!isRunning()) {
|
||||
return;
|
||||
}
|
||||
for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
|
||||
PendingAnimation pendAnim = mPendingAnims.get(i);
|
||||
ViewHolder item = pendAnim.viewHolder;
|
||||
switch (pendAnim.animType) {
|
||||
case ANIM_TYPE_REMOVE:
|
||||
dispatchRemoveFinished(item);
|
||||
break;
|
||||
case ANIM_TYPE_CHANGE:
|
||||
dispatchChangeFinished(item, true /* oldItem */);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
endPendingAnimation(mPendingAnims.get(i));
|
||||
mPendingAnims.remove(i);
|
||||
}
|
||||
for (int i = 0; i < mRunningAnims.size(); i++) {
|
||||
for (int i = mRunningAnims.size() - 1; i >= 0; i--) {
|
||||
ObjectAnimator anim = mRunningAnims.get(i);
|
||||
anim.end();
|
||||
// This calls the on end animation callback which will set values to their end target.
|
||||
anim.cancel();
|
||||
}
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
private void endPendingAnimation(PendingAnimation pendAnim) {
|
||||
ViewHolder item = pendAnim.viewHolder;
|
||||
switch (pendAnim.animType) {
|
||||
case ANIM_TYPE_REMOVE:
|
||||
item.itemView.setAlpha(1.0f);
|
||||
dispatchRemoveFinished(item);
|
||||
break;
|
||||
case ANIM_TYPE_CHANGE:
|
||||
CONTENT_TRANSITION_PROGRESS.set(item.itemView, 1.0f);
|
||||
dispatchChangeFinished(item, true /* oldItem */);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,6 +50,7 @@ public final class FallbackActivityControllerHelper extends
|
||||
}
|
||||
|
||||
IconRecentsView rv = activity.getOverviewPanel();
|
||||
rv.setUsingRemoteAnimation(true);
|
||||
rv.setAlpha(0);
|
||||
|
||||
return new AnimationFactory() {
|
||||
|
||||
@@ -40,6 +40,7 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel
|
||||
boolean activityVisible, boolean animateActivity,
|
||||
Consumer<AnimatorPlaybackController> callback) {
|
||||
LauncherState fromState = activity.getStateManager().getState();
|
||||
activity.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(true);
|
||||
//TODO: Implement this based off where the recents view needs to be for app => recents anim.
|
||||
return new AnimationFactory() {
|
||||
@Override
|
||||
@@ -87,6 +88,7 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel
|
||||
if (launcher == null) {
|
||||
return false;
|
||||
}
|
||||
launcher.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(false);
|
||||
launcher.getUserEventDispatcher().logActionCommand(
|
||||
LauncherLogProto.Action.Command.RECENTS_BUTTON,
|
||||
getContainerType(),
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.android.quickstep;
|
||||
import static com.android.systemui.shared.system.ActivityManagerWrapper
|
||||
.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
@@ -30,10 +29,10 @@ import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.launcher3.logging.UserEventDispatcher;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
|
||||
import com.android.quickstep.AppToOverviewAnimationProvider.AppToOverviewAnimationListener;
|
||||
import com.android.quickstep.views.IconRecentsView;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.LatencyTrackerCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
|
||||
/**
|
||||
* Helper class to handle various atomic commands for switching between Overview.
|
||||
@@ -105,7 +104,6 @@ public class OverviewCommandHelper {
|
||||
|
||||
protected final ActivityControlHelper<T> mHelper;
|
||||
private final long mCreateTime;
|
||||
private final AppToOverviewAnimationProvider<T> mAnimationProvider;
|
||||
|
||||
private final long mToggleClickedTime = SystemClock.uptimeMillis();
|
||||
private boolean mUserEventLogged;
|
||||
@@ -114,8 +112,6 @@ public class OverviewCommandHelper {
|
||||
public RecentsActivityCommand() {
|
||||
mHelper = mOverviewComponentObserver.getActivityControlHelper();
|
||||
mCreateTime = SystemClock.elapsedRealtime();
|
||||
mAnimationProvider =
|
||||
new AppToOverviewAnimationProvider<>(mHelper, RecentsModel.getRunningTaskId());
|
||||
|
||||
// Preload the plan
|
||||
mRecentsModel.getTasks(null);
|
||||
@@ -136,11 +132,37 @@ public class OverviewCommandHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
AppToOverviewAnimationProvider<T> provider =
|
||||
new AppToOverviewAnimationProvider<>(mHelper, RecentsModel.getRunningTaskId());
|
||||
provider.setAnimationListener(
|
||||
new AppToOverviewAnimationListener() {
|
||||
@Override
|
||||
public void onActivityReady(BaseDraggingActivity activity) {
|
||||
if (!mUserEventLogged) {
|
||||
activity.getUserEventDispatcher().logActionCommand(
|
||||
LauncherLogProto.Action.Command.RECENTS_BUTTON,
|
||||
mHelper.getContainerType(),
|
||||
LauncherLogProto.ContainerType.TASKSWITCHER);
|
||||
mUserEventLogged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowAnimationCreated() {
|
||||
if (LatencyTrackerCompat.isEnabled(mContext)) {
|
||||
LatencyTrackerCompat.logToggleRecents(
|
||||
(int) (SystemClock.uptimeMillis() - mToggleClickedTime));
|
||||
}
|
||||
|
||||
mListener.unregister();
|
||||
}
|
||||
});
|
||||
|
||||
// Otherwise, start overview.
|
||||
mListener = mHelper.createActivityInitListener(this::onActivityReady);
|
||||
mListener = mHelper.createActivityInitListener(provider::onActivityReady);
|
||||
mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
|
||||
this::createWindowAnimation, mContext, mMainThreadExecutor.getHandler(),
|
||||
mAnimationProvider.getRecentsLaunchDuration());
|
||||
provider, mContext, mMainThreadExecutor.getHandler(),
|
||||
provider.getRecentsLaunchDuration());
|
||||
}
|
||||
|
||||
protected boolean handleCommand(long elapsedTime) {
|
||||
@@ -155,27 +177,5 @@ public class OverviewCommandHelper {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean onActivityReady(T activity, Boolean wasVisible) {
|
||||
if (!mUserEventLogged) {
|
||||
activity.getUserEventDispatcher().logActionCommand(
|
||||
LauncherLogProto.Action.Command.RECENTS_BUTTON,
|
||||
mHelper.getContainerType(),
|
||||
LauncherLogProto.ContainerType.TASKSWITCHER);
|
||||
mUserEventLogged = true;
|
||||
}
|
||||
return mAnimationProvider.onActivityReady(activity, wasVisible);
|
||||
}
|
||||
|
||||
private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
|
||||
if (LatencyTrackerCompat.isEnabled(mContext)) {
|
||||
LatencyTrackerCompat.logToggleRecents(
|
||||
(int) (SystemClock.uptimeMillis() - mToggleClickedTime));
|
||||
}
|
||||
|
||||
mListener.unregister();
|
||||
|
||||
return mAnimationProvider.createWindowAnimation(targetCompats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public final class RecentsActivity extends BaseRecentsActivity {
|
||||
|
||||
@Override
|
||||
protected void reapplyUi() {
|
||||
//TODO: Implement this depending on how insets will affect the view.
|
||||
// No-op. Insets are automatically re-applied in the root view.
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,8 +67,8 @@ public final class RecentsActivity extends BaseRecentsActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
protected void onResume() {
|
||||
mIconRecentsView.onBeginTransitionToOverview();
|
||||
super.onStart();
|
||||
super.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,17 @@
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION;
|
||||
import static com.android.quickstep.TaskUtils.getLaunchComponentKeyForTask;
|
||||
|
||||
import android.app.ActivityOptions;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.quickstep.views.TaskItemView;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.Task.TaskKey;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
|
||||
/**
|
||||
@@ -34,10 +37,13 @@ public final class TaskActionController {
|
||||
|
||||
private final TaskListLoader mLoader;
|
||||
private final TaskAdapter mAdapter;
|
||||
private final StatsLogManager mStatsLogManager;
|
||||
|
||||
public TaskActionController(TaskListLoader loader, TaskAdapter adapter) {
|
||||
public TaskActionController(TaskListLoader loader, TaskAdapter adapter,
|
||||
StatsLogManager logManager) {
|
||||
mLoader = loader;
|
||||
mAdapter = adapter;
|
||||
mStatsLogManager = logManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,10 +62,11 @@ public final class TaskActionController {
|
||||
int width = v.getMeasuredWidth();
|
||||
int height = v.getMeasuredHeight();
|
||||
|
||||
TaskKey key = viewHolder.getTask().get().key;
|
||||
ActivityOptions opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
|
||||
ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(
|
||||
viewHolder.getTask().get().key, opts, null /* resultCallback */,
|
||||
null /* resultCallbackHandler */);
|
||||
ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts,
|
||||
null /* resultCallback */, null /* resultCallbackHandler */);
|
||||
mStatsLogManager.logTaskLaunch(null /* view */, getLaunchComponentKeyForTask(key));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,6 +78,7 @@ public final class TaskActionController {
|
||||
ActivityOptions opts = ActivityOptions.makeBasic();
|
||||
ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
|
||||
null /* resultCallback */, null /* resultCallbackHandler */);
|
||||
mStatsLogManager.logTaskLaunch(null /* view */, getLaunchComponentKeyForTask(task.key));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,6 +95,7 @@ public final class TaskActionController {
|
||||
ActivityManagerWrapper.getInstance().removeTask(task.key.id);
|
||||
mLoader.removeTask(task);
|
||||
mAdapter.notifyItemRemoved(position);
|
||||
// TODO(b/131840601): Add logging point for removal.
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
package com.android.quickstep.fallback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
@@ -30,5 +33,23 @@ public final class GoRecentsActivityRootView extends BaseDragLayer<RecentsActivi
|
||||
super(context, attrs, 1 /* alphaChannelCount */);
|
||||
// Go leaves touch control to the view itself.
|
||||
mControllers = new TouchController[0];
|
||||
setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInsets(Rect insets) {
|
||||
if (insets.equals(mInsets)) {
|
||||
return;
|
||||
}
|
||||
super.setInsets(insets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
|
||||
Insets sysInsets = insets.getSystemWindowInsets();
|
||||
setInsets(new Rect(sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom));
|
||||
return insets.consumeSystemWindowInsets();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,14 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
|
||||
|
||||
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
|
||||
import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
|
||||
import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL;
|
||||
import static com.android.quickstep.TaskAdapter.ITEM_TYPE_TASK;
|
||||
import static com.android.quickstep.TaskAdapter.MAX_TASKS_TO_DISPLAY;
|
||||
import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION;
|
||||
import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
|
||||
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
@@ -32,6 +36,7 @@ import android.animation.PropertyValuesHolder;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.ArraySet;
|
||||
@@ -40,10 +45,12 @@ import android.util.FloatProperty;
|
||||
import android.view.View;
|
||||
import android.view.ViewDebug;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.animation.PathInterpolator;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
@@ -64,7 +71,11 @@ import com.android.quickstep.TaskAdapter;
|
||||
import com.android.quickstep.TaskHolder;
|
||||
import com.android.quickstep.TaskListLoader;
|
||||
import com.android.quickstep.TaskSwipeCallback;
|
||||
import com.android.quickstep.util.MultiValueUpdateListener;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
|
||||
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -102,6 +113,22 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
|
||||
private static final float ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO = .25f;
|
||||
private static final long CLEAR_ALL_FADE_DELAY = 120;
|
||||
|
||||
private static final long REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION = 300;
|
||||
private static final long REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION = 400;
|
||||
private static final long REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY = 200;
|
||||
private static final long REMOTE_TO_RECENTS_ITEM_FADE_DURATION = 217;
|
||||
private static final long REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY = 33;
|
||||
|
||||
private static final PathInterpolator FAST_OUT_SLOW_IN_1 =
|
||||
new PathInterpolator(.4f, 0f, 0f, 1f);
|
||||
private static final PathInterpolator FAST_OUT_SLOW_IN_2 =
|
||||
new PathInterpolator(.5f, 0f, 0f, 1f);
|
||||
private static final LinearOutSlowInInterpolator OUT_SLOW_IN =
|
||||
new LinearOutSlowInInterpolator();
|
||||
|
||||
public static final long REMOTE_APP_TO_OVERVIEW_DURATION =
|
||||
REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION;
|
||||
|
||||
/**
|
||||
* A ratio representing the view's relative placement within its padded space. For example, 0
|
||||
* is top aligned and 0.5 is centered vertically.
|
||||
@@ -125,6 +152,9 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
|
||||
private View mEmptyView;
|
||||
private View mContentView;
|
||||
private boolean mTransitionedFromApp;
|
||||
private boolean mUsingRemoteAnimation;
|
||||
private boolean mStartedEnterAnimation;
|
||||
private boolean mShowStatusBarForegroundScrim;
|
||||
private AnimatorSet mLayoutAnimation;
|
||||
private final ArraySet<View> mLayingOutViews = new ArraySet<>();
|
||||
private Rect mInsets;
|
||||
@@ -154,7 +184,8 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
|
||||
mTaskLoader = new TaskListLoader(mContext);
|
||||
mTaskAdapter = new TaskAdapter(mTaskLoader);
|
||||
mTaskAdapter.setOnClearAllClickListener(view -> animateClearAllTasks());
|
||||
mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter);
|
||||
mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter,
|
||||
mActivity.getStatsLogManager());
|
||||
mTaskAdapter.setActionController(mTaskActionController);
|
||||
mTaskLayoutManager = new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */);
|
||||
RecentsModel.INSTANCE.get(context).addThumbnailChangeListener(listener);
|
||||
@@ -204,12 +235,8 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
|
||||
case ITEM_TYPE_CLEAR_ALL:
|
||||
outRect.top = (int) res.getDimension(
|
||||
R.dimen.clear_all_item_view_top_margin);
|
||||
int desiredBottomMargin = (int) res.getDimension(
|
||||
outRect.bottom = (int) res.getDimension(
|
||||
R.dimen.clear_all_item_view_bottom_margin);
|
||||
// Only add bottom margin if insets aren't enough.
|
||||
if (mInsets.bottom < desiredBottomMargin) {
|
||||
outRect.bottom = desiredBottomMargin - mInsets.bottom;
|
||||
}
|
||||
break;
|
||||
case ITEM_TYPE_TASK:
|
||||
int desiredTopMargin = (int) res.getDimension(
|
||||
@@ -270,13 +297,16 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
|
||||
* becomes visible.
|
||||
*/
|
||||
public void onBeginTransitionToOverview() {
|
||||
mStartedEnterAnimation = false;
|
||||
if (mContext.getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
|
||||
// Scroll to bottom of task in landscape mode. This is a non-issue in portrait mode as
|
||||
// all tasks should be visible to fill up the screen in portrait mode and the view will
|
||||
// not be scrollable.
|
||||
mTaskLayoutManager.scrollToPositionWithOffset(TASKS_START_POSITION, 0 /* offset */);
|
||||
}
|
||||
scheduleFadeInLayoutAnimation();
|
||||
if (!mUsingRemoteAnimation) {
|
||||
scheduleFadeInLayoutAnimation();
|
||||
}
|
||||
// Load any task changes
|
||||
if (!mTaskLoader.needsToLoad()) {
|
||||
return;
|
||||
@@ -292,16 +322,22 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
|
||||
+ "of items to animate to.");
|
||||
}
|
||||
// Possible that task list loads faster than adapter changes propagate to layout so
|
||||
// only start content fill animation if there aren't any pending adapter changes.
|
||||
if (!mTaskRecyclerView.hasPendingAdapterUpdates()) {
|
||||
// only start content fill animation if there aren't any pending adapter changes and
|
||||
// we've started the on enter layout animation.
|
||||
boolean needsContentFillAnimation =
|
||||
!mTaskRecyclerView.hasPendingAdapterUpdates() && mStartedEnterAnimation;
|
||||
if (needsContentFillAnimation) {
|
||||
// Set item animator for content filling animation. The item animator will switch
|
||||
// back to the default on completion
|
||||
mTaskRecyclerView.setItemAnimator(mLoadingContentItemAnimator);
|
||||
mTaskAdapter.notifyItemRangeRemoved(TASKS_START_POSITION + numActualItems,
|
||||
numEmptyItems - numActualItems);
|
||||
mTaskAdapter.notifyItemRangeChanged(TASKS_START_POSITION, numActualItems,
|
||||
CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT);
|
||||
} else {
|
||||
// Notify change without animating.
|
||||
mTaskAdapter.notifyDataSetChanged();
|
||||
}
|
||||
mTaskAdapter.notifyItemRangeRemoved(TASKS_START_POSITION + numActualItems,
|
||||
numEmptyItems - numActualItems);
|
||||
mTaskAdapter.notifyItemRangeChanged(TASKS_START_POSITION, numActualItems,
|
||||
CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -314,6 +350,17 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
|
||||
mTransitionedFromApp = transitionedFromApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether we're using a custom remote animation. If so, we will not do the default layout
|
||||
* animation when entering recents and instead wait for the remote app surface to be ready to
|
||||
* use.
|
||||
*
|
||||
* @param usingRemoteAnimation true if doing a remote animation, false o/w
|
||||
*/
|
||||
public void setUsingRemoteAnimation(boolean usingRemoteAnimation) {
|
||||
mUsingRemoteAnimation = usingRemoteAnimation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles input from the overview button. Launch the most recent task unless we just came from
|
||||
* the app. In that case, we launch the next most recent.
|
||||
@@ -360,22 +407,61 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
|
||||
* @param showStatusBarForegroundScrim true to show the scrim, false to hide
|
||||
*/
|
||||
public void setShowStatusBarForegroundScrim(boolean showStatusBarForegroundScrim) {
|
||||
boolean shouldShow = mInsets.top != 0 && showStatusBarForegroundScrim;
|
||||
mShowStatusBarForegroundScrim = showStatusBarForegroundScrim;
|
||||
if (mShowStatusBarForegroundScrim != showStatusBarForegroundScrim) {
|
||||
updateStatusBarScrim();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStatusBarScrim() {
|
||||
boolean shouldShow = mInsets.top != 0 && mShowStatusBarForegroundScrim;
|
||||
mActivity.getDragLayer().setForeground(shouldShow ? mStatusBarForegroundScrim : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bottom most thumbnail view to animate to.
|
||||
* Get the bottom most task view to animate to.
|
||||
*
|
||||
* @return the thumbnail view if laid out
|
||||
* @return the task view
|
||||
*/
|
||||
public @Nullable View getBottomThumbnailView() {
|
||||
ArrayList<TaskItemView> taskViews = getTaskViews();
|
||||
if (taskViews.isEmpty()) {
|
||||
return null;
|
||||
private @Nullable TaskItemView getBottomTaskView() {
|
||||
int childCount = mTaskRecyclerView.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View view = mTaskRecyclerView.getChildAt(i);
|
||||
if (mTaskRecyclerView.getChildViewHolder(view).getItemViewType() == ITEM_TYPE_TASK) {
|
||||
return (TaskItemView) view;
|
||||
}
|
||||
}
|
||||
TaskItemView view = taskViews.get(0);
|
||||
return view.getThumbnailView();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this view has processed all data changes and is ready to animate from the app to
|
||||
* the overview.
|
||||
*
|
||||
* @return true if ready to animate app to overview, false otherwise
|
||||
*/
|
||||
public boolean isReadyForRemoteAnim() {
|
||||
return !mTaskRecyclerView.hasPendingAdapterUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback for whenever this view is ready to do a remote animation from the app to
|
||||
* overview. See {@link #isReadyForRemoteAnim()}.
|
||||
*
|
||||
* @param callback callback to run when view is ready to animate
|
||||
*/
|
||||
public void setOnReadyForRemoteAnimCallback(onReadyForRemoteAnimCallback callback) {
|
||||
mTaskRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
if (isReadyForRemoteAnim()) {
|
||||
callback.onReadyForRemoteAnim();
|
||||
mTaskRecyclerView.getViewTreeObserver().
|
||||
removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -549,6 +635,255 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
|
||||
}
|
||||
});
|
||||
mLayoutAnimation.start();
|
||||
mStartedEnterAnimation = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play remote app to recents animation when the app is the home activity. We use a simple
|
||||
* cross-fade here. Note this is only used if the home activity is a separate app than the
|
||||
* recents activity.
|
||||
*
|
||||
* @param anim animator set
|
||||
* @param homeTarget the home surface thats closing
|
||||
* @param recentsTarget the surface containing recents
|
||||
*/
|
||||
public void playRemoteHomeToRecentsAnimation(@NonNull AnimatorSet anim,
|
||||
@NonNull RemoteAnimationTargetCompat homeTarget,
|
||||
@NonNull RemoteAnimationTargetCompat recentsTarget) {
|
||||
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
|
||||
new SyncRtSurfaceTransactionApplierCompat(this);
|
||||
|
||||
SurfaceParams[] params = new SurfaceParams[2];
|
||||
int boostedMode = MODE_CLOSING;
|
||||
|
||||
ValueAnimator remoteHomeAnim = ValueAnimator.ofFloat(0, 1);
|
||||
remoteHomeAnim.setDuration(REMOTE_APP_TO_OVERVIEW_DURATION);
|
||||
|
||||
remoteHomeAnim.addUpdateListener(valueAnimator -> {
|
||||
float val = (float) valueAnimator.getAnimatedValue();
|
||||
float alpha;
|
||||
RemoteAnimationTargetCompat visibleTarget;
|
||||
RemoteAnimationTargetCompat invisibleTarget;
|
||||
if (val < .5f) {
|
||||
visibleTarget = homeTarget;
|
||||
invisibleTarget = recentsTarget;
|
||||
alpha = 1 - (val * 2);
|
||||
} else {
|
||||
visibleTarget = recentsTarget;
|
||||
invisibleTarget = homeTarget;
|
||||
alpha = (val - .5f) * 2;
|
||||
}
|
||||
params[0] = new SurfaceParams(visibleTarget.leash, alpha, null /* matrix */,
|
||||
null /* windowCrop */, getLayer(visibleTarget, boostedMode),
|
||||
0 /* cornerRadius */);
|
||||
params[1] = new SurfaceParams(invisibleTarget.leash, 0.0f, null /* matrix */,
|
||||
null /* windowCrop */, getLayer(invisibleTarget, boostedMode),
|
||||
0 /* cornerRadius */);
|
||||
surfaceApplier.scheduleApply(params);
|
||||
});
|
||||
anim.play(remoteHomeAnim);
|
||||
animateFadeInLayoutAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Play remote animation from app to recents. This should scale the currently closing app down
|
||||
* to the recents thumbnail.
|
||||
*
|
||||
* @param anim animator set
|
||||
* @param appTarget the app surface thats closing
|
||||
* @param recentsTarget the surface containing recents
|
||||
*/
|
||||
public void playRemoteAppToRecentsAnimation(@NonNull AnimatorSet anim,
|
||||
@NonNull RemoteAnimationTargetCompat appTarget,
|
||||
@NonNull RemoteAnimationTargetCompat recentsTarget) {
|
||||
TaskItemView bottomView = getBottomTaskView();
|
||||
if (bottomView == null) {
|
||||
// This can be null if there were previously 0 tasks and the recycler view has not had
|
||||
// enough time to take in the data change, bind a new view, and lay out the new view.
|
||||
// TODO: Have a fallback to animate to
|
||||
anim.play(ValueAnimator.ofInt(0, 1).setDuration(REMOTE_APP_TO_OVERVIEW_DURATION));
|
||||
return;
|
||||
}
|
||||
final Matrix appMatrix = new Matrix();
|
||||
playRemoteTransYAnim(anim, appMatrix);
|
||||
playRemoteAppScaleDownAnim(anim, appMatrix, appTarget, recentsTarget,
|
||||
bottomView.getThumbnailView());
|
||||
playRemoteTaskListFadeIn(anim, bottomView);
|
||||
mStartedEnterAnimation = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play translation Y animation for the remote app to recents animation. Animates over all task
|
||||
* views as well as the closing app, easing them into their final vertical positions.
|
||||
*
|
||||
* @param anim animator set to play on
|
||||
* @param appMatrix transformation matrix for the closing app surface
|
||||
*/
|
||||
private void playRemoteTransYAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix) {
|
||||
final ArrayList<TaskItemView> views = getTaskViews();
|
||||
|
||||
// Start Y translation from about halfway through the tasks list to the bottom thumbnail.
|
||||
float taskHeight = getResources().getDimension(R.dimen.task_item_height);
|
||||
float totalTransY = -(MAX_TASKS_TO_DISPLAY / 2.0f - 1) * taskHeight;
|
||||
for (int i = 0, size = views.size(); i < size; i++) {
|
||||
views.get(i).setTranslationY(totalTransY);
|
||||
}
|
||||
|
||||
ValueAnimator transYAnim = ValueAnimator.ofFloat(totalTransY, 0);
|
||||
transYAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION);
|
||||
transYAnim.setInterpolator(FAST_OUT_SLOW_IN_2);
|
||||
transYAnim.addUpdateListener(valueAnimator -> {
|
||||
float transY = (float) valueAnimator.getAnimatedValue();
|
||||
for (int i = 0, size = views.size(); i < size; i++) {
|
||||
views.get(i).setTranslationY(transY);
|
||||
}
|
||||
appMatrix.postTranslate(0, transY - totalTransY);
|
||||
});
|
||||
transYAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
for (int i = 0, size = views.size(); i < size; i++) {
|
||||
views.get(i).setTranslationY(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
anim.play(transYAnim);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the scale down animation for the remote app to recents animation where the app surface
|
||||
* scales down to where the thumbnail is.
|
||||
*
|
||||
* @param anim animator set to play on
|
||||
* @param appMatrix transformation matrix for the app surface
|
||||
* @param appTarget closing app target
|
||||
* @param recentsTarget opening recents target
|
||||
* @param thumbnailView thumbnail view to animate to
|
||||
*/
|
||||
private void playRemoteAppScaleDownAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix,
|
||||
@NonNull RemoteAnimationTargetCompat appTarget,
|
||||
@NonNull RemoteAnimationTargetCompat recentsTarget,
|
||||
@NonNull View thumbnailView) {
|
||||
// Identify where the entering remote app should animate to.
|
||||
Rect endRect = new Rect();
|
||||
thumbnailView.getGlobalVisibleRect(endRect);
|
||||
Rect appBounds = appTarget.sourceContainerBounds;
|
||||
|
||||
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
|
||||
new SyncRtSurfaceTransactionApplierCompat(this);
|
||||
|
||||
// Keep recents visible throughout the animation.
|
||||
SurfaceParams[] params = new SurfaceParams[2];
|
||||
// Closing app should stay on top.
|
||||
int boostedMode = MODE_CLOSING;
|
||||
params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
|
||||
null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */);
|
||||
|
||||
ValueAnimator remoteAppAnim = ValueAnimator.ofInt(0, 1);
|
||||
remoteAppAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION);
|
||||
remoteAppAnim.addUpdateListener(new MultiValueUpdateListener() {
|
||||
private final FloatProp mScaleX;
|
||||
private final FloatProp mScaleY;
|
||||
private final FloatProp mTranslationX;
|
||||
private final FloatProp mTranslationY;
|
||||
private final FloatProp mAlpha;
|
||||
|
||||
{
|
||||
// Scale down and move to view location.
|
||||
float endScaleX = ((float) endRect.width()) / appBounds.width();
|
||||
mScaleX = new FloatProp(1f, endScaleX, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
|
||||
FAST_OUT_SLOW_IN_1);
|
||||
float endScaleY = ((float) endRect.height()) / appBounds.height();
|
||||
mScaleY = new FloatProp(1f, endScaleY, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
|
||||
FAST_OUT_SLOW_IN_1);
|
||||
float endTranslationX = endRect.left -
|
||||
(appBounds.width() - thumbnailView.getWidth()) / 2.0f;
|
||||
mTranslationX = new FloatProp(0, endTranslationX, 0,
|
||||
REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_1);
|
||||
float endTranslationY = endRect.top -
|
||||
(appBounds.height() - thumbnailView.getHeight()) / 2.0f;
|
||||
mTranslationY = new FloatProp(0, endTranslationY, 0,
|
||||
REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_2);
|
||||
mAlpha = new FloatProp(1.0f, 0, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
|
||||
ACCEL_2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(float percent) {
|
||||
appMatrix.preScale(mScaleX.value, mScaleY.value,
|
||||
appBounds.width() / 2.0f, appBounds.height() / 2.0f);
|
||||
appMatrix.postTranslate(mTranslationX.value, mTranslationY.value);
|
||||
|
||||
params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, appMatrix,
|
||||
null /* windowCrop */, getLayer(appTarget, boostedMode),
|
||||
0 /* cornerRadius */);
|
||||
surfaceApplier.scheduleApply(params);
|
||||
appMatrix.reset();
|
||||
}
|
||||
});
|
||||
anim.play(remoteAppAnim);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play task list fade in animation as part of remote app to recents animation. This animation
|
||||
* ensures that the task views in the recents list fade in from bottom to top.
|
||||
*
|
||||
* @param anim animator set to play on
|
||||
* @param appTaskView the task view associated with the remote app closing
|
||||
*/
|
||||
private void playRemoteTaskListFadeIn(@NonNull AnimatorSet anim,
|
||||
@NonNull TaskItemView appTaskView) {
|
||||
long delay = REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY;
|
||||
int childCount = mTaskRecyclerView.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
ValueAnimator fadeAnim = ValueAnimator.ofFloat(0, 1.0f);
|
||||
fadeAnim.setDuration(REMOTE_TO_RECENTS_ITEM_FADE_DURATION).setInterpolator(OUT_SLOW_IN);
|
||||
fadeAnim.setStartDelay(delay);
|
||||
View view = mTaskRecyclerView.getChildAt(i);
|
||||
if (Objects.equals(view, appTaskView)) {
|
||||
// Only animate icon and text for the view with snapshot animating in
|
||||
final View icon = appTaskView.getIconView();
|
||||
final View label = appTaskView.getLabelView();
|
||||
|
||||
icon.setAlpha(0.0f);
|
||||
label.setAlpha(0.0f);
|
||||
|
||||
fadeAnim.addUpdateListener(alphaVal -> {
|
||||
float val = alphaVal.getAnimatedFraction();
|
||||
|
||||
icon.setAlpha(val);
|
||||
label.setAlpha(val);
|
||||
});
|
||||
fadeAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
icon.setAlpha(1.0f);
|
||||
label.setAlpha(1.0f);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Otherwise, fade in the entire view.
|
||||
view.setAlpha(0.0f);
|
||||
fadeAnim.addUpdateListener(alphaVal -> {
|
||||
float val = alphaVal.getAnimatedFraction();
|
||||
view.setAlpha(val);
|
||||
});
|
||||
fadeAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
view.setAlpha(1.0f);
|
||||
}
|
||||
});
|
||||
}
|
||||
anim.play(fadeAnim);
|
||||
|
||||
int itemType = mTaskRecyclerView.getChildViewHolder(view).getItemViewType();
|
||||
if (itemType == ITEM_TYPE_CLEAR_ALL) {
|
||||
// Don't add delay. Clear all should animate at same time as next view.
|
||||
continue;
|
||||
}
|
||||
delay += REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -556,5 +891,16 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
|
||||
mInsets = insets;
|
||||
mTaskRecyclerView.setPadding(insets.left, insets.top, insets.right, insets.bottom);
|
||||
mTaskRecyclerView.invalidateItemDecorations();
|
||||
if (mInsets.top != 0) {
|
||||
updateStatusBarScrim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when this view is ready for a remote animation from app to overview.
|
||||
*/
|
||||
public interface onReadyForRemoteAnimCallback {
|
||||
|
||||
void onReadyForRemoteAnim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,14 @@ public final class TaskItemView extends LinearLayout {
|
||||
return mThumbnailView;
|
||||
}
|
||||
|
||||
public View getIconView() {
|
||||
return mIconView;
|
||||
}
|
||||
|
||||
public View getLabelView() {
|
||||
return mLabelView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new animation from the current task content to the specified new content. The caller
|
||||
* is responsible for the actual animation control via the property
|
||||
|
||||
@@ -78,7 +78,7 @@ public class BaseIconFactory implements AutoCloseable {
|
||||
|
||||
public IconNormalizer getNormalizer() {
|
||||
if (mNormalizer == null) {
|
||||
mNormalizer = new IconNormalizer(mContext, mIconBitmapSize);
|
||||
mNormalizer = new IconNormalizer(mIconBitmapSize);
|
||||
}
|
||||
return mNormalizer;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,10 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PathMeasure;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
import android.view.ViewDebug;
|
||||
|
||||
@@ -36,33 +38,51 @@ public class DotRenderer {
|
||||
private static final String TAG = "DotRenderer";
|
||||
|
||||
// The dot size is defined as a percentage of the app icon size.
|
||||
private static final float SIZE_PERCENTAGE = 0.38f;
|
||||
private static final float SIZE_PERCENTAGE = 0.228f;
|
||||
|
||||
// Extra scale down of the dot
|
||||
private static final float DOT_SCALE = 0.6f;
|
||||
|
||||
// Offset the dot slightly away from the icon if there's space.
|
||||
private static final float OFFSET_PERCENTAGE = 0.02f;
|
||||
|
||||
private final float mDotCenterOffset;
|
||||
private final int mOffset;
|
||||
private final float mCircleRadius;
|
||||
private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
|
||||
|
||||
private final Bitmap mBackgroundWithShadow;
|
||||
private final float mBitmapOffset;
|
||||
|
||||
public DotRenderer(int iconSizePx) {
|
||||
mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx;
|
||||
mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx);
|
||||
// Stores the center x and y position as a percentage (0 to 1) of the icon size
|
||||
private final float[] mRightDotPosition;
|
||||
private final float[] mLeftDotPosition;
|
||||
|
||||
int size = (int) (DOT_SCALE * mDotCenterOffset);
|
||||
public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) {
|
||||
int size = Math.round(SIZE_PERCENTAGE * iconSizePx);
|
||||
ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
|
||||
builder.ambientShadowAlpha = 88;
|
||||
mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
|
||||
mCircleRadius = builder.radius;
|
||||
|
||||
mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
|
||||
|
||||
// Find the points on the path that are closest to the top left and right corners.
|
||||
mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1);
|
||||
mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1);
|
||||
}
|
||||
|
||||
private static float[] getPathPoint(Path path, float size, float direction) {
|
||||
float halfSize = size / 2;
|
||||
// Small delta so that we don't get a zero size triangle
|
||||
float delta = 1;
|
||||
|
||||
float x = halfSize + direction * halfSize;
|
||||
Path trianglePath = new Path();
|
||||
trianglePath.moveTo(halfSize, halfSize);
|
||||
trianglePath.lineTo(x + delta * direction, 0);
|
||||
trianglePath.lineTo(x, -delta);
|
||||
trianglePath.close();
|
||||
|
||||
trianglePath.op(path, Path.Op.INTERSECT);
|
||||
float[] pos = new float[2];
|
||||
new PathMeasure(trianglePath, false).getPosTan(0, pos, null);
|
||||
|
||||
pos[0] = pos[0] / size;
|
||||
pos[1] = pos[1] / size;
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,15 +94,21 @@ public class DotRenderer {
|
||||
return;
|
||||
}
|
||||
canvas.save();
|
||||
// We draw the dot relative to its center.
|
||||
float dotCenterX = params.leftAlign
|
||||
? params.iconBounds.left + mDotCenterOffset / 2
|
||||
: params.iconBounds.right - mDotCenterOffset / 2;
|
||||
float dotCenterY = params.iconBounds.top + mDotCenterOffset / 2;
|
||||
|
||||
int offsetX = Math.min(mOffset, params.spaceForOffset.x);
|
||||
int offsetY = Math.min(mOffset, params.spaceForOffset.y);
|
||||
canvas.translate(dotCenterX + offsetX, dotCenterY - offsetY);
|
||||
Rect iconBounds = params.iconBounds;
|
||||
float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition;
|
||||
float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0];
|
||||
float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1];
|
||||
|
||||
// Ensure dot fits entirely in canvas clip bounds.
|
||||
Rect canvasBounds = canvas.getClipBounds();
|
||||
float offsetX = params.leftAlign
|
||||
? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset))
|
||||
: Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset));
|
||||
float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset));
|
||||
|
||||
// We draw the dot relative to its center.
|
||||
canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY);
|
||||
canvas.scale(params.scale, params.scale);
|
||||
|
||||
mCirclePaint.setColor(Color.BLACK);
|
||||
@@ -102,9 +128,6 @@ public class DotRenderer {
|
||||
/** The progress of the animation, from 0 to 1. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot")
|
||||
public float scale;
|
||||
/** Overrides internally calculated offset if specified value is smaller. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot")
|
||||
public Point spaceForOffset = new Point();
|
||||
/** Whether the dot should align to the top left of the icon rather than the top right. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot")
|
||||
public boolean leftAlign;
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
package com.android.launcher3.icons;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.RegionIterator;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -60,4 +63,14 @@ public class GraphicsUtils {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getArea(Region r) {
|
||||
RegionIterator itr = new RegionIterator(r);
|
||||
int area = 0;
|
||||
Rect tempRect = new Rect();
|
||||
while (itr.next(tempRect)) {
|
||||
area += tempRect.width() * tempRect.height();
|
||||
}
|
||||
return area;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,18 @@
|
||||
|
||||
package com.android.launcher3.icons;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@@ -57,7 +61,7 @@ public class IconNormalizer {
|
||||
private final Canvas mCanvas;
|
||||
private final byte[] mPixels;
|
||||
|
||||
private final Rect mAdaptiveIconBounds;
|
||||
private final RectF mAdaptiveIconBounds;
|
||||
private float mAdaptiveIconScale;
|
||||
|
||||
// for each y, stores the position of the leftmost x and the rightmost x
|
||||
@@ -66,7 +70,7 @@ public class IconNormalizer {
|
||||
private final Rect mBounds;
|
||||
|
||||
/** package private **/
|
||||
IconNormalizer(Context context, int iconBitmapSize) {
|
||||
IconNormalizer(int iconBitmapSize) {
|
||||
// Use twice the icon size as maximum size to avoid scaling down twice.
|
||||
mMaxSize = iconBitmapSize * 2;
|
||||
mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
|
||||
@@ -75,11 +79,53 @@ public class IconNormalizer {
|
||||
mLeftBorder = new float[mMaxSize];
|
||||
mRightBorder = new float[mMaxSize];
|
||||
mBounds = new Rect();
|
||||
mAdaptiveIconBounds = new Rect();
|
||||
mAdaptiveIconBounds = new RectF();
|
||||
|
||||
mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
private static float getScale(float hullArea, float boundingArea, float fullArea) {
|
||||
float hullByRect = hullArea / boundingArea;
|
||||
float scaleRequired;
|
||||
if (hullByRect < CIRCLE_AREA_BY_RECT) {
|
||||
scaleRequired = MAX_CIRCLE_AREA_FACTOR;
|
||||
} else {
|
||||
scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
|
||||
}
|
||||
|
||||
float areaScale = hullArea / fullArea;
|
||||
// Use sqrt of the final ratio as the images is scaled across both width and height.
|
||||
return areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param d Should be AdaptiveIconDrawable
|
||||
* @param size Canvas size to use
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public static float normalizeAdaptiveIcon(Drawable d, int size, @Nullable RectF outBounds) {
|
||||
Rect tmpBounds = new Rect(d.getBounds());
|
||||
d.setBounds(0, 0, size, size);
|
||||
|
||||
Path path = ((AdaptiveIconDrawable) d).getIconMask();
|
||||
Region region = new Region();
|
||||
region.setPath(path, new Region(0, 0, size, size));
|
||||
|
||||
Rect hullBounds = region.getBounds();
|
||||
int hullArea = GraphicsUtils.getArea(region);
|
||||
|
||||
if (outBounds != null) {
|
||||
float sizeF = size;
|
||||
outBounds.set(
|
||||
hullBounds.left / sizeF,
|
||||
hullBounds.top / sizeF,
|
||||
1 - (hullBounds.right / sizeF),
|
||||
1 - (hullBounds.bottom / sizeF));
|
||||
}
|
||||
d.setBounds(tmpBounds);
|
||||
return getScale(hullArea, hullArea, size * size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
|
||||
* matches the design guidelines for a launcher icon.
|
||||
@@ -96,12 +142,13 @@ public class IconNormalizer {
|
||||
*/
|
||||
public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds) {
|
||||
if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
|
||||
if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
|
||||
if (outBounds != null) {
|
||||
outBounds.set(mAdaptiveIconBounds);
|
||||
}
|
||||
return mAdaptiveIconScale;
|
||||
if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
|
||||
mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);
|
||||
}
|
||||
if (outBounds != null) {
|
||||
outBounds.set(mAdaptiveIconBounds);
|
||||
}
|
||||
return mAdaptiveIconScale;
|
||||
}
|
||||
int width = d.getIntrinsicWidth();
|
||||
int height = d.getIntrinsicHeight();
|
||||
@@ -184,16 +231,6 @@ public class IconNormalizer {
|
||||
area += mRightBorder[y] - mLeftBorder[y] + 1;
|
||||
}
|
||||
|
||||
// Area of the rectangle required to fit the convex hull
|
||||
float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
|
||||
float hullByRect = area / rectArea;
|
||||
|
||||
float scaleRequired;
|
||||
if (hullByRect < CIRCLE_AREA_BY_RECT) {
|
||||
scaleRequired = MAX_CIRCLE_AREA_FACTOR;
|
||||
} else {
|
||||
scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
|
||||
}
|
||||
mBounds.left = leftX;
|
||||
mBounds.right = rightX;
|
||||
|
||||
@@ -205,15 +242,10 @@ public class IconNormalizer {
|
||||
1 - ((float) mBounds.right) / width,
|
||||
1 - ((float) mBounds.bottom) / height);
|
||||
}
|
||||
float areaScale = area / (width * height);
|
||||
// Use sqrt of the final ratio as the images is scaled across both width and height.
|
||||
float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
|
||||
if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
|
||||
mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
|
||||
mAdaptiveIconScale = scale;
|
||||
mAdaptiveIconBounds.set(mBounds);
|
||||
}
|
||||
return scale;
|
||||
|
||||
// Area of the rectangle required to fit the convex hull
|
||||
float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
|
||||
return getScale(area, rectArea, width * height);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,6 +56,7 @@ message Target {
|
||||
optional int32 predictedRank = 15;
|
||||
optional TargetExtension extension = 16;
|
||||
optional TipType tip_type = 17;
|
||||
optional int32 search_query_length = 18;
|
||||
}
|
||||
|
||||
// Used to define what type of item a Target would represent.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<com.android.quickstep.hints.ProactiveHintsContainer
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal|bottom"
|
||||
android:gravity="center_horizontal">
|
||||
</com.android.quickstep.hints.ProactiveHintsContainer>
|
||||
@@ -23,4 +23,8 @@
|
||||
|
||||
<!-- Minimum distance to swipe to trigger accessibility gesture -->
|
||||
<dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
|
||||
|
||||
<!-- Swipe up to home related -->
|
||||
<dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
|
||||
<dimen name="swipe_up_y_overshoot">10dp</dimen>
|
||||
</resources>
|
||||
+8
-8
@@ -158,10 +158,10 @@ public class PredictionAppTracker extends AppLaunchTracker {
|
||||
public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
|
||||
String container) {
|
||||
// TODO: Use the full shortcut info
|
||||
AppTarget target = new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutId))
|
||||
.setTarget(packageName, user)
|
||||
.setClassName(shortcutId)
|
||||
.build();
|
||||
AppTarget target = new AppTarget
|
||||
.Builder(new AppTargetId("shortcut:" + shortcutId), packageName, user)
|
||||
.setClassName(shortcutId)
|
||||
.build();
|
||||
sendLaunch(target, container);
|
||||
}
|
||||
|
||||
@@ -169,10 +169,10 @@ public class PredictionAppTracker extends AppLaunchTracker {
|
||||
@UiThread
|
||||
public void onStartApp(ComponentName cn, UserHandle user, String container) {
|
||||
if (cn != null) {
|
||||
AppTarget target = new AppTarget.Builder(new AppTargetId("app:" + cn))
|
||||
.setTarget(cn.getPackageName(), user)
|
||||
.setClassName(cn.getClassName())
|
||||
.build();
|
||||
AppTarget target = new AppTarget
|
||||
.Builder(new AppTargetId("app:" + cn), cn.getPackageName(), user)
|
||||
.setClassName(cn.getClassName())
|
||||
.build();
|
||||
sendLaunch(target, container);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -67,8 +67,8 @@ public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInf
|
||||
|
||||
// TODO (b/129421797): Update the client constants
|
||||
public enum Client {
|
||||
HOME("GEL"),
|
||||
OVERVIEW("OVERVIEW_GEL");
|
||||
HOME("home"),
|
||||
OVERVIEW("overview");
|
||||
|
||||
public final String id;
|
||||
|
||||
|
||||
+79
-2
@@ -20,6 +20,11 @@ import static android.view.View.VISIBLE;
|
||||
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.view.Gravity;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
@@ -28,6 +33,7 @@ import com.android.launcher3.LauncherStateManager.StateHandler;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.graphics.RotationMode;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
|
||||
@@ -58,12 +64,83 @@ public abstract class RecentsUiFactory {
|
||||
// Scale recents takes before animating in
|
||||
private static final float RECENTS_PREPARE_SCALE = 1.33f;
|
||||
|
||||
public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
|
||||
@Override
|
||||
public void mapRect(int left, int top, int right, int bottom, Rect out) {
|
||||
out.left = top;
|
||||
out.top = right;
|
||||
out.right = bottom;
|
||||
out.bottom = left;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mapInsets(Context context, Rect insets, Rect out) {
|
||||
if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
|
||||
out.set(insets);
|
||||
} else {
|
||||
out.top = Math.max(insets.top, insets.left);
|
||||
out.bottom = insets.right;
|
||||
out.left = insets.bottom;
|
||||
out.right = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
|
||||
@Override
|
||||
public void mapRect(int left, int top, int right, int bottom, Rect out) {
|
||||
out.left = bottom;
|
||||
out.top = left;
|
||||
out.right = top;
|
||||
out.bottom = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mapInsets(Context context, Rect insets, Rect out) {
|
||||
if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
|
||||
out.set(insets);
|
||||
} else {
|
||||
out.top = Math.max(insets.top, insets.right);
|
||||
out.bottom = insets.left;
|
||||
out.right = insets.bottom;
|
||||
out.left = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toNaturalGravity(int absoluteGravity) {
|
||||
int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
|
||||
int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK;
|
||||
|
||||
if (horizontalGravity == Gravity.RIGHT) {
|
||||
horizontalGravity = Gravity.LEFT;
|
||||
} else if (horizontalGravity == Gravity.LEFT) {
|
||||
horizontalGravity = Gravity.RIGHT;
|
||||
}
|
||||
|
||||
if (verticalGravity == Gravity.TOP) {
|
||||
verticalGravity = Gravity.BOTTOM;
|
||||
} else if (verticalGravity == Gravity.BOTTOM) {
|
||||
verticalGravity = Gravity.TOP;
|
||||
}
|
||||
|
||||
return ((absoluteGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK)
|
||||
& ~Gravity.VERTICAL_GRAVITY_MASK)
|
||||
| horizontalGravity | verticalGravity;
|
||||
}
|
||||
};
|
||||
|
||||
public static RotationMode getRotationMode(DeviceProfile dp) {
|
||||
return !dp.isVerticalBarLayout() ? RotationMode.NORMAL
|
||||
: (dp.isSeascape() ? ROTATION_SEASCAPE : ROTATION_LANDSCAPE);
|
||||
}
|
||||
|
||||
public static TouchController[] createTouchControllers(Launcher launcher) {
|
||||
Mode mode = SysUINavigationMode.getMode(launcher);
|
||||
|
||||
ArrayList<TouchController> list = new ArrayList<>();
|
||||
list.add(launcher.getDragController());
|
||||
if (mode == Mode.NO_BUTTON) {
|
||||
if (mode == NO_BUTTON) {
|
||||
list.add(new QuickSwitchTouchController(launcher));
|
||||
list.add(new NavBarToHomeTouchController(launcher));
|
||||
list.add(new FlingAndHoldTouchController(launcher));
|
||||
@@ -106,7 +183,7 @@ public abstract class RecentsUiFactory {
|
||||
* @param launcher the launcher activity
|
||||
*/
|
||||
public static void prepareToShowOverview(Launcher launcher) {
|
||||
if (SysUINavigationMode.getMode(launcher) == Mode.NO_BUTTON) {
|
||||
if (SysUINavigationMode.getMode(launcher) == NO_BUTTON) {
|
||||
// Overview lives on the side, so doesn't scale in from above.
|
||||
return;
|
||||
}
|
||||
|
||||
+18
@@ -31,6 +31,7 @@ import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.LauncherStateManager.AnimationConfig;
|
||||
import com.android.launcher3.anim.AnimatorSetBuilder;
|
||||
import com.android.launcher3.anim.PropertySetter;
|
||||
import com.android.quickstep.hints.ProactiveHintsContainer;
|
||||
import com.android.quickstep.views.ClearAllButton;
|
||||
import com.android.quickstep.views.LauncherRecentsView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
@@ -53,6 +54,14 @@ public final class RecentsViewStateController extends
|
||||
if (state.overviewUi) {
|
||||
mRecentsView.updateEmptyMessage();
|
||||
mRecentsView.resetTaskVisuals();
|
||||
mRecentsView.setHintVisibility(1f);
|
||||
} else {
|
||||
mRecentsView.setHintVisibility(0f);
|
||||
ProactiveHintsContainer
|
||||
proactiveHintsContainer = mRecentsView.getProactiveHintsContainer();
|
||||
if (proactiveHintsContainer != null) {
|
||||
proactiveHintsContainer.removeAllViews();
|
||||
}
|
||||
}
|
||||
setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state.getVisibleElements(mLauncher));
|
||||
}
|
||||
@@ -64,6 +73,14 @@ public final class RecentsViewStateController extends
|
||||
|
||||
if (!toState.overviewUi) {
|
||||
builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
|
||||
mRecentsView.setHintVisibility(0f);
|
||||
builder.addOnFinishRunnable(() -> {
|
||||
ProactiveHintsContainer
|
||||
proactiveHintsContainer = mRecentsView.getProactiveHintsContainer();
|
||||
if (proactiveHintsContainer != null) {
|
||||
proactiveHintsContainer.removeAllViews();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (toState.overviewUi) {
|
||||
@@ -75,6 +92,7 @@ public final class RecentsViewStateController extends
|
||||
updateAnim.setDuration(config.duration);
|
||||
builder.play(updateAnim);
|
||||
mRecentsView.updateEmptyMessage();
|
||||
builder.addOnFinishRunnable(() -> mRecentsView.setHintVisibility(1f));
|
||||
}
|
||||
|
||||
setAlphas(config.getPropertySetter(builder), toState.getVisibleElements(mLauncher));
|
||||
|
||||
+2
-14
@@ -17,13 +17,10 @@ package com.android.launcher3.uioverrides.states;
|
||||
|
||||
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
import com.android.quickstep.util.ClipAnimationHelper;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskThumbnailView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
|
||||
/**
|
||||
@@ -45,18 +42,9 @@ public class QuickSwitchState extends OverviewState {
|
||||
if (recentsView.getTaskViewCount() == 0) {
|
||||
return super.getOverviewScaleAndTranslation(launcher);
|
||||
}
|
||||
// Compute scale and translation y such that the most recent task view fills the screen.
|
||||
TaskThumbnailView dummyThumbnail = recentsView.getTaskViewAt(0).getThumbnail();
|
||||
TaskView dummyTask = recentsView.getTaskViewAt(0);
|
||||
ClipAnimationHelper clipAnimationHelper = new ClipAnimationHelper(launcher);
|
||||
clipAnimationHelper.fromTaskThumbnailView(dummyThumbnail, recentsView);
|
||||
Rect targetRect = new Rect();
|
||||
recentsView.getTaskSize(targetRect);
|
||||
clipAnimationHelper.updateTargetRect(targetRect);
|
||||
float toScale = clipAnimationHelper.getSourceRect().width()
|
||||
/ clipAnimationHelper.getTargetRect().width();
|
||||
float toTranslationY = clipAnimationHelper.getSourceRect().centerY()
|
||||
- clipAnimationHelper.getTargetRect().centerY();
|
||||
return new ScaleAndTranslation(toScale, 0, toTranslationY);
|
||||
return clipAnimationHelper.getOverviewFullscreenScaleAndTranslation(dummyTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+9
-3
@@ -28,9 +28,13 @@ import static com.android.launcher3.anim.Interpolators.ACCEL_2;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
|
||||
import static com.android.launcher3.anim.Interpolators.INSTANT;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
|
||||
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.LauncherStateManager;
|
||||
@@ -45,8 +49,6 @@ import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Handles quick switching to a recent task from the home screen.
|
||||
*/
|
||||
@@ -122,12 +124,16 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll
|
||||
@Override
|
||||
protected void updateProgress(float progress) {
|
||||
super.updateProgress(progress);
|
||||
updateFullscreenProgress(progress);
|
||||
updateFullscreenProgress(Utilities.boundToRange(progress, 0, 1));
|
||||
}
|
||||
|
||||
private void updateFullscreenProgress(float progress) {
|
||||
if (mTaskToLaunch != null) {
|
||||
mTaskToLaunch.setFullscreenProgress(progress);
|
||||
int sysuiFlags = progress > UPDATE_SYSUI_FLAGS_THRESHOLD
|
||||
? mTaskToLaunch.getThumbnail().getSysUiStatusNavFlags()
|
||||
: 0;
|
||||
mLauncher.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
|
||||
* @param wasVisible true if it was visible before
|
||||
*/
|
||||
boolean onActivityReady(T activity, Boolean wasVisible) {
|
||||
activity.<RecentsView>getOverviewPanel().setCurrentTask(mTargetTaskId);
|
||||
activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
|
||||
AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
|
||||
ActivityControlHelper.AnimationFactory factory =
|
||||
mHelper.prepareRecentsUI(activity, wasVisible,
|
||||
|
||||
+12
-27
@@ -270,9 +270,9 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
|
||||
playScaleDownAnim(anim, activity, endState);
|
||||
|
||||
anim.setDuration(transitionLength * 2);
|
||||
activity.getStateManager().setCurrentAnimation(anim);
|
||||
AnimatorPlaybackController controller =
|
||||
AnimatorPlaybackController.wrap(anim, transitionLength * 2);
|
||||
activity.getStateManager().setCurrentUserControlledAnimation(controller);
|
||||
|
||||
// Since we are changing the start position of the UI, reapply the state, at the end
|
||||
controller.setEndAction(() -> {
|
||||
@@ -301,34 +301,19 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup the clip animation helper source/target rects in the final transformed state
|
||||
// of the recents view (a scale/translationY may be applied prior to this animation
|
||||
// starting to line up the side pages during swipe up)
|
||||
float prevRvScale = recentsView.getScaleX();
|
||||
float prevRvTransY = recentsView.getTranslationY();
|
||||
float targetRvScale = endState.getOverviewScaleAndTranslation(launcher).scale;
|
||||
SCALE_PROPERTY.set(recentsView, targetRvScale);
|
||||
recentsView.setTranslationY(0);
|
||||
ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher);
|
||||
float tmpCurveScale = v.getCurveScale();
|
||||
v.setCurveScale(1f);
|
||||
clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null);
|
||||
v.setCurveScale(tmpCurveScale);
|
||||
SCALE_PROPERTY.set(recentsView, prevRvScale);
|
||||
recentsView.setTranslationY(prevRvTransY);
|
||||
LauncherState.ScaleAndTranslation fromScaleAndTranslation
|
||||
= clipHelper.getOverviewFullscreenScaleAndTranslation(v);
|
||||
LauncherState.ScaleAndTranslation endScaleAndTranslation
|
||||
= endState.getOverviewScaleAndTranslation(launcher);
|
||||
|
||||
if (!clipHelper.getSourceRect().isEmpty() && !clipHelper.getTargetRect().isEmpty()) {
|
||||
float fromScale = clipHelper.getSourceRect().width()
|
||||
/ clipHelper.getTargetRect().width();
|
||||
float fromTranslationY = clipHelper.getSourceRect().centerY()
|
||||
- clipHelper.getTargetRect().centerY();
|
||||
Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, fromScale, 1);
|
||||
Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y,
|
||||
fromTranslationY, 0);
|
||||
scale.setInterpolator(LINEAR);
|
||||
translateY.setInterpolator(LINEAR);
|
||||
anim.playTogether(scale, translateY);
|
||||
}
|
||||
Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
|
||||
fromScaleAndTranslation.scale, endScaleAndTranslation.scale);
|
||||
Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y,
|
||||
fromScaleAndTranslation.translationY, endScaleAndTranslation.translationY);
|
||||
scale.setInterpolator(LINEAR);
|
||||
translateY.setInterpolator(LINEAR);
|
||||
anim.playTogether(scale, translateY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.quickstep.inputconsumers.InputConsumer;
|
||||
import com.android.quickstep.util.SwipeAnimationTargetSet;
|
||||
import com.android.systemui.shared.system.InputConsumerController;
|
||||
|
||||
@@ -59,6 +60,10 @@ public class RecentsAnimationWrapper {
|
||||
mInputProxySupplier = inputProxySupplier;
|
||||
}
|
||||
|
||||
public boolean hasTargets() {
|
||||
return targetSet != null && targetSet.hasTargets();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public synchronized void setController(SwipeAnimationTargetSet targetSet) {
|
||||
Preconditions.assertUIThread();
|
||||
|
||||
@@ -38,6 +38,8 @@ public class SwipeSharedState implements SwipeAnimationListener {
|
||||
|
||||
public boolean canGestureBeContinued;
|
||||
public boolean goingToLauncher;
|
||||
public boolean recentsAnimationFinishInterrupted;
|
||||
public int nextRunningTaskId = -1;
|
||||
|
||||
public SwipeSharedState(OverviewComponentObserver overviewComponentObserver) {
|
||||
mOverviewComponentObserver = overviewComponentObserver;
|
||||
@@ -114,9 +116,22 @@ public class SwipeSharedState implements SwipeAnimationListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a recents animation has finished, but was interrupted before the next task was
|
||||
* launched. The given {@param runningTaskId} should be used as the running task for the
|
||||
* continuing input consumer.
|
||||
*/
|
||||
public void setRecentsAnimationFinishInterrupted(int runningTaskId) {
|
||||
recentsAnimationFinishInterrupted = true;
|
||||
nextRunningTaskId = runningTaskId;
|
||||
mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets();
|
||||
}
|
||||
|
||||
public void clearAllState() {
|
||||
clearListenerState();
|
||||
canGestureBeContinued = false;
|
||||
recentsAnimationFinishInterrupted = false;
|
||||
nextRunningTaskId = -1;
|
||||
goingToLauncher = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -116,21 +117,22 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
protected abstract boolean isAvailable(BaseDraggingActivity activity);
|
||||
protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
|
||||
protected abstract ActivityOptions makeLaunchOptions(Activity activity);
|
||||
protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getOnClickListener(
|
||||
BaseDraggingActivity activity, TaskView taskView) {
|
||||
if (!isAvailable(activity)) {
|
||||
return null;
|
||||
}
|
||||
final Task task = taskView.getTask();
|
||||
final int taskId = task.key.id;
|
||||
final int displayId = task.key.displayId;
|
||||
if (!task.isDockable) {
|
||||
return null;
|
||||
}
|
||||
if (!isAvailable(activity, displayId)) {
|
||||
return null;
|
||||
}
|
||||
final RecentsView recentsView = activity.getOverviewPanel();
|
||||
|
||||
final TaskThumbnailView thumbnailView = taskView.getThumbnail();
|
||||
@@ -218,9 +220,13 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAvailable(BaseDraggingActivity activity) {
|
||||
// Don't show menu-item if already in multi-window
|
||||
return !activity.getDeviceProfile().isMultiWindowMode;
|
||||
protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
|
||||
// Don't show menu-item if already in multi-window and the task is from
|
||||
// the secondary display.
|
||||
// TODO(b/118266305): Temporarily disable splitscreen for secondary display while new
|
||||
// implementation is enabled
|
||||
return !activity.getDeviceProfile().isMultiWindowMode
|
||||
&& displayId == DEFAULT_DISPLAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -256,7 +262,7 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAvailable(BaseDraggingActivity activity) {
|
||||
protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
|
||||
return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity);
|
||||
}
|
||||
|
||||
@@ -290,10 +296,6 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
|
||||
if (sysUiProxy == null) {
|
||||
return null;
|
||||
}
|
||||
if (SysUINavigationMode.getMode(activity) == SysUINavigationMode.Mode.NO_BUTTON) {
|
||||
// TODO(b/130225926): Temporarily disable pinning while gesture nav is enabled
|
||||
return null;
|
||||
}
|
||||
if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ public final class TaskViewUtils {
|
||||
|
||||
@Override
|
||||
public void onUpdate(float percent) {
|
||||
// TODO: Take into account the current fullscreen progress for animating the insets
|
||||
params.setProgress(1 - percent);
|
||||
RectF taskBounds = inOutHelper.applyTransform(targetSet, params);
|
||||
if (!skipViewChanges) {
|
||||
|
||||
+78
-54
@@ -26,6 +26,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.Service;
|
||||
@@ -34,7 +35,6 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
@@ -57,6 +57,7 @@ import android.view.WindowManager;
|
||||
|
||||
import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.ResourceUtils;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.compat.UserManagerCompat;
|
||||
import com.android.launcher3.logging.EventLogArray;
|
||||
@@ -65,6 +66,13 @@ import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.launcher3.util.UiThreadHelper;
|
||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
|
||||
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
|
||||
import com.android.quickstep.inputconsumers.AssistantTouchConsumer;
|
||||
import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
|
||||
import com.android.quickstep.inputconsumers.InputConsumer;
|
||||
import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
|
||||
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
|
||||
import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
|
||||
import com.android.systemui.shared.recents.IOverviewProxy;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
@@ -89,9 +97,6 @@ public class TouchInteractionService extends Service implements
|
||||
public static final LooperExecutor BACKGROUND_EXECUTOR =
|
||||
new LooperExecutor(UiThreadHelper.getBackgroundLooper());
|
||||
|
||||
private static final String NAVBAR_VERTICAL_SIZE = "navigation_bar_frame_height";
|
||||
private static final String NAVBAR_HORIZONTAL_SIZE = "navigation_bar_width";
|
||||
|
||||
public static final EventLogArray TOUCH_INTERACTION_LOG =
|
||||
new EventLogArray("touch_interaction_log", 40);
|
||||
|
||||
@@ -290,15 +295,7 @@ public class TouchInteractionService extends Service implements
|
||||
}
|
||||
|
||||
private int getNavbarSize(String resName) {
|
||||
int frameSize;
|
||||
Resources res = getResources();
|
||||
int frameSizeResID = res.getIdentifier(resName, "dimen", "android");
|
||||
if (frameSizeResID != 0) {
|
||||
frameSize = res.getDimensionPixelSize(frameSizeResID);
|
||||
} else {
|
||||
frameSize = Utilities.pxFromDp(48, res.getDisplayMetrics());
|
||||
}
|
||||
return frameSize;
|
||||
return ResourceUtils.getNavbarSize(resName, getResources());
|
||||
}
|
||||
|
||||
private void initTouchBounds() {
|
||||
@@ -311,20 +308,21 @@ public class TouchInteractionService extends Service implements
|
||||
defaultDisplay.getRealSize(realSize);
|
||||
mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y);
|
||||
if (mMode == Mode.NO_BUTTON) {
|
||||
mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - getNavbarSize(NAVBAR_VERTICAL_SIZE);
|
||||
mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - getNavbarSize(
|
||||
ResourceUtils.NAVBAR_VERTICAL_SIZE);
|
||||
} else {
|
||||
switch (defaultDisplay.getRotation()) {
|
||||
case Surface.ROTATION_90:
|
||||
mSwipeTouchRegion.left = mSwipeTouchRegion.right
|
||||
- getNavbarSize(NAVBAR_HORIZONTAL_SIZE);
|
||||
- getNavbarSize(ResourceUtils.NAVBAR_HORIZONTAL_SIZE);
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
mSwipeTouchRegion.right = mSwipeTouchRegion.left
|
||||
+ getNavbarSize(NAVBAR_HORIZONTAL_SIZE);
|
||||
+ getNavbarSize(ResourceUtils.NAVBAR_HORIZONTAL_SIZE);
|
||||
break;
|
||||
default:
|
||||
mSwipeTouchRegion.top = mSwipeTouchRegion.bottom
|
||||
- getNavbarSize(NAVBAR_VERTICAL_SIZE);
|
||||
- getNavbarSize(ResourceUtils.NAVBAR_VERTICAL_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,8 +426,7 @@ public class TouchInteractionService extends Service implements
|
||||
MotionEvent event = (MotionEvent) ev;
|
||||
TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
|
||||
if (event.getAction() == ACTION_DOWN) {
|
||||
if (isInValidSystemUiState()
|
||||
&& mSwipeTouchRegion.contains(event.getX(), event.getY())) {
|
||||
if (mSwipeTouchRegion.contains(event.getX(), event.getY())) {
|
||||
boolean useSharedState = mConsumer.useSharedSwipeState();
|
||||
mConsumer.onConsumerAboutToBeSwitched();
|
||||
mConsumer = newConsumer(useSharedState, event);
|
||||
@@ -442,59 +439,86 @@ public class TouchInteractionService extends Service implements
|
||||
mUncheckedConsumer.onMotionEvent(event);
|
||||
}
|
||||
|
||||
private boolean isInValidSystemUiState() {
|
||||
return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
|
||||
&& (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
|
||||
&& !ActivityManagerWrapper.getInstance().isLockToAppActive();
|
||||
}
|
||||
|
||||
private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
|
||||
// TODO: this makes a binder call every touch down. we should move to a listener pattern.
|
||||
if (!mIsUserUnlocked || mKM.isDeviceLocked()) {
|
||||
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched
|
||||
// while device is locked even after exiting direct boot mode (e.g. camera).
|
||||
return new DeviceLockedInputConsumer(this);
|
||||
}
|
||||
|
||||
RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
|
||||
if (!useSharedState) {
|
||||
mSwipeSharedState.clearAllState();
|
||||
}
|
||||
|
||||
final ActivityControlHelper activityControl =
|
||||
mOverviewComponentObserver.getActivityControlHelper();
|
||||
|
||||
InputConsumer base;
|
||||
if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
|
||||
base = InputConsumer.NO_OP;
|
||||
} else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
|
||||
base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
|
||||
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
|
||||
activityControl.isInLiveTileMode()) {
|
||||
base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
|
||||
} else if (mGestureBlockingActivity != null && runningTaskInfo != null
|
||||
&& mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
|
||||
base = InputConsumer.NO_OP;
|
||||
} else {
|
||||
base = createOtherActivityInputConsumer(event, runningTaskInfo);
|
||||
boolean validSystemUIFlags = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
|
||||
&& (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0;
|
||||
boolean topTaskLocked = ActivityManagerWrapper.getInstance().isLockToAppActive();
|
||||
boolean isInValidSystemUiState = validSystemUIFlags && !topTaskLocked;
|
||||
|
||||
if (!mIsUserUnlocked) {
|
||||
if (isInValidSystemUiState) {
|
||||
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps
|
||||
// launched while device is locked even after exiting direct boot mode (e.g. camera).
|
||||
return new DeviceLockedInputConsumer(this);
|
||||
} else {
|
||||
return InputConsumer.NO_OP;
|
||||
}
|
||||
}
|
||||
|
||||
InputConsumer base = isInValidSystemUiState
|
||||
? newBaseConsumer(useSharedState, event) : InputConsumer.NO_OP;
|
||||
if (mMode == Mode.NO_BUTTON) {
|
||||
if (mAssistantAvailable && AssistantTouchConsumer.withinTouchRegion(this, event)) {
|
||||
final ActivityControlHelper activityControl =
|
||||
mOverviewComponentObserver.getActivityControlHelper();
|
||||
if (mAssistantAvailable && !topTaskLocked
|
||||
&& AssistantTouchConsumer.withinTouchRegion(this, event)) {
|
||||
base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base,
|
||||
mInputMonitorCompat);
|
||||
}
|
||||
|
||||
if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
|
||||
// Note: we only allow accessibility to wrap this, and it replaces the previous
|
||||
// base input consumer (which should be NO_OP anyway since topTaskLocked == true).
|
||||
base = new ScreenPinnedInputConsumer(this, mISystemUiProxy, activityControl);
|
||||
}
|
||||
|
||||
if ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) {
|
||||
base = new AccessibilityInputConsumer(this, mISystemUiProxy,
|
||||
(mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0, base,
|
||||
mInputMonitorCompat);
|
||||
}
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) {
|
||||
if (mKM.isDeviceLocked()) {
|
||||
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched
|
||||
// while device is locked even after exiting direct boot mode (e.g. camera).
|
||||
return new DeviceLockedInputConsumer(this);
|
||||
}
|
||||
|
||||
final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
|
||||
if (!useSharedState) {
|
||||
mSwipeSharedState.clearAllState();
|
||||
}
|
||||
|
||||
final ActivityControlHelper activityControl =
|
||||
mOverviewComponentObserver.getActivityControlHelper();
|
||||
|
||||
if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher
|
||||
&& !mSwipeSharedState.recentsAnimationFinishInterrupted) {
|
||||
return InputConsumer.NO_OP;
|
||||
} else if (mSwipeSharedState.recentsAnimationFinishInterrupted) {
|
||||
// If the finish animation was interrupted, then continue using the other activity input
|
||||
// consumer but with the next task as the running task
|
||||
RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
|
||||
info.id = mSwipeSharedState.nextRunningTaskId;
|
||||
return createOtherActivityInputConsumer(event, info);
|
||||
} else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
|
||||
return OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
|
||||
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
|
||||
activityControl.isInLiveTileMode()) {
|
||||
return OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
|
||||
} else if (mGestureBlockingActivity != null && runningTaskInfo != null
|
||||
&& mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
|
||||
return InputConsumer.NO_OP;
|
||||
} else {
|
||||
return createOtherActivityInputConsumer(event, runningTaskInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event,
|
||||
RunningTaskInfo runningTaskInfo) {
|
||||
final ActivityControlHelper activityControl =
|
||||
|
||||
+86
-36
@@ -27,6 +27,7 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TI
|
||||
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
|
||||
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
|
||||
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
|
||||
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
|
||||
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
|
||||
import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE;
|
||||
import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK;
|
||||
@@ -66,6 +67,7 @@ import android.view.WindowManager;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
@@ -90,6 +92,8 @@ import com.android.quickstep.ActivityControlHelper.AnimationFactory;
|
||||
import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
|
||||
import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
|
||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.quickstep.inputconsumers.InputConsumer;
|
||||
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
|
||||
import com.android.quickstep.util.ClipAnimationHelper;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.RemoteAnimationTargetSet;
|
||||
@@ -165,7 +169,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
private static final int LAUNCHER_UI_STATES =
|
||||
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
|
||||
|
||||
enum GestureEndTarget {
|
||||
public enum GestureEndTarget {
|
||||
HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE, false),
|
||||
|
||||
RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
|
||||
@@ -219,8 +223,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
private final ClipAnimationHelper mClipAnimationHelper;
|
||||
private final ClipAnimationHelper.TransformParams mTransformParams;
|
||||
|
||||
protected Runnable mGestureEndCallback;
|
||||
protected GestureEndTarget mGestureEndTarget;
|
||||
private Runnable mGestureEndCallback;
|
||||
private GestureEndTarget mGestureEndTarget;
|
||||
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
|
||||
private RunningWindowAnim mRunningWindowAnim;
|
||||
private boolean mIsShelfPeeking;
|
||||
@@ -256,7 +260,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
private AnimationFactory mAnimationFactory = (t) -> { };
|
||||
private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
|
||||
|
||||
private boolean mCanceled;
|
||||
private boolean mWasLauncherAlreadyVisible;
|
||||
private int mFinishingRecentsAnimationForNewTaskId = -1;
|
||||
|
||||
private boolean mPassedOverviewThreshold;
|
||||
private boolean mGestureStarted;
|
||||
@@ -270,7 +276,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
private final long mTouchTimeMs;
|
||||
private long mLauncherFrameDrawnTime;
|
||||
|
||||
WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
|
||||
public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
|
||||
long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
|
||||
InputConsumerController inputConsumer) {
|
||||
mContext = context;
|
||||
@@ -412,7 +418,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
mRecentsAnimationWrapper.runOnInit(() ->
|
||||
mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
|
||||
});
|
||||
mRecentsView.setEnableFreeScroll(false);
|
||||
|
||||
mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
|
||||
if (mGestureEndTarget != HOME) {
|
||||
@@ -500,10 +505,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
if (mContinuingLastGesture) {
|
||||
return;
|
||||
}
|
||||
mRecentsView.setEnableDrawingLiveTile(false);
|
||||
mRecentsView.showTask(mRunningTaskId);
|
||||
mRecentsView.setRunningTaskHidden(true);
|
||||
mRecentsView.setRunningTaskIconScaledDown(true);
|
||||
mRecentsView.onGestureAnimationStart(mRunningTaskId);
|
||||
}
|
||||
|
||||
private void launcherFrameDrawn() {
|
||||
@@ -656,8 +658,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
|
||||
mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
|
||||
mTransformParams);
|
||||
mRecentsAnimationWrapper.setWindowThresholdCrossed(
|
||||
shift > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD);
|
||||
updateSysUiFlags(shift);
|
||||
}
|
||||
|
||||
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
|
||||
@@ -679,7 +680,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
int runningTaskIndex = mRecentsView == null ? -1 : mRecentsView.getRunningTaskIndex();
|
||||
if (runningTaskIndex >= 0) {
|
||||
for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
|
||||
if (i != runningTaskIndex) {
|
||||
if (i != runningTaskIndex || !mRecentsAnimationWrapper.hasTargets()) {
|
||||
mRecentsView.getTaskViewAt(i).setFullscreenProgress(1 - mCurrentShift.value);
|
||||
}
|
||||
}
|
||||
@@ -702,6 +703,18 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart));
|
||||
}
|
||||
|
||||
private void updateSysUiFlags(float windowProgress) {
|
||||
if (mRecentsView != null) {
|
||||
// We will handle the sysui flags based on the centermost task view.
|
||||
mRecentsAnimationWrapper.setWindowThresholdCrossed(true);
|
||||
int sysuiFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD
|
||||
? 0
|
||||
: mRecentsView.getTaskViewAt(mRecentsView.getPageNearestToCenterOfScreen())
|
||||
.getThumbnail().getSysUiStatusNavFlags();
|
||||
mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
|
||||
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
|
||||
@@ -838,9 +851,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
Interpolator interpolator = DEACCEL;
|
||||
final boolean goingToNewTask;
|
||||
if (mRecentsView != null) {
|
||||
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
|
||||
final int taskToLaunch = mRecentsView.getNextPage();
|
||||
goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
|
||||
if (!mRecentsAnimationWrapper.hasTargets()) {
|
||||
// If there are no running tasks, then we can assume that this is a continuation of
|
||||
// the last gesture, but after the recents animation has finished
|
||||
goingToNewTask = true;
|
||||
} else {
|
||||
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
|
||||
final int taskToLaunch = mRecentsView.getNextPage();
|
||||
goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
|
||||
}
|
||||
} else {
|
||||
goingToNewTask = false;
|
||||
}
|
||||
@@ -1058,10 +1077,11 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
|
||||
final View floatingView = homeAnimationFactory.getFloatingView();
|
||||
final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
|
||||
|
||||
RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect);
|
||||
RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mActivity.getResources());
|
||||
if (isFloatingIconView) {
|
||||
anim.addAnimatorListener((FloatingIconView) floatingView);
|
||||
FloatingIconView fiv = (FloatingIconView) floatingView;
|
||||
anim.addAnimatorListener(fiv);
|
||||
fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
|
||||
}
|
||||
|
||||
AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
|
||||
@@ -1074,17 +1094,19 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
|
||||
homeAnim.setPlayFraction(progress);
|
||||
|
||||
float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0,
|
||||
windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR);
|
||||
mTransformParams.setCurrentRectAndTargetAlpha(currentRect, 1f - iconAlpha);
|
||||
float windowAlpha = Utilities.mapToRange(interpolatedProgress, 0,
|
||||
windowAlphaThreshold, 1f, 0f, Interpolators.LINEAR);
|
||||
mTransformParams.setProgress(progress)
|
||||
.setCurrentRectAndTargetAlpha(currentRect, windowAlpha);
|
||||
mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
|
||||
false /* launcherOnTop */);
|
||||
|
||||
if (isFloatingIconView) {
|
||||
((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress,
|
||||
((FloatingIconView) floatingView).update(currentRect, 1f, progress,
|
||||
windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(), false);
|
||||
}
|
||||
|
||||
updateSysUiFlags(Math.max(progress, mCurrentShift.value));
|
||||
});
|
||||
anim.addAnimatorListener(new AnimationSuccessListener() {
|
||||
@Override
|
||||
@@ -1106,6 +1128,13 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
return anim;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The GestureEndTarget if the gesture has ended, else null.
|
||||
*/
|
||||
public @Nullable GestureEndTarget getGestureEndTarget() {
|
||||
return mGestureEndTarget;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void resumeLastTask() {
|
||||
mRecentsAnimationWrapper.finish(false /* toRecents */, null);
|
||||
@@ -1122,14 +1151,22 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */,
|
||||
true /* freezeTaskList */);
|
||||
} else {
|
||||
int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id;
|
||||
mFinishingRecentsAnimationForNewTaskId = taskId;
|
||||
mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
|
||||
mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(
|
||||
false /* animate */, true /* freezeTaskList */);
|
||||
if (!mCanceled) {
|
||||
TaskView nextTask = mRecentsView.getTaskView(taskId);
|
||||
if (nextTask != null) {
|
||||
nextTask.launchTask(false /* animate */, true /* freezeTaskList */);
|
||||
doLogGesture(NEW_TASK);
|
||||
}
|
||||
reset();
|
||||
}
|
||||
mCanceled = false;
|
||||
mFinishingRecentsAnimationForNewTaskId = -1;
|
||||
});
|
||||
}
|
||||
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
|
||||
doLogGesture(NEW_TASK);
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
@@ -1140,12 +1177,26 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
* Cancels any running animation so that the active target can be overriden by a new swipe
|
||||
* handle (in case of quick switch).
|
||||
*/
|
||||
public void cancelCurrentAnimation() {
|
||||
public void cancelCurrentAnimation(SwipeSharedState sharedState) {
|
||||
mCanceled = true;
|
||||
mCurrentShift.cancelAnimation();
|
||||
if (mLauncherTransitionController != null && mLauncherTransitionController
|
||||
.getAnimationPlayer().isStarted()) {
|
||||
mLauncherTransitionController.getAnimationPlayer().cancel();
|
||||
}
|
||||
|
||||
if (mFinishingRecentsAnimationForNewTaskId != -1) {
|
||||
// If we are canceling mid-starting a new task, switch to the screenshot since the
|
||||
// recents animation has finished
|
||||
switchToScreenshot();
|
||||
TaskView newRunningTaskView = mRecentsView.getTaskView(
|
||||
mFinishingRecentsAnimationForNewTaskId);
|
||||
int newRunningTaskId = newRunningTaskView != null
|
||||
? newRunningTaskView.getTask().key.id
|
||||
: -1;
|
||||
mRecentsView.setCurrentTask(newRunningTaskId);
|
||||
sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateHandler() {
|
||||
@@ -1167,11 +1218,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
mLauncherTransitionController = null;
|
||||
}
|
||||
|
||||
mRecentsView.setEnableFreeScroll(true);
|
||||
mRecentsView.setRunningTaskIconScaledDown(false);
|
||||
mRecentsView.setOnScrollChangeListener(null);
|
||||
mRecentsView.setRunningTaskHidden(false);
|
||||
mRecentsView.setEnableDrawingLiveTile(true);
|
||||
mRecentsView.onGestureAnimationEnd();
|
||||
|
||||
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
|
||||
mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
|
||||
@@ -1192,6 +1239,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
private void switchToScreenshot() {
|
||||
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
|
||||
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
|
||||
} else if (!mRecentsAnimationWrapper.hasTargets()) {
|
||||
// If there are no targets, then we don't need to capture anything
|
||||
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
|
||||
} else {
|
||||
boolean finishTransitionPosted = false;
|
||||
SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
|
||||
@@ -1239,6 +1289,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
private void finishCurrentTransitionToRecents() {
|
||||
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
|
||||
setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
|
||||
} else if (!mRecentsAnimationWrapper.hasTargets()) {
|
||||
// If there are no targets, then there is nothing to finish
|
||||
setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
|
||||
} else {
|
||||
synchronized (mRecentsAnimationWrapper) {
|
||||
mRecentsAnimationWrapper.finish(true /* toRecents */,
|
||||
@@ -1265,10 +1318,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
}
|
||||
mActivityControlHelper.onSwipeUpComplete(mActivity);
|
||||
mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
|
||||
|
||||
// Animate the first icon.
|
||||
mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation());
|
||||
mRecentsView.setSwipeDownShouldLaunchApp(true);
|
||||
mRecentsView.onSwipeUpAnimationSuccess();
|
||||
|
||||
RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
|
||||
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.android.quickstep.hints;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class ProactiveHintsContainer extends FrameLayout {
|
||||
|
||||
public static final FloatProperty<ProactiveHintsContainer> HINT_VISIBILITY =
|
||||
new FloatProperty<ProactiveHintsContainer>("hint_visibility") {
|
||||
@Override
|
||||
public void setValue(ProactiveHintsContainer proactiveHintsContainer, float v) {
|
||||
proactiveHintsContainer.setHintVisibility(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(ProactiveHintsContainer proactiveHintsContainer) {
|
||||
return proactiveHintsContainer.mHintVisibility;
|
||||
}
|
||||
};
|
||||
|
||||
private float mHintVisibility;
|
||||
|
||||
public ProactiveHintsContainer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ProactiveHintsContainer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ProactiveHintsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public ProactiveHintsContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public void setView(View v) {
|
||||
removeAllViews();
|
||||
addView(v);
|
||||
}
|
||||
|
||||
public void setHintVisibility(float v) {
|
||||
if (v == 1) {
|
||||
setVisibility(VISIBLE);
|
||||
} else {
|
||||
setVisibility(GONE);
|
||||
}
|
||||
mHintVisibility = v;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
package com.android.quickstep.inputconsumers;
|
||||
|
||||
import static android.view.MotionEvent.ACTION_CANCEL;
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
+68
-40
@@ -14,16 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep;
|
||||
package com.android.quickstep.inputconsumers;
|
||||
|
||||
import static android.view.MotionEvent.ACTION_CANCEL;
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_MOVE;
|
||||
import static android.view.MotionEvent.ACTION_POINTER_UP;
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
|
||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPLEFT;
|
||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPRIGHT;
|
||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.FLING;
|
||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE;
|
||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE_NOOP;
|
||||
import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR;
|
||||
@@ -38,11 +38,12 @@ import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.logging.UserEventDispatcher;
|
||||
import com.android.launcher3.touch.SwipeDetector;
|
||||
import com.android.quickstep.ActivityControlHelper;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
import com.android.systemui.shared.system.InputMonitorCompat;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
@@ -50,12 +51,15 @@ import com.android.systemui.shared.system.QuickStepContract;
|
||||
/**
|
||||
* Touch consumer for handling events to launch assistant from launcher
|
||||
*/
|
||||
public class AssistantTouchConsumer extends DelegateInputConsumer {
|
||||
public class AssistantTouchConsumer extends DelegateInputConsumer
|
||||
implements SwipeDetector.Listener {
|
||||
|
||||
private static final String TAG = "AssistantTouchConsumer";
|
||||
private static final long RETRACT_ANIMATION_DURATION_MS = 300;
|
||||
|
||||
private static final String INVOCATION_TYPE_KEY = "invocation_type";
|
||||
private static final int INVOCATION_TYPE_GESTURE = 1;
|
||||
private static final int INVOCATION_TYPE_FLING = 6;
|
||||
|
||||
private final PointF mDownPos = new PointF();
|
||||
private final PointF mLastPos = new PointF();
|
||||
@@ -77,6 +81,7 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
|
||||
private final float mSlop;
|
||||
private final ISystemUiProxy mSysUiProxy;
|
||||
private final Context mContext;
|
||||
private final SwipeDetector mSwipeDetector;
|
||||
|
||||
public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
|
||||
ActivityControlHelper activityControlHelper, InputConsumer delegate,
|
||||
@@ -90,6 +95,8 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
|
||||
mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
|
||||
mSlop = QuickStepContract.getQuickStepDragSlopPx();
|
||||
mActivityControlHelper = activityControlHelper;
|
||||
mSwipeDetector = new SwipeDetector(mContext, this, SwipeDetector.VERTICAL);
|
||||
mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,6 +107,7 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
|
||||
@Override
|
||||
public void onMotionEvent(MotionEvent ev) {
|
||||
// TODO add logging
|
||||
mSwipeDetector.onTouchEvent(ev);
|
||||
|
||||
switch (ev.getActionMasked()) {
|
||||
case ACTION_DOWN: {
|
||||
@@ -146,7 +154,7 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
|
||||
|
||||
// Determine if angle is larger than threshold for assistant detection
|
||||
float angle = (float) Math.toDegrees(
|
||||
Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x));
|
||||
Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x));
|
||||
mDirection = angle > 90 ? UPLEFT : UPRIGHT;
|
||||
angle = angle > 90 ? 180 - angle : angle;
|
||||
|
||||
@@ -159,7 +167,7 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
|
||||
} else {
|
||||
// Movement
|
||||
mDistance = (float) Math.hypot(mLastPos.x - mStartDragPos.x,
|
||||
mLastPos.y - mStartDragPos.y);
|
||||
mLastPos.y - mStartDragPos.y);
|
||||
if (mDistance >= 0) {
|
||||
final long diff = SystemClock.uptimeMillis() - mDragTime;
|
||||
mTimeFraction = Math.min(diff * 1f / mTimeThreshold, 1);
|
||||
@@ -172,18 +180,18 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
|
||||
case ACTION_UP:
|
||||
if (mState != STATE_DELEGATE_ACTIVE && !mLaunchedAssistant) {
|
||||
ValueAnimator animator = ValueAnimator.ofFloat(mLastProgress, 0)
|
||||
.setDuration(RETRACT_ANIMATION_DURATION_MS);
|
||||
.setDuration(RETRACT_ANIMATION_DURATION_MS);
|
||||
UserEventDispatcher.newInstance(mContext).logActionOnContainer(
|
||||
SWIPE_NOOP, mDirection, NAVBAR);
|
||||
SWIPE_NOOP, mDirection, NAVBAR);
|
||||
animator.addUpdateListener(valueAnimator -> {
|
||||
float progress = (float) valueAnimator.getAnimatedValue();
|
||||
try {
|
||||
float progress = (float) valueAnimator.getAnimatedValue();
|
||||
try {
|
||||
|
||||
mSysUiProxy.onAssistantProgress(progress);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to send SysUI start/send assistant progress: "
|
||||
+ progress, e);
|
||||
}
|
||||
mSysUiProxy.onAssistantProgress(progress);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to send SysUI start/send assistant progress: "
|
||||
+ progress, e);
|
||||
}
|
||||
});
|
||||
animator.setInterpolator(Interpolators.DEACCEL_2);
|
||||
animator.start();
|
||||
@@ -200,38 +208,58 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
|
||||
|
||||
private void updateAssistantProgress() {
|
||||
if (!mLaunchedAssistant) {
|
||||
float progress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction;
|
||||
mLastProgress = progress;
|
||||
try {
|
||||
mSysUiProxy.onAssistantProgress(progress);
|
||||
if (mDistance >= mDistThreshold && mTimeFraction >= 1) {
|
||||
UserEventDispatcher.newInstance(mContext).logActionOnContainer(
|
||||
SWIPE, mDirection, NAVBAR);
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
|
||||
|
||||
BaseDraggingActivity launcherActivity =
|
||||
mActivityControlHelper.getCreatedActivity();
|
||||
if (launcherActivity != null) {
|
||||
launcherActivity.getRootView().
|
||||
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
|
||||
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
|
||||
}
|
||||
|
||||
mSysUiProxy.startAssistant(args);
|
||||
mLaunchedAssistant = true;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + progress, e);
|
||||
}
|
||||
mLastProgress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction;
|
||||
updateAssistant(SWIPE);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean withinTouchRegion(Context context, MotionEvent ev) {
|
||||
private void updateAssistant(int gestureType) {
|
||||
try {
|
||||
mSysUiProxy.onAssistantProgress(mLastProgress);
|
||||
if (gestureType == FLING || (mDistance >= mDistThreshold && mTimeFraction >= 1)) {
|
||||
UserEventDispatcher.newInstance(mContext)
|
||||
.logActionOnContainer(gestureType, mDirection, NAVBAR);
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
|
||||
|
||||
BaseDraggingActivity launcherActivity = mActivityControlHelper.getCreatedActivity();
|
||||
if (launcherActivity != null) {
|
||||
launcherActivity.getRootView().performHapticFeedback(
|
||||
13, // HapticFeedbackConstants.GESTURE_END
|
||||
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
|
||||
}
|
||||
|
||||
mSysUiProxy.startAssistant(args);
|
||||
mLaunchedAssistant = true;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + mLastProgress, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean withinTouchRegion(Context context, MotionEvent ev) {
|
||||
final Resources res = context.getResources();
|
||||
final int width = res.getDisplayMetrics().widthPixels;
|
||||
final int height = res.getDisplayMetrics().heightPixels;
|
||||
final int size = res.getDimensionPixelSize(R.dimen.gestures_assistant_size);
|
||||
return (ev.getX() > width - size || ev.getX() < size) && ev.getY() > height - size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragStart(boolean start) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDrag(float displacement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragEnd(float velocity, boolean fling) {
|
||||
if (fling) {
|
||||
mLastProgress = 1;
|
||||
updateAssistant(FLING);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.android.quickstep;
|
||||
package com.android.quickstep.inputconsumers;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
package com.android.quickstep.inputconsumers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
+2
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
package com.android.quickstep.inputconsumers;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
@@ -30,6 +30,7 @@ public interface InputConsumer {
|
||||
int TYPE_ASSISTANT = 1 << 3;
|
||||
int TYPE_DEVICE_LOCKED = 1 << 4;
|
||||
int TYPE_ACCESSIBILITY = 1 << 5;
|
||||
int TYPE_SCREEN_PINNED = 1 << 6;
|
||||
|
||||
InputConsumer NO_OP = () -> TYPE_NO_OP;
|
||||
|
||||
+22
-16
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
package com.android.quickstep.inputconsumers;
|
||||
|
||||
import static android.view.MotionEvent.ACTION_CANCEL;
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
@@ -21,8 +21,9 @@ import static android.view.MotionEvent.ACTION_MOVE;
|
||||
import static android.view.MotionEvent.ACTION_POINTER_UP;
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
import static android.view.MotionEvent.INVALID_POINTER_ID;
|
||||
|
||||
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
|
||||
import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_LANDSCAPE;
|
||||
import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_SEASCAPE;
|
||||
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
|
||||
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
|
||||
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
|
||||
@@ -44,12 +45,20 @@ import android.view.VelocityTracker;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.graphics.RotationMode;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.launcher3.util.RaceConditionTracker;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
import com.android.quickstep.ActivityControlHelper;
|
||||
import com.android.quickstep.OverviewCallbacks;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.SwipeSharedState;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.quickstep.WindowTransformSwipeHandler;
|
||||
import com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget;
|
||||
import com.android.quickstep.util.CachedEventDispatcher;
|
||||
import com.android.quickstep.util.MotionPauseDetector;
|
||||
@@ -62,8 +71,6 @@ import com.android.systemui.shared.system.QuickStepContract;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
/**
|
||||
* Input consumer for handling events originating from an activity other than Launcher
|
||||
*/
|
||||
@@ -108,7 +115,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
|
||||
// Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
|
||||
private float mStartDisplacement;
|
||||
private float mStartDisplacementX;
|
||||
|
||||
private Handler mMainThreadHandler;
|
||||
private Runnable mCancelRecentsAnimationRunnable = () -> {
|
||||
@@ -167,8 +173,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
&& !mRecentsViewDispatcher.hasConsumer()) {
|
||||
mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
|
||||
isNavBarOnLeft()
|
||||
? RotationMode.SEASCAPE
|
||||
: (isNavBarOnRight() ? RotationMode.LANDSCAPE : RotationMode.NORMAL)));
|
||||
? ROTATION_SEASCAPE
|
||||
: (isNavBarOnRight() ? ROTATION_LANDSCAPE : RotationMode.NORMAL)));
|
||||
}
|
||||
int edgeFlags = ev.getEdgeFlags();
|
||||
ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
|
||||
@@ -227,7 +233,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
if (Math.abs(displacement) > mDragSlop) {
|
||||
mPassedDragSlop = true;
|
||||
mStartDisplacement = displacement;
|
||||
mStartDisplacementX = displacementX;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,19 +249,20 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
if (!mPassedDragSlop) {
|
||||
mPassedDragSlop = true;
|
||||
mStartDisplacement = displacement;
|
||||
mStartDisplacementX = displacementX;
|
||||
}
|
||||
notifyGestureStarted();
|
||||
}
|
||||
}
|
||||
|
||||
if (mPassedDragSlop && mInteractionHandler != null) {
|
||||
// Move
|
||||
mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
|
||||
if (mInteractionHandler != null) {
|
||||
if (mPassedDragSlop) {
|
||||
// Move
|
||||
mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
|
||||
}
|
||||
|
||||
if (mMode == Mode.NO_BUTTON) {
|
||||
float horizontalDist = Math.abs(displacementX - mStartDisplacementX);
|
||||
float upDist = -(displacement - mStartDisplacement);
|
||||
float horizontalDist = Math.abs(displacementX);
|
||||
float upDist = -displacement;
|
||||
boolean isLikelyToStartNewTask = horizontalDist > upDist;
|
||||
mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
|
||||
|| isLikelyToStartNewTask);
|
||||
@@ -378,11 +384,11 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
// The consumer is being switched while we are active. Set up the shared state to be
|
||||
// used by the next animation
|
||||
removeListener();
|
||||
GestureEndTarget endTarget = mInteractionHandler.mGestureEndTarget;
|
||||
GestureEndTarget endTarget = mInteractionHandler.getGestureEndTarget();
|
||||
mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
|
||||
mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
|
||||
if (mSwipeSharedState.canGestureBeContinued) {
|
||||
mInteractionHandler.cancelCurrentAnimation();
|
||||
mInteractionHandler.cancelCurrentAnimation(mSwipeSharedState);
|
||||
} else {
|
||||
mInteractionHandler.reset();
|
||||
}
|
||||
+3
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
package com.android.quickstep.inputconsumers;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
|
||||
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
|
||||
@@ -27,6 +27,8 @@ import androidx.annotation.Nullable;
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
import com.android.quickstep.ActivityControlHelper;
|
||||
import com.android.quickstep.OverviewCallbacks;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.InputMonitorCompat;
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.inputconsumers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.quickstep.ActivityControlHelper;
|
||||
import com.android.quickstep.util.MotionPauseDetector;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
|
||||
/**
|
||||
* An input consumer that detects swipe up and hold to exit screen pinning mode.
|
||||
*/
|
||||
public class ScreenPinnedInputConsumer implements InputConsumer {
|
||||
|
||||
private static final String TAG = "ScreenPinnedConsumer";
|
||||
|
||||
private final float mMotionPauseMinDisplacement;
|
||||
private final MotionPauseDetector mMotionPauseDetector;
|
||||
|
||||
private float mTouchDownY;
|
||||
|
||||
public ScreenPinnedInputConsumer(Context context, ISystemUiProxy sysuiProxy,
|
||||
ActivityControlHelper activityControl) {
|
||||
mMotionPauseMinDisplacement = context.getResources().getDimension(
|
||||
R.dimen.motion_pause_detector_min_displacement_from_app);
|
||||
mMotionPauseDetector = new MotionPauseDetector(context, true /* makePauseHarderToTrigger*/);
|
||||
mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
|
||||
if (isPaused) {
|
||||
try {
|
||||
sysuiProxy.stopScreenPinning();
|
||||
BaseDraggingActivity launcherActivity = activityControl.getCreatedActivity();
|
||||
if (launcherActivity != null) {
|
||||
launcherActivity.getRootView().performHapticFeedback(
|
||||
HapticFeedbackConstants.LONG_PRESS,
|
||||
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
|
||||
}
|
||||
mMotionPauseDetector.clear();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to stop screen pinning ", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return TYPE_SCREEN_PINNED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMotionEvent(MotionEvent ev) {
|
||||
float y = ev.getY();
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mTouchDownY = y;
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
float displacement = mTouchDownY - y;
|
||||
mMotionPauseDetector.setDisallowPause(displacement < mMotionPauseMinDisplacement);
|
||||
mMotionPauseDetector.addPosition(y, ev.getEventTime());
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
mMotionPauseDetector.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -35,12 +35,14 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskThumbnailView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.shared.recents.ISystemUiProxy;
|
||||
import com.android.systemui.shared.recents.utilities.RectFEvaluator;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
@@ -280,6 +282,21 @@ public class ClipAnimationHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute scale and translation y such that the specified task view fills the screen.
|
||||
*/
|
||||
public LauncherState.ScaleAndTranslation getOverviewFullscreenScaleAndTranslation(TaskView v) {
|
||||
TaskThumbnailView thumbnailView = v.getThumbnail();
|
||||
RecentsView recentsView = v.getRecentsView();
|
||||
fromTaskThumbnailView(thumbnailView, recentsView);
|
||||
Rect taskSize = new Rect();
|
||||
recentsView.getTaskSize(taskSize);
|
||||
updateTargetRect(taskSize);
|
||||
float scale = mSourceRect.width() / mTargetRect.width();
|
||||
float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY();
|
||||
return new LauncherState.ScaleAndTranslation(scale, 0, translationY);
|
||||
}
|
||||
|
||||
private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
|
||||
ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
|
||||
if (sysUiProxy != null) {
|
||||
|
||||
+62
-15
@@ -19,6 +19,7 @@ import android.animation.Animator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
import android.util.FloatProperty;
|
||||
@@ -26,6 +27,7 @@ import android.util.FloatProperty;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.anim.FlingSpringAnim;
|
||||
@@ -33,6 +35,8 @@ import com.android.launcher3.anim.FlingSpringAnim;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL;
|
||||
|
||||
/**
|
||||
* Applies spring forces to animate from a starting rect to a target rect,
|
||||
* while providing update callbacks to the caller.
|
||||
@@ -45,7 +49,7 @@ public class RectFSpringAnim {
|
||||
* can be done in parallel at a fixed duration. Update callbacks are sent based on the progress
|
||||
* of this animation, while the end callback is sent after all animations finish.
|
||||
*/
|
||||
private static final long RECT_SCALE_DURATION = 180;
|
||||
private static final long RECT_SCALE_DURATION = 250;
|
||||
|
||||
private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X =
|
||||
new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") {
|
||||
@@ -61,16 +65,16 @@ public class RectFSpringAnim {
|
||||
}
|
||||
};
|
||||
|
||||
private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_Y =
|
||||
new FloatPropertyCompat<RectFSpringAnim>("rectCenterYSpring") {
|
||||
private static final FloatPropertyCompat<RectFSpringAnim> RECT_Y =
|
||||
new FloatPropertyCompat<RectFSpringAnim>("rectYSpring") {
|
||||
@Override
|
||||
public float getValue(RectFSpringAnim anim) {
|
||||
return anim.mCurrentCenterY;
|
||||
return anim.mCurrentY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(RectFSpringAnim anim, float currentCenterY) {
|
||||
anim.mCurrentCenterY = currentCenterY;
|
||||
public void setValue(RectFSpringAnim anim, float y) {
|
||||
anim.mCurrentY = y;
|
||||
anim.onUpdate();
|
||||
}
|
||||
};
|
||||
@@ -96,7 +100,9 @@ public class RectFSpringAnim {
|
||||
private final List<Animator.AnimatorListener> mAnimatorListeners = new ArrayList<>();
|
||||
|
||||
private float mCurrentCenterX;
|
||||
private float mCurrentCenterY;
|
||||
private float mCurrentY;
|
||||
// If true, tracking the bottom of the rects, else tracking the top.
|
||||
private boolean mTrackingBottomY;
|
||||
private float mCurrentScaleProgress;
|
||||
private FlingSpringAnim mRectXAnim;
|
||||
private FlingSpringAnim mRectYAnim;
|
||||
@@ -106,11 +112,33 @@ public class RectFSpringAnim {
|
||||
private boolean mRectYAnimEnded;
|
||||
private boolean mRectScaleAnimEnded;
|
||||
|
||||
public RectFSpringAnim(RectF startRect, RectF targetRect) {
|
||||
private float mMinVisChange;
|
||||
private float mYOvershoot;
|
||||
|
||||
public RectFSpringAnim(RectF startRect, RectF targetRect, Resources resources) {
|
||||
mStartRect = startRect;
|
||||
mTargetRect = targetRect;
|
||||
mCurrentCenterX = mStartRect.centerX();
|
||||
mCurrentCenterY = mStartRect.centerY();
|
||||
|
||||
mTrackingBottomY = startRect.bottom < targetRect.bottom;
|
||||
mCurrentY = mTrackingBottomY ? mStartRect.bottom : mStartRect.top;
|
||||
|
||||
mMinVisChange = resources.getDimensionPixelSize(R.dimen.swipe_up_fling_min_visible_change);
|
||||
mYOvershoot = resources.getDimensionPixelSize(R.dimen.swipe_up_y_overshoot);
|
||||
}
|
||||
|
||||
public void onTargetPositionChanged() {
|
||||
if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) {
|
||||
mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX());
|
||||
}
|
||||
|
||||
if (mRectYAnim != null) {
|
||||
if (mTrackingBottomY && mRectYAnim.getTargetPosition() != mTargetRect.bottom) {
|
||||
mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom);
|
||||
} else if (!mTrackingBottomY && mRectYAnim.getTargetPosition() != mTargetRect.top) {
|
||||
mRectYAnim.updatePosition(mCurrentY, mTargetRect.top);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addOnUpdateListener(OnUpdateListener onUpdateListener) {
|
||||
@@ -131,14 +159,28 @@ public class RectFSpringAnim {
|
||||
mRectYAnimEnded = true;
|
||||
maybeOnEnd();
|
||||
});
|
||||
mRectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, mCurrentCenterX,
|
||||
mTargetRect.centerX(), velocityPxPerMs.x * 1000, onXEndListener);
|
||||
mRectYAnim = new FlingSpringAnim(this, RECT_CENTER_Y, mCurrentCenterY,
|
||||
mTargetRect.centerY(), velocityPxPerMs.y * 1000, onYEndListener);
|
||||
|
||||
float startX = mCurrentCenterX;
|
||||
float endX = mTargetRect.centerX();
|
||||
float minXValue = Math.min(startX, endX);
|
||||
float maxXValue = Math.max(startX, endX);
|
||||
mRectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, startX, endX,
|
||||
velocityPxPerMs.x * 1000, mMinVisChange, minXValue, maxXValue, 1f, onXEndListener);
|
||||
|
||||
float startVelocityY = velocityPxPerMs.y * 1000;
|
||||
// Scale the Y velocity based on the initial velocity to tune the curves.
|
||||
float springVelocityFactor = 0.1f + 0.9f * Math.abs(startVelocityY) / 20000.0f;
|
||||
float startY = mCurrentY;
|
||||
float endY = mTrackingBottomY ? mTargetRect.bottom : mTargetRect.top;
|
||||
float minYValue = Math.min(startY, endY - mYOvershoot);
|
||||
float maxYValue = Math.max(startY, endY);
|
||||
mRectYAnim = new FlingSpringAnim(this, RECT_Y, startY, endY, startVelocityY,
|
||||
mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener);
|
||||
|
||||
mRectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||
PropertyValuesHolder.ofFloat(RECT_SCALE_PROGRESS, 1))
|
||||
.setDuration(RECT_SCALE_DURATION);
|
||||
mRectScaleAnim.setInterpolator(DEACCEL);
|
||||
mRectScaleAnim.addListener(new AnimationSuccessListener() {
|
||||
@Override
|
||||
public void onAnimationSuccess(Animator animator) {
|
||||
@@ -170,8 +212,13 @@ public class RectFSpringAnim {
|
||||
mTargetRect.width());
|
||||
float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(),
|
||||
mTargetRect.height());
|
||||
mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentCenterY - currentHeight / 2,
|
||||
mCurrentCenterX + currentWidth / 2, mCurrentCenterY + currentHeight / 2);
|
||||
if (mTrackingBottomY) {
|
||||
mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY - currentHeight,
|
||||
mCurrentCenterX + currentWidth / 2, mCurrentY);
|
||||
} else {
|
||||
mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY,
|
||||
mCurrentCenterX + currentWidth / 2, mCurrentY + currentHeight);
|
||||
}
|
||||
for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
|
||||
onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress);
|
||||
}
|
||||
|
||||
+14
@@ -53,6 +53,20 @@ public class SwipeAnimationTargetSet extends RemoteAnimationTargetSet {
|
||||
this.mOnFinishListener = onFinishListener;
|
||||
}
|
||||
|
||||
public boolean hasTargets() {
|
||||
return unfilteredApps.length != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the target set without any actual targets. Used only when continuing a gesture after
|
||||
* the actual recents animation has finished.
|
||||
*/
|
||||
public SwipeAnimationTargetSet cloneWithoutTargets() {
|
||||
return new SwipeAnimationTargetSet(controller, new RemoteAnimationTargetCompat[0],
|
||||
homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
|
||||
mOnFinishListener);
|
||||
}
|
||||
|
||||
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
|
||||
mOnFinishListener.accept(this);
|
||||
BACKGROUND_EXECUTOR.execute(() -> {
|
||||
|
||||
+64
@@ -34,6 +34,8 @@ import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
@@ -41,8 +43,11 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.appprediction.PredictionUiStateManager;
|
||||
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
|
||||
import com.android.launcher3.util.PendingAnimation;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
import com.android.launcher3.views.ScrimView;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
import com.android.quickstep.hints.ProactiveHintsContainer;
|
||||
import com.android.quickstep.util.ClipAnimationHelper;
|
||||
import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
|
||||
import com.android.quickstep.util.LayoutUtils;
|
||||
@@ -54,6 +59,8 @@ import com.android.quickstep.util.LayoutUtils;
|
||||
public class LauncherRecentsView extends RecentsView<Launcher> {
|
||||
|
||||
private final TransformParams mTransformParams = new TransformParams();
|
||||
private final int mChipOverhang;
|
||||
@Nullable private ProactiveHintsContainer mProactiveHintsContainer;
|
||||
|
||||
public LauncherRecentsView(Context context) {
|
||||
this(context, null);
|
||||
@@ -66,6 +73,16 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
||||
public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setContentAlpha(0);
|
||||
mChipOverhang = (int) context.getResources().getDimension(R.dimen.chip_hint_overhang);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
View hintContainer = mActivity.findViewById(R.id.hints);
|
||||
mProactiveHintsContainer =
|
||||
hintContainer instanceof ProactiveHintsContainer
|
||||
? (ProactiveHintsContainer) hintContainer : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,6 +101,11 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ProactiveHintsContainer getProactiveHintsContainer() {
|
||||
return mProactiveHintsContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
maybeDrawEmptyMessage(canvas);
|
||||
@@ -137,6 +159,48 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
|
||||
@Override
|
||||
protected void getTaskSize(DeviceProfile dp, Rect outRect) {
|
||||
LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
|
||||
if (mProactiveHintsContainer != null) {
|
||||
BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) mProactiveHintsContainer.getLayoutParams();
|
||||
params.bottomMargin = getHeight() - outRect.bottom - mChipOverhang;
|
||||
params.width = outRect.width();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
|
||||
PendingAnimation anim = super.createTaskLauncherAnimation(tv, duration);
|
||||
|
||||
if (mProactiveHintsContainer != null) {
|
||||
anim.anim.play(ObjectAnimator.ofFloat(
|
||||
mProactiveHintsContainer, ProactiveHintsContainer.HINT_VISIBILITY, 0));
|
||||
}
|
||||
|
||||
return anim;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
|
||||
boolean shouldRemoveTask, long duration) {
|
||||
PendingAnimation anim = super.createTaskDismissAnimation(taskView, animateTaskView,
|
||||
shouldRemoveTask, duration);
|
||||
|
||||
if (mProactiveHintsContainer != null) {
|
||||
anim.anim.play(ObjectAnimator.ofFloat(
|
||||
mProactiveHintsContainer, ProactiveHintsContainer.HINT_VISIBILITY, 0));
|
||||
anim.addEndListener(onEndListener -> {
|
||||
if (!onEndListener.isSuccess) {
|
||||
mProactiveHintsContainer.setHintVisibility(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return anim;
|
||||
}
|
||||
|
||||
public void setHintVisibility(float v) {
|
||||
if (mProactiveHintsContainer != null) {
|
||||
mProactiveHintsContainer.setHintVisibility(v);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -744,9 +744,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
public abstract void startHome();
|
||||
|
||||
public void reset() {
|
||||
setRunningTaskViewShowScreenshot(false);
|
||||
mRunningTaskId = -1;
|
||||
mRunningTaskTileHidden = false;
|
||||
setCurrentTask(-1);
|
||||
mIgnoreResetTaskId = -1;
|
||||
mTaskListChangeId = -1;
|
||||
|
||||
@@ -757,6 +755,15 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
setCurrentPage(0);
|
||||
}
|
||||
|
||||
public @Nullable TaskView getRunningTaskView() {
|
||||
return getTaskView(mRunningTaskId);
|
||||
}
|
||||
|
||||
public int getRunningTaskIndex() {
|
||||
TaskView tv = getRunningTaskView();
|
||||
return tv == null ? -1 : indexOfChild(tv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the view if anything in recents changed.
|
||||
*/
|
||||
@@ -767,14 +774,50 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the first task in the view represents {@param task} and reloads the view
|
||||
* if needed. This allows the swipe-up gesture to assume that the first tile always
|
||||
* corresponds to the correct task.
|
||||
* All subsequent calls to reload will keep the task as the first item until {@link #reset()}
|
||||
* is called.
|
||||
* Also scrolls the view to this task
|
||||
* Called when a gesture from an app is starting.
|
||||
*/
|
||||
public void showTask(int runningTaskId) {
|
||||
public void onGestureAnimationStart(int runningTaskId) {
|
||||
// This needs to be called before the other states are set since it can create the task view
|
||||
showCurrentTask(runningTaskId);
|
||||
setEnableFreeScroll(false);
|
||||
setEnableDrawingLiveTile(false);
|
||||
setRunningTaskHidden(true);
|
||||
setRunningTaskIconScaledDown(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called only when a swipe-up gesture from an app has completed. Only called after
|
||||
* {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
|
||||
*/
|
||||
public void onSwipeUpAnimationSuccess() {
|
||||
if (getRunningTaskView() != null) {
|
||||
float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get()
|
||||
? mLiveTileOverlay.cancelIconAnimation()
|
||||
: 0f;
|
||||
animateUpRunningTaskIconScale(startProgress);
|
||||
}
|
||||
setSwipeDownShouldLaunchApp(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a gesture from an app has finished.
|
||||
*/
|
||||
public void onGestureAnimationEnd() {
|
||||
setEnableFreeScroll(true);
|
||||
setEnableDrawingLiveTile(true);
|
||||
setOnScrollChangeListener(null);
|
||||
setRunningTaskViewShowScreenshot(true);
|
||||
setRunningTaskHidden(false);
|
||||
animateUpRunningTaskIconScale();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a task view (if necessary) to represent the task with the {@param runningTaskId}.
|
||||
*
|
||||
* All subsequent calls to reload will keep the task as the first item until {@link #reset()}
|
||||
* is called. Also scrolls the view to this task.
|
||||
*/
|
||||
public void showCurrentTask(int runningTaskId) {
|
||||
if (getChildCount() == 0) {
|
||||
// Add an empty view for now until the task plan is loaded and applied
|
||||
final TaskView taskView = mTaskViewPool.getView();
|
||||
@@ -789,16 +832,33 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
new ComponentName("", ""), false);
|
||||
taskView.bind(mTmpRunningTask);
|
||||
}
|
||||
|
||||
boolean runningTaskTileHidden = mRunningTaskTileHidden;
|
||||
setCurrentTask(runningTaskId);
|
||||
setCurrentPage(getRunningTaskIndex());
|
||||
setRunningTaskViewShowScreenshot(false);
|
||||
setRunningTaskHidden(runningTaskTileHidden);
|
||||
|
||||
// Reload the task list
|
||||
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
|
||||
}
|
||||
|
||||
public @Nullable TaskView getRunningTaskView() {
|
||||
return getTaskView(mRunningTaskId);
|
||||
}
|
||||
/**
|
||||
* Sets the running task id, cleaning up the old running task if necessary.
|
||||
* @param runningTaskId
|
||||
*/
|
||||
public void setCurrentTask(int runningTaskId) {
|
||||
if (mRunningTaskId == runningTaskId) {
|
||||
return;
|
||||
}
|
||||
|
||||
public int getRunningTaskIndex() {
|
||||
TaskView tv = getRunningTaskView();
|
||||
return tv == null ? -1 : indexOfChild(tv);
|
||||
if (mRunningTaskId != -1) {
|
||||
// Reset the state on the old running task view
|
||||
setRunningTaskIconScaledDown(false);
|
||||
setRunningTaskViewShowScreenshot(true);
|
||||
setRunningTaskHidden(false);
|
||||
}
|
||||
mRunningTaskId = runningTaskId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -812,27 +872,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #showTask(int)} but does not put any restrictions on the first tile.
|
||||
*/
|
||||
public void setCurrentTask(int runningTaskId) {
|
||||
boolean runningTaskTileHidden = mRunningTaskTileHidden;
|
||||
boolean runningTaskIconScaledDown = mRunningTaskIconScaledDown;
|
||||
|
||||
setRunningTaskIconScaledDown(false);
|
||||
setRunningTaskHidden(false);
|
||||
setRunningTaskViewShowScreenshot(true);
|
||||
mRunningTaskId = runningTaskId;
|
||||
setRunningTaskViewShowScreenshot(false);
|
||||
setRunningTaskIconScaledDown(runningTaskIconScaledDown);
|
||||
setRunningTaskHidden(runningTaskTileHidden);
|
||||
|
||||
setCurrentPage(getRunningTaskIndex());
|
||||
|
||||
// Load the tasks (if the loading is already
|
||||
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
|
||||
}
|
||||
|
||||
private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
|
||||
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
|
||||
TaskView runningTaskView = getRunningTaskView();
|
||||
@@ -1520,7 +1559,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
|
||||
|
||||
protected void onTaskLaunched(boolean success) {
|
||||
resetTaskVisuals();
|
||||
if (success) {
|
||||
resetTaskVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+47
-30
@@ -33,6 +33,7 @@ import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
@@ -59,7 +60,7 @@ public class TaskThumbnailView extends View {
|
||||
|
||||
private final static ColorMatrix COLOR_MATRIX = new ColorMatrix();
|
||||
private final static ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
|
||||
private final static Rect EMPTY_RECT = new Rect();
|
||||
private final static RectF EMPTY_RECT_F = new RectF();
|
||||
|
||||
public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
|
||||
new FloatProperty<TaskThumbnailView>("dimAlpha") {
|
||||
@@ -87,10 +88,9 @@ public class TaskThumbnailView extends View {
|
||||
private final Matrix mMatrix = new Matrix();
|
||||
|
||||
private float mClipBottom = -1;
|
||||
private Rect mScaledInsets = new Rect();
|
||||
private Rect mCurrentDrawnInsets = new Rect();
|
||||
private float mCurrentDrawnCornerRadius;
|
||||
private boolean mIsRotated;
|
||||
// Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
|
||||
private RectF mClippedInsets = new RectF();
|
||||
private TaskView.FullscreenDrawParams mFullscreenParams;
|
||||
|
||||
private Task mTask;
|
||||
private ThumbnailData mThumbnailData;
|
||||
@@ -118,7 +118,7 @@ public class TaskThumbnailView extends View {
|
||||
mDimmingPaintAfterClearing.setColor(Color.BLACK);
|
||||
mActivity = BaseActivity.fromContext(context);
|
||||
mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
|
||||
setCurrentDrawnInsetsAndRadius(EMPTY_RECT, mCornerRadius);
|
||||
mFullscreenParams = new TaskView.FullscreenDrawParams(mCornerRadius);
|
||||
}
|
||||
|
||||
public void bind(Task task) {
|
||||
@@ -201,23 +201,27 @@ public class TaskThumbnailView extends View {
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
RectF currentDrawnInsets = mFullscreenParams.mCurrentDrawnInsets;
|
||||
canvas.save();
|
||||
canvas.translate(currentDrawnInsets.left, currentDrawnInsets.top);
|
||||
canvas.scale(mFullscreenParams.mScale, mFullscreenParams.mScale);
|
||||
// Draw the insets if we're being drawn fullscreen (we do this for quick switch).
|
||||
drawOnCanvas(canvas,
|
||||
-mCurrentDrawnInsets.left,
|
||||
-mCurrentDrawnInsets.top,
|
||||
getMeasuredWidth() + mCurrentDrawnInsets.right,
|
||||
getMeasuredHeight() + mCurrentDrawnInsets.bottom,
|
||||
mCurrentDrawnCornerRadius);
|
||||
-currentDrawnInsets.left,
|
||||
-currentDrawnInsets.top,
|
||||
getMeasuredWidth() + currentDrawnInsets.right,
|
||||
getMeasuredHeight() + currentDrawnInsets.bottom,
|
||||
mFullscreenParams.mCurrentDrawnCornerRadius);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
public Rect getInsetsToDrawInFullscreen(boolean isMultiWindowMode) {
|
||||
// Don't show insets in the wrong orientation or in multi window mode.
|
||||
return mIsRotated || isMultiWindowMode ? EMPTY_RECT : mScaledInsets;
|
||||
public RectF getInsetsToDrawInFullscreen(boolean isMultiWindowMode) {
|
||||
// Don't show insets in multi window mode.
|
||||
return isMultiWindowMode ? EMPTY_RECT_F : mClippedInsets;
|
||||
}
|
||||
|
||||
public void setCurrentDrawnInsetsAndRadius(Rect insets, float radius) {
|
||||
mCurrentDrawnInsets.set(insets);
|
||||
mCurrentDrawnCornerRadius = radius;
|
||||
public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
|
||||
mFullscreenParams = fullscreenParams;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@@ -275,7 +279,7 @@ public class TaskThumbnailView extends View {
|
||||
}
|
||||
|
||||
private void updateThumbnailMatrix() {
|
||||
mIsRotated = false;
|
||||
boolean isRotated = false;
|
||||
mClipBottom = -1;
|
||||
if (mBitmapShader != null && mThumbnailData != null) {
|
||||
float scale = mThumbnailData.scale;
|
||||
@@ -296,30 +300,28 @@ public class TaskThumbnailView extends View {
|
||||
final Configuration configuration =
|
||||
getContext().getResources().getConfiguration();
|
||||
// Rotate the screenshot if not in multi-window mode
|
||||
mIsRotated = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION &&
|
||||
isRotated = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION &&
|
||||
configuration.orientation != mThumbnailData.orientation &&
|
||||
!mActivity.isInMultiWindowMode() &&
|
||||
mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
|
||||
// Scale the screenshot to always fit the width of the card.
|
||||
thumbnailScale = mIsRotated
|
||||
thumbnailScale = isRotated
|
||||
? getMeasuredWidth() / thumbnailHeight
|
||||
: getMeasuredWidth() / thumbnailWidth;
|
||||
}
|
||||
|
||||
mScaledInsets.set(thumbnailInsets);
|
||||
Utilities.scaleRect(mScaledInsets, thumbnailScale);
|
||||
|
||||
if (mIsRotated) {
|
||||
if (isRotated) {
|
||||
int rotationDir = profile.isVerticalBarLayout() && !profile.isSeascape() ? -1 : 1;
|
||||
mMatrix.setRotate(90 * rotationDir);
|
||||
int newLeftInset = rotationDir == 1 ? thumbnailInsets.bottom : thumbnailInsets.top;
|
||||
int newTopInset = rotationDir == 1 ? thumbnailInsets.left : thumbnailInsets.right;
|
||||
mMatrix.postTranslate(-newLeftInset * scale, -newTopInset * scale);
|
||||
mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
|
||||
if (rotationDir == -1) {
|
||||
// Crop the right/bottom side of the screenshot rather than left/top
|
||||
float excessHeight = thumbnailWidth * thumbnailScale - getMeasuredHeight();
|
||||
mMatrix.postTranslate(0, -excessHeight);
|
||||
mClippedInsets.offset(0, excessHeight);
|
||||
}
|
||||
mMatrix.postTranslate(-mClippedInsets.left, -mClippedInsets.top);
|
||||
// Move the screenshot to the thumbnail window (rotation moved it out).
|
||||
if (rotationDir == 1) {
|
||||
mMatrix.postTranslate(mThumbnailData.thumbnail.getHeight(), 0);
|
||||
@@ -327,13 +329,28 @@ public class TaskThumbnailView extends View {
|
||||
mMatrix.postTranslate(0, mThumbnailData.thumbnail.getWidth());
|
||||
}
|
||||
} else {
|
||||
mMatrix.setTranslate(-mThumbnailData.insets.left * scale,
|
||||
-mThumbnailData.insets.top * scale);
|
||||
mClippedInsets.offsetTo(thumbnailInsets.left * scale, thumbnailInsets.top * scale);
|
||||
mMatrix.setTranslate(-mClippedInsets.left, -mClippedInsets.top);
|
||||
}
|
||||
|
||||
final float widthWithInsets;
|
||||
final float heightWithInsets;
|
||||
if (isRotated) {
|
||||
widthWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale;
|
||||
heightWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale;
|
||||
} else {
|
||||
widthWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale;
|
||||
heightWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale;
|
||||
}
|
||||
mClippedInsets.left *= thumbnailScale;
|
||||
mClippedInsets.top *= thumbnailScale;
|
||||
mClippedInsets.right = widthWithInsets - mClippedInsets.left - getMeasuredWidth();
|
||||
mClippedInsets.bottom = heightWithInsets - mClippedInsets.top - getMeasuredHeight();
|
||||
|
||||
mMatrix.postScale(thumbnailScale, thumbnailScale);
|
||||
mBitmapShader.setLocalMatrix(mMatrix);
|
||||
|
||||
float bitmapHeight = Math.max((mIsRotated ? thumbnailWidth : thumbnailHeight)
|
||||
float bitmapHeight = Math.max((isRotated ? thumbnailWidth : thumbnailHeight)
|
||||
* thumbnailScale, 0);
|
||||
if (Math.round(bitmapHeight) < getMeasuredHeight()) {
|
||||
mClipBottom = bitmapHeight;
|
||||
@@ -341,7 +358,7 @@ public class TaskThumbnailView extends View {
|
||||
mPaint.setShader(mBitmapShader);
|
||||
}
|
||||
|
||||
if (mIsRotated) {
|
||||
if (isRotated) {
|
||||
// The overlay doesn't really work when the screenshot is rotated, so don't add it.
|
||||
mOverlay.reset();
|
||||
} else {
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -166,7 +167,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
private float mCurveScale;
|
||||
private float mZoomScale;
|
||||
private float mFullscreenProgress;
|
||||
private final Rect mCurrentDrawnInsets = new Rect();
|
||||
private final FullscreenDrawParams mCurrentFullscreenParams;
|
||||
private final float mCornerRadius;
|
||||
private final float mWindowCornerRadius;
|
||||
private final BaseDraggingActivity mActivity;
|
||||
@@ -214,7 +215,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
});
|
||||
mCornerRadius = TaskCornerRadius.get(context);
|
||||
mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
|
||||
mOutlineProvider = new TaskOutlineProvider(getResources(), mCornerRadius);
|
||||
mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius);
|
||||
mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams);
|
||||
setOutlineProvider(mOutlineProvider);
|
||||
}
|
||||
|
||||
@@ -540,26 +542,26 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
private static final class TaskOutlineProvider extends ViewOutlineProvider {
|
||||
|
||||
private final int mMarginTop;
|
||||
private final Rect mInsets = new Rect();
|
||||
private float mRadius;
|
||||
private FullscreenDrawParams mFullscreenParams;
|
||||
|
||||
TaskOutlineProvider(Resources res, float radius) {
|
||||
TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) {
|
||||
mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
|
||||
mRadius = radius;
|
||||
mFullscreenParams = fullscreenParams;
|
||||
}
|
||||
|
||||
public void setCurrentDrawnInsetsAndRadius(Rect insets, float radius) {
|
||||
mInsets.set(insets);
|
||||
mRadius = radius;
|
||||
public void setFullscreenParams(FullscreenDrawParams params) {
|
||||
mFullscreenParams = params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
outline.setRoundRect(-mInsets.left,
|
||||
mMarginTop - mInsets.top,
|
||||
view.getWidth() + mInsets.right,
|
||||
view.getHeight() + mInsets.bottom,
|
||||
mRadius);
|
||||
RectF insets = mFullscreenParams.mCurrentDrawnInsets;
|
||||
float scale = mFullscreenParams.mScale;
|
||||
outline.setRoundRect(0,
|
||||
(int) (mMarginTop * scale),
|
||||
(int) ((insets.left + view.getWidth() + insets.right) * scale),
|
||||
(int) ((insets.top + view.getHeight() + insets.bottom) * scale),
|
||||
mFullscreenParams.mCurrentDrawnCornerRadius);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,17 +660,25 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
|
||||
TaskThumbnailView thumbnail = getThumbnail();
|
||||
boolean isMultiWindowMode = mActivity.getDeviceProfile().isMultiWindowMode;
|
||||
Rect insets = thumbnail.getInsetsToDrawInFullscreen(isMultiWindowMode);
|
||||
mCurrentDrawnInsets.set((int) (insets.left * mFullscreenProgress),
|
||||
(int) (insets.top * mFullscreenProgress),
|
||||
(int) (insets.right * mFullscreenProgress),
|
||||
(int) (insets.bottom * mFullscreenProgress));
|
||||
RectF insets = thumbnail.getInsetsToDrawInFullscreen(isMultiWindowMode);
|
||||
float currentInsetsLeft = insets.left * mFullscreenProgress;
|
||||
float currentInsetsRight = insets.right * mFullscreenProgress;
|
||||
mCurrentFullscreenParams.setInsets(currentInsetsLeft,
|
||||
insets.top * mFullscreenProgress,
|
||||
currentInsetsRight,
|
||||
insets.bottom * mFullscreenProgress);
|
||||
float fullscreenCornerRadius = isMultiWindowMode ? 0 : mWindowCornerRadius;
|
||||
float cornerRadius = Utilities.mapRange(mFullscreenProgress, mCornerRadius,
|
||||
fullscreenCornerRadius) / getRecentsView().getScaleX();
|
||||
mCurrentFullscreenParams.setCornerRadius(Utilities.mapRange(mFullscreenProgress,
|
||||
mCornerRadius, fullscreenCornerRadius) / getRecentsView().getScaleX());
|
||||
// We scaled the thumbnail to fit the content (excluding insets) within task view width.
|
||||
// Now that we are drawing left/right insets again, we need to scale down to fit them.
|
||||
if (getWidth() > 0) {
|
||||
mCurrentFullscreenParams.setScale(getWidth()
|
||||
/ (getWidth() + currentInsetsLeft + currentInsetsRight));
|
||||
}
|
||||
|
||||
thumbnail.setCurrentDrawnInsetsAndRadius(mCurrentDrawnInsets, cornerRadius);
|
||||
mOutlineProvider.setCurrentDrawnInsetsAndRadius(mCurrentDrawnInsets, cornerRadius);
|
||||
thumbnail.setFullscreenParams(mCurrentFullscreenParams);
|
||||
mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams);
|
||||
invalidateOutline();
|
||||
}
|
||||
|
||||
@@ -686,4 +696,30 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
|
||||
}
|
||||
return mShowScreenshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
|
||||
*/
|
||||
static class FullscreenDrawParams {
|
||||
RectF mCurrentDrawnInsets = new RectF();
|
||||
float mCurrentDrawnCornerRadius;
|
||||
/** The current scale we apply to the thumbnail to adjust for new left/right insets. */
|
||||
float mScale = 1;
|
||||
|
||||
public FullscreenDrawParams(float cornerRadius) {
|
||||
setCornerRadius(cornerRadius);
|
||||
}
|
||||
|
||||
public void setInsets(float left, float top, float right, float bottom) {
|
||||
mCurrentDrawnInsets.set(left, top, right, bottom);
|
||||
}
|
||||
|
||||
public void setCornerRadius(float cornerRadius) {
|
||||
mCurrentDrawnCornerRadius = cornerRadius;
|
||||
}
|
||||
|
||||
public void setScale(float scale) {
|
||||
mScale = scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,4 +61,14 @@
|
||||
android:gravity="center_vertical"
|
||||
/>
|
||||
</com.android.quickstep.views.DigitalWellBeingToast>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/proactive_suggest_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="bottom|center"
|
||||
android:translationY="20dp"
|
||||
android:elevation="4dp"
|
||||
/>
|
||||
</com.android.quickstep.views.TaskView>
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minuut"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> oor vandag"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Programvoorstelle"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Alle programme"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Jou voorspelde programme"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>፣ <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 ደቂቃ"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"ዛሬ <xliff:g id="TIME">%1$s</xliff:g> ቀርቷል"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"የመተግበሪያ ጥቆማዎች"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"ሁሉም መተግበሪያዎች"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"የእርስዎ የሚገመቱ መተግበሪያዎች"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>، <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"أقل من دقيقة"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"يتبقى اليوم <xliff:g id="TIME">%1$s</xliff:g>."</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"اقتراحات التطبيقات"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"جميع التطبيقات"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"تطبيقاتك المتوقّعة"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 dəq"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Bu gün <xliff:g id="TIME">%1$s</xliff:g> qaldı"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Tətbiq təklifləri"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Bütün tətbiqlər"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Təklif edilən tətbiqlər"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Još <xliff:g id="TIME">%1$s</xliff:g> danas"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Predlozi aplikacija"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Sve aplikacije"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Predviđene aplikacije"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 хв"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Сёння засталося <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Прапановы праграм"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Усе праграмы"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Вашы праграмы з падказак"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 мин"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Оставащо време днес: <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Предложения за приложения"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Всички приложения"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Предвидени приложения"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< ১ মি."</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"আজকে <xliff:g id="TIME">%1$s</xliff:g> বাকি আছে"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"অ্যাপের সাজেশন"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"সব অ্যাপ"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"আপনার প্রয়োজন হতে পারে এমন অ্যাপ"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Preostalo vrijeme: <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Prijedlozi za aplikacije"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Sve aplikacije"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Predviđene aplikacije"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>; <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minut"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"temps restant avui: <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Suggeriments d\'aplicacions"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Totes les aplicacions"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Prediccions d\'aplicacions"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minuta"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"dnes zbývá: <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Návrhy aplikací"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Všechny aplikace"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vaše předpovídané aplikace"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> tilbage i dag"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Appforslag"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Alle apps"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Dine foreslåede apps"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Heute noch <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"App-Vorschläge"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Alle Apps"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"App-Vorschläge für dich"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 λ."</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Απομένουν <xliff:g id="TIME">%1$s</xliff:g> σήμερα"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Προτάσεις εφαρμογών"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Όλες οι εφαρμογές"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Προβλέψεις εφαρμογών"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minute"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"App suggestions"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"All apps"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Your predicted apps"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minute"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"App suggestions"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"All apps"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Your predicted apps"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minute"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"App suggestions"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"All apps"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Your predicted apps"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minuto"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Tiempo restante: <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Sugerencias de apps"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Todas las apps"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Predicción de tus apps"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"<1 minuto"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"tiempo restante: <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Sugerencias de aplicaciones"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Todas las aplicaciones"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Predicción de aplicaciones"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minut"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Tääna jäänud <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Rakenduste soovitused"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Kõik rakendused"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Teie ennustatud rakendused"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> gelditzen dira gaur"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Iradokitako aplikazioak"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Aplikazio guztiak"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Lagungarri izan dakizkizukeen aplikazioak"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>، <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< ۱ دقیقه"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> باقیمانده برای امروز"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"برنامههای پیشنهادی"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"همه برنامهها"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"برنامههای پیشبینیشده"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> jäljellä tänään"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Sovellusehdotukset"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Kaikki sovellukset"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Sovellusennusteet"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> : <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Il reste <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Suggestions d\'applications"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Toutes les applications"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vos prédictions d\'applications"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Encore <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Suggestions d\'applications"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Toutes les applications"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vos applications prévues"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"<1 min"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Tempo restante hoxe <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Suxestións de aplicacións"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Todas as aplicacións"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"As túas aplicacións preditas"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 મિનિટ"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> આજે બાકી"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"ઍપ સૂચનો"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"બધી ઍપ"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"તમારી પૂર્વાનુમાનિત ઍપ"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"<1 मिनट"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"आज <xliff:g id="TIME">%1$s</xliff:g> और चलेगा"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"ऐप्लिकेशन के सुझाव"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"सभी ऐप्लिकेशन"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"आपके काम के ऐप्लिकेशन"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Još <xliff:g id="TIME">%1$s</xliff:g> danas"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Predložene aplikacije"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Sve aplikacije"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vaše predviđene aplikacije"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 perc"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Ma még <xliff:g id="TIME">%1$s</xliff:g> van hátra"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Alkalmazásjavaslatok"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Az összes alkalmazás"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Várható alkalmazások"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 ր"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Այսօր մնացել է՝ <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Առաջարկվող հավելվածներ"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Բոլոր հավելվածները"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Ձեր կանխատեսված հավելվածները"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 menit"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> tersisa hari ini"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Saran aplikasi"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Semua aplikasi"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Aplikasi yang diprediksi"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 mín."</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> eftir í dag"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Tillögur að forritum"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Öll forrit"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Spáð forrit"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Rimanente oggi: <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"App suggerite"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Tutte le app"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Le app previste"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< דקה"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"הזמן שנותר להיום: <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"הצעות לאפליקציות"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"כל האפליקציות"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"האפליקציות החזויות שלך"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>、<xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"1 分未満"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"今日はあと <xliff:g id="TIME">%1$s</xliff:g>です"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"アプリの候補"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"すべてのアプリ"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"予測されたアプリ"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 წუთი"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"დღეს დარჩენილია <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"აპების შემოთავაზებები"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"ყველა აპი"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"თქვენი პროგნოზირებული აპები"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 мин"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Бүгін <xliff:g id="TIME">%1$s</xliff:g> қалды"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Қолданба ұсыныстары"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Барлық қолданбалар"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Ұсынылатын қолданбалар"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 នាទី"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"នៅសល់ <xliff:g id="TIME">%1$s</xliff:g> ទៀតនៅថ្ងៃនេះ"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"ការណែនាំកម្មវិធី"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"កម្មវិធីទាំងអស់"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"កម្មវិធីដែលបានព្យាកររបស់អ្នក"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 ನಿ"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"ಇಂದು <xliff:g id="TIME">%1$s</xliff:g> ಸಮಯ ಉಳಿದಿದೆ"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"ಆ್ಯಪ್ ಸಲಹೆಗಳು"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"ಎಲ್ಲಾ ಆ್ಯಪ್ಗಳು"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"ನಿಮ್ಮ ಸಂಭವನೀಯ ಆ್ಯಪ್ಗಳು"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1분"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"오늘 <xliff:g id="TIME">%1$s</xliff:g> 남음"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"앱 추천"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"모든 앱"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"추천 앱"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 мүнөт"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Бүгүн <xliff:g id="TIME">%1$s</xliff:g> мүнөт калды"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Колдонмо сунуштары"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Бардык колдонмолор"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Божомолдонгон колдонмолоруңуз"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 ນາທີ"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"ເຫຼືອ <xliff:g id="TIME">%1$s</xliff:g> ມື້ນີ້"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"ການແນະນຳແອັບ"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"ແອັບທັງໝົດ"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"ແອັບທີ່ຄາດເດົາໄວ້ແລ້ວຂອງທ່ານ"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min."</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Šiandien liko: <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Programų pasiūlymai"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Visos programos"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Numatomos programos"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"<1 minūte"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Šodien atlicis: <xliff:g id="TIME">%1$s</xliff:g>"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Ieteicamās lietotnes"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Visas lietotnes"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Jūsu prognozētās lietotnes"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 минута"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Уште <xliff:g id="TIME">%1$s</xliff:g> за денес"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Предлози за апликации"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Сите апликации"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Вашите предвидени апликации"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 മിനിറ്റ്"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"ഇന്ന് <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"ആപ്പ് നിർദ്ദേശങ്ങൾ"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"എല്ലാ ആപ്പുകളും"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"നിങ്ങളുടെ പ്രവചിക്കപ്പെട്ട ആപ്പുകൾ"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
|
||||
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 минут"</string>
|
||||
<string name="time_left_for_app" msgid="3111996412933644358">"Өнөөдөр <xliff:g id="TIME">%1$s</xliff:g> үлдсэн"</string>
|
||||
<string name="title_app_suggestions" msgid="4185902664111965088">"Аппын зөвлөмж"</string>
|
||||
<string name="all_apps_label" msgid="8542784161730910663">"Бүх апп"</string>
|
||||
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"Таны таамагласан аппууд"</string>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user