TaskView.java revision 59924fe0d9136cf349759bea1e06b661603f95fe
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
20
21import android.animation.Animator;
22import android.animation.AnimatorSet;
23import android.animation.ObjectAnimator;
24import android.animation.ValueAnimator;
25import android.content.Context;
26import android.content.res.Resources;
27import android.graphics.Outline;
28import android.graphics.Paint;
29import android.graphics.Point;
30import android.graphics.PorterDuff;
31import android.graphics.PorterDuffColorFilter;
32import android.graphics.Rect;
33import android.util.AttributeSet;
34import android.util.FloatProperty;
35import android.util.Property;
36import android.view.MotionEvent;
37import android.view.View;
38import android.view.ViewDebug;
39import android.view.ViewOutlineProvider;
40import android.widget.Toast;
41
42import com.android.internal.logging.MetricsLogger;
43import com.android.internal.logging.MetricsProto.MetricsEvent;
44import com.android.systemui.Interpolators;
45import com.android.systemui.R;
46import com.android.systemui.recents.Recents;
47import com.android.systemui.recents.RecentsActivity;
48import com.android.systemui.recents.RecentsConfiguration;
49import com.android.systemui.recents.events.EventBus;
50import com.android.systemui.recents.events.activity.LaunchTaskEvent;
51import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
52import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
53import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
54import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
55import com.android.systemui.recents.misc.ReferenceCountedTrigger;
56import com.android.systemui.recents.misc.SystemServicesProxy;
57import com.android.systemui.recents.misc.Utilities;
58import com.android.systemui.recents.model.Task;
59import com.android.systemui.recents.model.TaskStack;
60
61import java.util.ArrayList;
62
63/**
64 * A {@link TaskView} represents a fixed view of a task. Because the TaskView's layout is directed
65 * solely by the {@link TaskStackView}, we make it a fixed size layout which allows relayouts down
66 * the view hierarchy, but not upwards from any of its children (the TaskView will relayout itself
67 * with the previous bounds if any child requests layout).
68 */
69public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks,
70        TaskStackAnimationHelper.Callbacks, View.OnClickListener, View.OnLongClickListener {
71
72    /** The TaskView callbacks */
73    interface TaskViewCallbacks {
74        void onTaskViewClipStateChanged(TaskView tv);
75    }
76
77    /**
78     * The dim overlay is generally calculated from the task progress, but occasionally (like when
79     * launching) needs to be animated independently of the task progress.  This call is only used
80     * when animating the task into Recents, when the header dim is already applied
81     */
82    public static final Property<TaskView, Float> DIM_ALPHA_WITHOUT_HEADER =
83            new FloatProperty<TaskView>("dimAlphaWithoutHeader") {
84                @Override
85                public void setValue(TaskView tv, float dimAlpha) {
86                    tv.setDimAlphaWithoutHeader(dimAlpha);
87                }
88
89                @Override
90                public Float get(TaskView tv) {
91                    return tv.getDimAlpha();
92                }
93            };
94
95    /**
96     * The dim overlay is generally calculated from the task progress, but occasionally (like when
97     * launching) needs to be animated independently of the task progress.
98     */
99    public static final Property<TaskView, Float> DIM_ALPHA =
100            new FloatProperty<TaskView>("dimAlpha") {
101                @Override
102                public void setValue(TaskView tv, float dimAlpha) {
103                    tv.setDimAlpha(dimAlpha);
104                }
105
106                @Override
107                public Float get(TaskView tv) {
108                    return tv.getDimAlpha();
109                }
110            };
111
112    /**
113     * The dim overlay is generally calculated from the task progress, but occasionally (like when
114     * launching) needs to be animated independently of the task progress.
115     */
116    public static final Property<TaskView, Float> VIEW_OUTLINE_ALPHA =
117            new FloatProperty<TaskView>("viewOutlineAlpha") {
118                @Override
119                public void setValue(TaskView tv, float alpha) {
120                    tv.getViewBounds().setAlpha(alpha);
121                }
122
123                @Override
124                public Float get(TaskView tv) {
125                    return tv.getViewBounds().getAlpha();
126                }
127            };
128
129    @ViewDebug.ExportedProperty(category="recents")
130    float mDimAlpha;
131    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
132    Paint mDimLayerPaint = new Paint();
133    float mActionButtonTranslationZ;
134
135    @ViewDebug.ExportedProperty(deepExport=true, prefix="task_")
136    Task mTask;
137    @ViewDebug.ExportedProperty(category="recents")
138    boolean mTaskDataLoaded;
139    @ViewDebug.ExportedProperty(category="recents")
140    boolean mClipViewInStack = true;
141    @ViewDebug.ExportedProperty(category="recents")
142    boolean mTouchExplorationEnabled;
143    @ViewDebug.ExportedProperty(category="recents")
144    boolean mIsDisabledInSafeMode;
145    @ViewDebug.ExportedProperty(deepExport=true, prefix="view_bounds_")
146    AnimateableViewBounds mViewBounds;
147
148    private AnimatorSet mTransformAnimation;
149    private final TaskViewTransform mTargetAnimationTransform = new TaskViewTransform();
150    private ArrayList<Animator> mTmpAnimators = new ArrayList<>();
151
152    View mContent;
153    @ViewDebug.ExportedProperty(deepExport=true, prefix="thumbnail_")
154    TaskViewThumbnail mThumbnailView;
155    @ViewDebug.ExportedProperty(deepExport=true, prefix="header_")
156    TaskViewHeader mHeaderView;
157    View mActionButtonView;
158    TaskViewCallbacks mCb;
159
160    @ViewDebug.ExportedProperty(category="recents")
161    Point mDownTouchPos = new Point();
162
163    private Toast mDisabledAppToast;
164
165    public TaskView(Context context) {
166        this(context, null);
167    }
168
169    public TaskView(Context context, AttributeSet attrs) {
170        this(context, attrs, 0);
171    }
172
173    public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
174        this(context, attrs, defStyleAttr, 0);
175    }
176
177    public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
178        super(context, attrs, defStyleAttr, defStyleRes);
179        RecentsConfiguration config = Recents.getConfiguration();
180        Resources res = context.getResources();
181        mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize(
182                R.dimen.recents_task_view_shadow_rounded_corners_radius));
183        if (config.fakeShadows) {
184            setBackground(new FakeShadowDrawable(res, config));
185        }
186        setOutlineProvider(mViewBounds);
187        setOnLongClickListener(this);
188    }
189
190    /** Set callback */
191    void setCallbacks(TaskViewCallbacks cb) {
192        mCb = cb;
193    }
194
195    /** Resets this TaskView for reuse. */
196    void onResume(boolean isResumingFromVisible) {
197        resetNoUserInteractionState();
198        readSystemFlags();
199        if (!isResumingFromVisible) {
200            resetViewProperties();
201            setClipViewInStack(false);
202        }
203        setCallbacks(null);
204    }
205
206    /** Gets the task */
207    public Task getTask() {
208        return mTask;
209    }
210
211    /** Returns the view bounds. */
212    AnimateableViewBounds getViewBounds() {
213        return mViewBounds;
214    }
215
216    @Override
217    protected void onAttachedToWindow() {
218        super.onAttachedToWindow();
219        readSystemFlags();
220    }
221
222    @Override
223    protected void onFinishInflate() {
224        // Bind the views
225        mContent = findViewById(R.id.task_view_content);
226        mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar);
227        mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
228        mActionButtonView = findViewById(R.id.lock_to_app_fab);
229        mActionButtonView.setOutlineProvider(new ViewOutlineProvider() {
230            @Override
231            public void getOutline(View view, Outline outline) {
232                // Set the outline to match the FAB background
233                outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight());
234                outline.setAlpha(0.35f);
235            }
236        });
237        mActionButtonView.setOnClickListener(this);
238        mActionButtonTranslationZ = mActionButtonView.getTranslationZ();
239    }
240
241    @Override
242    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
243        super.onSizeChanged(w, h, oldw, oldh);
244        if (w > 0 && h > 0) {
245            mHeaderView.onTaskViewSizeChanged(w, h);
246            mThumbnailView.onTaskViewSizeChanged(w, h);
247        }
248    }
249
250    @Override
251    public boolean hasOverlappingRendering() {
252        return false;
253    }
254
255    @Override
256    public boolean onInterceptTouchEvent(MotionEvent ev) {
257        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
258            mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY()));
259        }
260        return super.onInterceptTouchEvent(ev);
261    }
262
263
264    @Override
265    protected void measureContents(int width, int height) {
266        int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
267        int heightWithoutPadding = height - mPaddingTop - mPaddingBottom;
268
269        // Measure the content
270        mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
271                MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
272
273        // Optimization: Prevent overdraw of the thumbnail under the header view
274        mThumbnailView.updateClipToTaskBar(mHeaderView);
275
276        setMeasuredDimension(width, height);
277    }
278
279    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform,
280            AnimationProps toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) {
281        RecentsConfiguration config = Recents.getConfiguration();
282        cancelTransformAnimation();
283
284        // Compose the animations for the transform
285        mTmpAnimators.clear();
286        toTransform.applyToTaskView(this, mTmpAnimators, toAnimation, !config.fakeShadows);
287        if (toAnimation.isImmediate()) {
288            if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) {
289                setDimAlpha(toTransform.dimAlpha);
290            }
291            if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) {
292                mViewBounds.setAlpha(toTransform.viewOutlineAlpha);
293            }
294            // Manually call back to the animator listener and update callback
295            if (toAnimation.getListener() != null) {
296                toAnimation.getListener().onAnimationEnd(null);
297            }
298            if (updateCallback != null) {
299                updateCallback.onAnimationUpdate(null);
300            }
301        } else {
302            // Both the progress and the update are a function of the bounds movement of the task
303            if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) {
304                ObjectAnimator anim = ObjectAnimator.ofFloat(this, DIM_ALPHA, getDimAlpha(),
305                        toTransform.dimAlpha);
306                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, anim));
307            }
308            if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) {
309                ObjectAnimator anim = ObjectAnimator.ofFloat(this, VIEW_OUTLINE_ALPHA,
310                        mViewBounds.getAlpha(), toTransform.viewOutlineAlpha);
311                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, anim));
312            }
313            if (updateCallback != null) {
314                ValueAnimator updateCallbackAnim = ValueAnimator.ofInt(0, 1);
315                updateCallbackAnim.addUpdateListener(updateCallback);
316                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, updateCallbackAnim));
317            }
318
319            // Create the animator
320            mTransformAnimation = toAnimation.createAnimator(mTmpAnimators);
321            mTransformAnimation.start();
322            mTargetAnimationTransform.copyFrom(toTransform);
323        }
324    }
325
326    /** Resets this view's properties */
327    void resetViewProperties() {
328        cancelTransformAnimation();
329        setDimAlpha(0);
330        setVisibility(View.VISIBLE);
331        getViewBounds().reset();
332        getHeaderView().reset();
333        TaskViewTransform.reset(this);
334
335        mActionButtonView.setScaleX(1f);
336        mActionButtonView.setScaleY(1f);
337        mActionButtonView.setAlpha(0f);
338        mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
339    }
340
341    /**
342     * @return whether we are animating towards {@param transform}
343     */
344    boolean isAnimatingTo(TaskViewTransform transform) {
345        return mTransformAnimation != null && mTransformAnimation.isStarted()
346                && mTargetAnimationTransform.isSame(transform);
347    }
348
349    /**
350     * Cancels any current transform animations.
351     */
352    public void cancelTransformAnimation() {
353        Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
354    }
355
356    /** Enables/disables handling touch on this task view. */
357    void setTouchEnabled(boolean enabled) {
358        setOnClickListener(enabled ? this : null);
359    }
360
361    /** Animates this task view if the user does not interact with the stack after a certain time. */
362    void startNoUserInteractionAnimation() {
363        mHeaderView.startNoUserInteractionAnimation();
364    }
365
366    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
367    void setNoUserInteractionState() {
368        mHeaderView.setNoUserInteractionState();
369    }
370
371    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
372    void resetNoUserInteractionState() {
373        mHeaderView.resetNoUserInteractionState();
374    }
375
376    /** Dismisses this task. */
377    void dismissTask() {
378        // Animate out the view and call the callback
379        final TaskView tv = this;
380        DismissTaskViewEvent dismissEvent = new DismissTaskViewEvent(tv, mTask);
381        dismissEvent.addPostAnimationCallback(new Runnable() {
382            @Override
383            public void run() {
384                EventBus.getDefault().send(new TaskViewDismissedEvent(mTask, tv));
385            }
386        });
387        EventBus.getDefault().send(dismissEvent);
388    }
389
390    /**
391     * Returns whether this view should be clipped, or any views below should clip against this
392     * view.
393     */
394    boolean shouldClipViewInStack() {
395        // Never clip for freeform tasks or if invisible
396        if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) {
397            return false;
398        }
399        return mClipViewInStack;
400    }
401
402    /** Sets whether this view should be clipped, or clipped against. */
403    void setClipViewInStack(boolean clip) {
404        if (clip != mClipViewInStack) {
405            mClipViewInStack = clip;
406            if (mCb != null) {
407                mCb.onTaskViewClipStateChanged(this);
408            }
409        }
410    }
411
412    public TaskViewHeader getHeaderView() {
413        return mHeaderView;
414    }
415
416    /**
417     * Sets the current dim.
418     */
419    public void setDimAlpha(float dimAlpha) {
420        mDimAlpha = dimAlpha;
421        mThumbnailView.setDimAlpha(dimAlpha);
422        mHeaderView.setDimAlpha(dimAlpha);
423    }
424
425    /**
426     * Sets the current dim without updating the header's dim.
427     */
428    public void setDimAlphaWithoutHeader(float dimAlpha) {
429        mDimAlpha = dimAlpha;
430        mThumbnailView.setDimAlpha(dimAlpha);
431    }
432
433    /**
434     * Returns the current dim.
435     */
436    public float getDimAlpha() {
437        return mDimAlpha;
438    }
439
440    /**
441     * Explicitly sets the focused state of this task.
442     */
443    public void setFocusedState(boolean isFocused, boolean requestViewFocus) {
444        SystemServicesProxy ssp = Recents.getSystemServices();
445        if (isFocused) {
446            if (requestViewFocus && !isFocused()) {
447                requestFocus();
448            }
449            if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
450                requestAccessibilityFocus();
451            }
452        } else {
453            if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
454                clearAccessibilityFocus();
455            }
456        }
457    }
458
459    /**
460     * Shows the action button.
461     * @param fadeIn whether or not to animate the action button in.
462     * @param fadeInDuration the duration of the action button animation, only used if
463     *                       {@param fadeIn} is true.
464     */
465    public void showActionButton(boolean fadeIn, int fadeInDuration) {
466        mActionButtonView.setVisibility(View.VISIBLE);
467
468        if (fadeIn && mActionButtonView.getAlpha() < 1f) {
469            mActionButtonView.animate()
470                    .alpha(1f)
471                    .scaleX(1f)
472                    .scaleY(1f)
473                    .setDuration(fadeInDuration)
474                    .setInterpolator(Interpolators.ALPHA_IN)
475                    .start();
476        } else {
477            mActionButtonView.setScaleX(1f);
478            mActionButtonView.setScaleY(1f);
479            mActionButtonView.setAlpha(1f);
480            mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
481        }
482    }
483
484    /**
485     * Immediately hides the action button.
486     *
487     * @param fadeOut whether or not to animate the action button out.
488     */
489    public void hideActionButton(boolean fadeOut, int fadeOutDuration, boolean scaleDown,
490            final Animator.AnimatorListener animListener) {
491        if (fadeOut && mActionButtonView.getAlpha() > 0f) {
492            if (scaleDown) {
493                float toScale = 0.9f;
494                mActionButtonView.animate()
495                        .scaleX(toScale)
496                        .scaleY(toScale);
497            }
498            mActionButtonView.animate()
499                    .alpha(0f)
500                    .setDuration(fadeOutDuration)
501                    .setInterpolator(Interpolators.ALPHA_OUT)
502                    .withEndAction(new Runnable() {
503                        @Override
504                        public void run() {
505                            if (animListener != null) {
506                                animListener.onAnimationEnd(null);
507                            }
508                            mActionButtonView.setVisibility(View.INVISIBLE);
509                        }
510                    })
511                    .start();
512        } else {
513            mActionButtonView.setAlpha(0f);
514            mActionButtonView.setVisibility(View.INVISIBLE);
515            if (animListener != null) {
516                animListener.onAnimationEnd(null);
517            }
518        }
519    }
520
521    /**** TaskStackAnimationHelper.Callbacks Implementation ****/
522
523    @Override
524    public void onPrepareLaunchTargetForEnterAnimation() {
525        // These values will be animated in when onStartLaunchTargetEnterAnimation() is called
526        setDimAlphaWithoutHeader(0);
527        mActionButtonView.setAlpha(0f);
528    }
529
530    @Override
531    public void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration,
532            boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger) {
533        // Dim the view after the app window transitions down into recents
534        postAnimationTrigger.increment();
535        AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT);
536        Animator anim = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this,
537                DIM_ALPHA_WITHOUT_HEADER, getDimAlpha(), transform.dimAlpha));
538        anim.addListener(postAnimationTrigger.decrementOnAnimationEnd());
539        anim.start();
540
541        if (screenPinningEnabled) {
542            showActionButton(true /* fadeIn */, duration /* fadeInDuration */);
543        }
544    }
545
546    @Override
547    public void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested,
548            ReferenceCountedTrigger postAnimationTrigger) {
549        // Un-dim the view before/while launching the target
550        AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT);
551        Animator anim = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this,
552                DIM_ALPHA, getDimAlpha(), 0));
553        anim.start();
554
555        postAnimationTrigger.increment();
556        hideActionButton(true /* fadeOut */, duration,
557                !screenPinningRequested /* scaleDown */,
558                postAnimationTrigger.decrementOnAnimationEnd());
559    }
560
561    @Override
562    public void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled) {
563        if (screenPinningEnabled) {
564            showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
565        }
566    }
567
568    /**** TaskCallbacks Implementation ****/
569
570    public void onTaskBound(Task t) {
571        SystemServicesProxy ssp = Recents.getSystemServices();
572        mTask = t;
573        mTask.addCallback(this);
574        mIsDisabledInSafeMode = !mTask.isSystemApp && ssp.isInSafeMode();
575    }
576
577    @Override
578    public void onTaskDataLoaded(Task task) {
579        // Bind each of the views to the new task data
580        mThumbnailView.rebindToTask(mTask, mIsDisabledInSafeMode);
581        mHeaderView.rebindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
582        mTaskDataLoaded = true;
583    }
584
585    @Override
586    public void onTaskDataUnloaded() {
587        // Unbind each of the views from the task data and remove the task callback
588        mTask.removeCallback(this);
589        mThumbnailView.unbindFromTask();
590        mHeaderView.unbindFromTask(mTouchExplorationEnabled);
591        mTaskDataLoaded = false;
592    }
593
594    @Override
595    public void onTaskStackIdChanged() {
596        mHeaderView.rebindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
597    }
598
599    /**** View.OnClickListener Implementation ****/
600
601    @Override
602     public void onClick(final View v) {
603        if (mIsDisabledInSafeMode) {
604            Context context = getContext();
605            String msg = context.getString(R.string.recents_launch_disabled_message, mTask.title);
606            if (mDisabledAppToast != null) {
607                mDisabledAppToast.cancel();
608            }
609            mDisabledAppToast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
610            mDisabledAppToast.show();
611            return;
612        }
613
614        boolean screenPinningRequested = false;
615        if (v == mActionButtonView) {
616            // Reset the translation of the action button before we animate it out
617            mActionButtonView.setTranslationZ(0f);
618            screenPinningRequested = true;
619        }
620        EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, INVALID_STACK_ID,
621                screenPinningRequested));
622
623        MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT,
624                mTask.key.getComponent().toString());
625    }
626
627    /**** View.OnLongClickListener Implementation ****/
628
629    @Override
630    public boolean onLongClick(View v) {
631        SystemServicesProxy ssp = Recents.getSystemServices();
632        // Since we are clipping the view to the bounds, manually do the hit test
633        Rect clipBounds = new Rect(mViewBounds.mClipBounds);
634        clipBounds.scale(getScaleX());
635        boolean inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y);
636        if (v == this && inBounds && !ssp.hasDockedTask()) {
637            // Start listening for drag events
638            setClipViewInStack(false);
639
640            mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2;
641            mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2;
642
643            EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
644            EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos));
645            return true;
646        }
647        return false;
648    }
649
650    /**** Events ****/
651
652    public final void onBusEvent(DragEndEvent event) {
653        if (!(event.dropTarget instanceof TaskStack.DockState)) {
654            event.addPostAnimationCallback(new Runnable() {
655                @Override
656                public void run() {
657                    // Animate the drag view back from where it is, to the view location, then after
658                    // it returns, update the clip state
659                    setClipViewInStack(true);
660                }
661            });
662        }
663        EventBus.getDefault().unregister(this);
664    }
665
666    /**
667     * Reads current system flags related to accessibility and screen pinning.
668     */
669    private void readSystemFlags() {
670        SystemServicesProxy ssp = Recents.getSystemServices();
671        mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
672    }
673}
674