Merging ub-launcher3-qt-dev, build 5565340

am: 662f12fe62

Change-Id: Ib3d34a7370f0da285d795170dc755cac72adb1b2
This commit is contained in:
Jon Miranda
2019-05-15 15:21:34 -07:00
committed by android-build-merger
203 changed files with 2847 additions and 1115 deletions
+1
View File
@@ -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",
+4 -4
View File
@@ -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);
}
/**
+1
View File
@@ -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>
@@ -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);
}
}
@@ -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;
@@ -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;
}
@@ -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));
@@ -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
@@ -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);
}
}
@@ -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,
@@ -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) {
@@ -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 =
@@ -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);
@@ -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;
}
}
@@ -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;
@@ -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,4 +1,4 @@
package com.android.quickstep;
package com.android.quickstep.inputconsumers;
import android.view.MotionEvent;
@@ -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;
@@ -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;
@@ -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();
}
@@ -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;
@@ -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;
}
}
}
@@ -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) {
@@ -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);
}
@@ -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(() -> {
@@ -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
@@ -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;
}
}
}
+10
View File
@@ -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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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>
+3
View File
@@ -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">"&lt; 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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; ১ মি."</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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt;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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; ۱ دقیقه"</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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt;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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt;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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; דקה"</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>
+3
View File
@@ -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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt;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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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>
+3
View File
@@ -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">"&lt; 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