1/*
2 * Copyright (C) 2016 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 */
16package com.android.systemui.recents.tv.views;
17
18import android.content.Context;
19import android.graphics.Rect;
20import android.os.Handler;
21import android.support.v7.widget.RecyclerView;
22import android.util.AttributeSet;
23import android.util.Log;
24import android.view.LayoutInflater;
25import android.view.View;
26import android.view.WindowInsets;
27import android.widget.FrameLayout;
28
29import com.android.systemui.R;
30import com.android.systemui.recents.Recents;
31import com.android.systemui.recents.RecentsActivity;
32import com.android.systemui.recents.RecentsActivityLaunchState;
33import com.android.systemui.recents.RecentsConfiguration;
34import com.android.systemui.recents.events.EventBus;
35import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
36import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
37import com.android.systemui.recents.events.activity.LaunchTvTaskEvent;
38import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
39import com.android.systemui.recents.misc.SystemServicesProxy;
40import com.android.systemui.recents.model.Task;
41import com.android.systemui.recents.model.TaskStack;
42import com.android.systemui.recents.tv.animations.RecentsRowFocusAnimationHolder;
43import android.support.v7.widget.RecyclerView.OnScrollListener;
44import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
45
46/**
47 * Top level layout of recents for TV. This will show the TaskStacks using a HorizontalGridView.
48 */
49public class RecentsTvView extends FrameLayout {
50
51    private static final String TAG = "RecentsTvView";
52    private static final boolean DEBUG = false;
53
54    private TaskStack mStack;
55    private TaskStackHorizontalGridView mTaskStackHorizontalView;
56    private View mEmptyView;
57    private View mDismissPlaceholder;
58    private RecentsRowFocusAnimationHolder mEmptyViewFocusAnimationHolder;
59    private boolean mAwaitingFirstLayout = true;
60    private Rect mSystemInsets = new Rect();
61    private RecentsTvTransitionHelper mTransitionHelper;
62    private final Handler mHandler = new Handler();
63    private OnScrollListener mScrollListener;
64    public RecentsTvView(Context context) {
65        this(context, null);
66    }
67
68    public RecentsTvView(Context context, AttributeSet attrs) {
69        this(context, attrs, 0);
70    }
71
72    public RecentsTvView(Context context, AttributeSet attrs, int defStyleAttr) {
73        this(context, attrs, defStyleAttr, 0);
74    }
75
76    public RecentsTvView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
77        super(context, attrs, defStyleAttr, defStyleRes);
78
79        setWillNotDraw(false);
80
81        LayoutInflater inflater = LayoutInflater.from(context);
82        mEmptyView = inflater.inflate(R.layout.recents_tv_empty, this, false);
83        addView(mEmptyView);
84
85        mTransitionHelper = new RecentsTvTransitionHelper(mContext, mHandler);
86    }
87
88    @Override
89    protected void onFinishInflate() {
90        super.onFinishInflate();
91        mDismissPlaceholder = findViewById(R.id.dismiss_placeholder);
92        mTaskStackHorizontalView = (TaskStackHorizontalGridView) findViewById(R.id.task_list);
93    }
94
95    /**
96     * Initialize the view.
97     */
98    public void init(TaskStack stack) {
99        RecentsConfiguration config = Recents.getConfiguration();
100        RecentsActivityLaunchState launchState = config.getLaunchState();
101        mStack = stack;
102
103        mTaskStackHorizontalView.init(stack);
104
105        if (stack.getStackTaskCount() > 0) {
106            hideEmptyView();
107        } else {
108            showEmptyView();
109        }
110
111        // Layout with the new stack
112        requestLayout();
113    }
114
115    public boolean launchFocusedTask() {
116        if (mTaskStackHorizontalView != null) {
117            Task task = mTaskStackHorizontalView.getFocusedTask();
118            if (task != null) {
119                launchTaskFomRecents(task, true);
120                return true;
121            }
122        }
123        return false;
124    }
125
126    /** Launches the task that recents was launched from if possible */
127    public boolean launchPreviousTask(boolean animate) {
128        if (mTaskStackHorizontalView != null) {
129            TaskStack stack = mTaskStackHorizontalView.getStack();
130            Task task = stack.getLaunchTarget();
131            if (task != null) {
132                launchTaskFomRecents(task, animate);
133                return true;
134            }
135        }
136        return false;
137    }
138
139    /**
140     * Launch the given task from recents with animation. If the task is not focused, this will
141     * attempt to scroll to focus the task before launching.
142     * @param task
143     */
144    private void launchTaskFomRecents(final Task task, boolean animate) {
145        if (!animate) {
146            SystemServicesProxy ssp = Recents.getSystemServices();
147            ssp.startActivityFromRecents(getContext(), task.key, task.title, null);
148            return;
149        }
150        mTaskStackHorizontalView.requestFocus();
151        Task focusedTask = mTaskStackHorizontalView.getFocusedTask();
152        if (focusedTask != null && task != focusedTask) {
153            if (mScrollListener != null) {
154                mTaskStackHorizontalView.removeOnScrollListener(mScrollListener);
155            }
156            mScrollListener = new OnScrollListener() {
157                @Override
158                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
159                    super.onScrollStateChanged(recyclerView, newState);
160                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
161                        TaskCardView cardView = mTaskStackHorizontalView.getChildViewForTask(task);
162                        if (cardView != null) {
163                            mTransitionHelper.launchTaskFromRecents(mStack, task,
164                                    mTaskStackHorizontalView, cardView, null, INVALID_STACK_ID);
165                        } else {
166                            // This should not happen normally. If this happens then the data in
167                            // the grid view was altered during the scroll. Log error and launch
168                            // task with no animation.
169                            Log.e(TAG, "Card view for task : " + task + ", returned null.");
170                            SystemServicesProxy ssp = Recents.getSystemServices();
171                            ssp.startActivityFromRecents(getContext(), task.key, task.title, null);
172                        }
173                        mTaskStackHorizontalView.removeOnScrollListener(mScrollListener);
174                    }
175                }
176            };
177            mTaskStackHorizontalView.addOnScrollListener(mScrollListener);
178            mTaskStackHorizontalView.setSelectedPositionSmooth(
179                    ((TaskStackHorizontalViewAdapter) mTaskStackHorizontalView.getAdapter())
180                            .getPositionOfTask(task));
181        } else {
182            mTransitionHelper.launchTaskFromRecents(mStack, task, mTaskStackHorizontalView,
183                    mTaskStackHorizontalView.getChildViewForTask(task), null,
184                    INVALID_STACK_ID);
185        }
186    }
187
188    /**
189     * Hides the task stack and shows the empty view.
190     */
191    public void showEmptyView() {
192        mEmptyView.setVisibility(View.VISIBLE);
193        mTaskStackHorizontalView.setVisibility(View.GONE);
194        if (Recents.getSystemServices().isTouchExplorationEnabled()) {
195            mDismissPlaceholder.setVisibility(View.GONE);
196        }
197    }
198
199    /**
200     * Shows the task stack and hides the empty view.
201     */
202    public void hideEmptyView() {
203        mEmptyView.setVisibility(View.GONE);
204        mTaskStackHorizontalView.setVisibility(View.VISIBLE);
205        if (Recents.getSystemServices().isTouchExplorationEnabled()) {
206            mDismissPlaceholder.setVisibility(View.VISIBLE);
207        }
208    }
209
210    /**
211     * Returns the last known system insets.
212     */
213    public Rect getSystemInsets() {
214        return mSystemInsets;
215    }
216
217    @Override
218    protected void onAttachedToWindow() {
219        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
220        super.onAttachedToWindow();
221    }
222
223    @Override
224    protected void onDetachedFromWindow() {
225        super.onDetachedFromWindow();
226        EventBus.getDefault().unregister(this);
227    }
228
229    @Override
230    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
231        mSystemInsets.set(insets.getSystemWindowInsets());
232        requestLayout();
233        return insets;
234    }
235
236    /**** EventBus Events ****/
237
238    public final void onBusEvent(LaunchTvTaskEvent event) {
239        mTransitionHelper.launchTaskFromRecents(mStack, event.task, mTaskStackHorizontalView,
240                event.taskView, event.targetTaskBounds, event.targetTaskStack);
241    }
242
243    public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
244        // If we are going home, cancel the previous task's window transition
245        EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
246    }
247
248    public final void onBusEvent(RecentsVisibilityChangedEvent event) {
249        if (!event.visible) {
250            // Reset the view state
251            mAwaitingFirstLayout = true;
252        }
253    }
254
255    public TaskStackHorizontalGridView setTaskStackViewAdapter(
256            TaskStackHorizontalViewAdapter taskStackViewAdapter) {
257        if (mTaskStackHorizontalView != null) {
258            mTaskStackHorizontalView.setAdapter(taskStackViewAdapter);
259            taskStackViewAdapter.setTaskStackHorizontalGridView(mTaskStackHorizontalView);
260        }
261        return mTaskStackHorizontalView;
262    }
263
264    public TaskStackHorizontalGridView getGridView() {
265        return mTaskStackHorizontalView;
266    }
267}
268