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