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  */
16 package com.android.systemui.recents.tv.views;
17 
18 import android.animation.Animator;
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.content.res.Configuration;
22 import android.content.res.Resources;
23 import android.graphics.Bitmap;
24 import android.graphics.Outline;
25 import android.graphics.Point;
26 import android.graphics.Rect;
27 import android.graphics.drawable.Drawable;
28 import android.util.AttributeSet;
29 import android.util.Log;
30 import android.util.TypedValue;
31 import android.view.Display;
32 import android.view.KeyEvent;
33 import android.view.View;
34 import android.view.ViewOutlineProvider;
35 import android.view.WindowManager;
36 import android.widget.ImageView;
37 import android.widget.LinearLayout;
38 import android.widget.TextView;
39 
40 import com.android.systemui.R;
41 import com.android.systemui.recents.Recents;
42 import com.android.systemui.recents.misc.SystemServicesProxy;
43 import com.android.systemui.recents.model.Task;
44 import com.android.systemui.recents.tv.RecentsTvActivity;
45 import com.android.systemui.recents.tv.animations.DismissAnimationsHolder;
46 import com.android.systemui.recents.tv.animations.RecentsRowFocusAnimationHolder;
47 import com.android.systemui.recents.tv.animations.ViewFocusAnimator;
48 
49 public class TaskCardView extends LinearLayout {
50 
51     private static final String TAG = "TaskCardView";
52     private View mThumbnailView;
53     private View mDismissIconView;
54     private View mInfoFieldView;
55     private TextView mTitleTextView;
56     private ImageView mBadgeView;
57     private Task mTask;
58     private boolean mDismissState;
59     private boolean mTouchExplorationEnabled;
60     private int mCornerRadius;
61 
62     private ViewFocusAnimator mViewFocusAnimator;
63     private DismissAnimationsHolder mDismissAnimationsHolder;
64     private RecentsRowFocusAnimationHolder mRecentsRowFocusAnimationHolder;
65 
TaskCardView(Context context)66     public TaskCardView(Context context) {
67         this(context, null);
68     }
69 
TaskCardView(Context context, AttributeSet attrs)70     public TaskCardView(Context context, AttributeSet attrs) {
71         this(context, attrs, 0);
72     }
73 
TaskCardView(Context context, AttributeSet attrs, int defStyleAttr)74     public TaskCardView(Context context, AttributeSet attrs, int defStyleAttr) {
75         super(context, attrs, defStyleAttr);
76         mDismissState = false;
77         Configuration config = getResources().getConfiguration();
78         setLayoutDirection(config.getLayoutDirection());
79     }
80 
81     @Override
onFinishInflate()82     protected void onFinishInflate() {
83         super.onFinishInflate();
84         mThumbnailView = findViewById(R.id.card_view_thumbnail);
85         mInfoFieldView = findViewById(R.id.card_info_field);
86         mTitleTextView = (TextView) findViewById(R.id.card_title_text);
87         mBadgeView = (ImageView) findViewById(R.id.card_extra_badge);
88         mDismissIconView = findViewById(R.id.dismiss_icon);
89         mDismissAnimationsHolder = new DismissAnimationsHolder(this);
90         mCornerRadius = getResources().getDimensionPixelSize(
91                 R.dimen.recents_task_view_rounded_corners_radius);
92         mRecentsRowFocusAnimationHolder = new RecentsRowFocusAnimationHolder(this, mInfoFieldView);
93         SystemServicesProxy ssp = Recents.getSystemServices();
94         mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
95         if (!mTouchExplorationEnabled) {
96             mDismissIconView.setVisibility(VISIBLE);
97         } else {
98             mDismissIconView.setVisibility(GONE);
99         }
100         mViewFocusAnimator = new ViewFocusAnimator(this);
101     }
102 
init(Task task)103     public void init(Task task) {
104         mTask = task;
105         mTitleTextView.setText(task.title);
106         mBadgeView.setImageDrawable(task.icon);
107         setThumbnailView();
108         setContentDescription(task.titleDescription);
109         mDismissState = false;
110         mDismissAnimationsHolder.reset();
111         mRecentsRowFocusAnimationHolder.reset();
112     }
113 
getTask()114     public Task getTask() {
115         return mTask;
116     }
117 
118     @Override
getFocusedRect(Rect r)119     public void getFocusedRect(Rect r) {
120         mThumbnailView.getFocusedRect(r);
121     }
122 
getFocusedThumbnailRect()123     public Rect getFocusedThumbnailRect() {
124         Rect r = new Rect();
125         mThumbnailView.getGlobalVisibleRect(r);
126         return r;
127     }
128 
getStartingCardThumbnailRect( Context context, boolean hasFocus, int numberOfTasks)129     public static Rect getStartingCardThumbnailRect(
130             Context context, boolean hasFocus, int numberOfTasks) {
131         if(numberOfTasks > 1) {
132             return getStartingCardThumbnailRectForStartPosition(context, hasFocus);
133         } else {
134             return getStartingCardThumbnailRectForFocusedPosition(context, hasFocus);
135         }
136     }
137 
getStartingCardThumbnailRectForStartPosition( Context context, boolean hasFocus)138     private static Rect getStartingCardThumbnailRectForStartPosition(
139             Context context, boolean hasFocus) {
140         Resources res = context.getResources();
141 
142         int width = res.getDimensionPixelOffset(R.dimen.recents_tv_card_width);
143         int totalSpacing = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_card_spacing) * 2;
144         if (hasFocus) {
145             totalSpacing += res.getDimensionPixelOffset(R.dimen.recents_tv_gird_focused_card_delta);
146         }
147         int height = res.getDimensionPixelOffset(R.dimen.recents_tv_screenshot_height);
148         int topMargin = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_row_top_margin);
149         int headerHeight = res.getDimensionPixelOffset(R.dimen.recents_tv_card_extra_badge_size) +
150                 res.getDimensionPixelOffset(R.dimen.recents_tv_icon_padding_bottom);
151 
152         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
153         Display display = wm.getDefaultDisplay();
154         Point size = new Point();
155         display.getSize(size);
156         int screenWidth = size.x;
157 
158         return new Rect(screenWidth / 2 + width / 2 + totalSpacing,
159                 topMargin + headerHeight,
160                 screenWidth / 2 + width / 2 + totalSpacing + width,
161                 topMargin + headerHeight + height);
162     }
163 
getStartingCardThumbnailRectForFocusedPosition( Context context, boolean hasFocus)164     private static Rect getStartingCardThumbnailRectForFocusedPosition(
165             Context context, boolean hasFocus) {
166         Resources res = context.getResources();
167 
168         TypedValue out = new TypedValue();
169         res.getValue(R.integer.selected_scale, out, true);
170         float scale = hasFocus ? out.getFloat() : 1;
171 
172         int width = res.getDimensionPixelOffset(R.dimen.recents_tv_card_width);
173         int widthDelta = (int) (width * scale - width);
174         int height = res.getDimensionPixelOffset(R.dimen.recents_tv_screenshot_height);
175         int heightDelta = (int) (height * scale - height);
176         int topMargin = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_row_top_margin);
177 
178         int headerHeight = res.getDimensionPixelOffset(R.dimen.recents_tv_card_extra_badge_size) +
179                 res.getDimensionPixelOffset(R.dimen.recents_tv_icon_padding_bottom);
180         int headerHeightDelta = (int) (headerHeight * scale - headerHeight);
181 
182         int dismissAreaHeight =
183                 res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_top_margin) +
184                 res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_bottom_margin) +
185                 res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_size) +
186                 res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_text_size);
187 
188         int dismissAreaHeightDelta = (int) (dismissAreaHeight * scale - dismissAreaHeight);
189 
190         int totalHeightDelta = heightDelta + headerHeightDelta + dismissAreaHeightDelta;
191 
192         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
193         Display display = wm.getDefaultDisplay();
194         Point size = new Point();
195         display.getSize(size);
196         int screenWidth = size.x;
197 
198         return new Rect(screenWidth / 2 - width / 2 - widthDelta / 2,
199                 topMargin - totalHeightDelta / 2 + (int) (headerHeight * scale),
200                 screenWidth / 2 + width / 2 + widthDelta / 2,
201                 topMargin - totalHeightDelta / 2 + (int) (headerHeight * scale) +
202                         (int) (height * scale));
203     }
204 
205     @Override
dispatchKeyEvent(KeyEvent event)206     public boolean dispatchKeyEvent(KeyEvent event) {
207         // Override dispatchKeyEvent() instead of onKeyDown() to prevent warning from ViewRootImpl.
208         switch (event.getKeyCode()) {
209             case KeyEvent.KEYCODE_DPAD_DOWN : {
210                 if (!isInDismissState() && event.getAction() == KeyEvent.ACTION_DOWN) {
211                     setDismissState(true);
212                     return true;
213                 }
214                 break;
215             }
216             case KeyEvent.KEYCODE_DPAD_UP : {
217                 if (event.getAction() == KeyEvent.ACTION_DOWN) {
218                     if (isInDismissState()) {
219                         setDismissState(false);
220                     } else {
221                         ((RecentsTvActivity) getContext()).requestPipControlsFocus();
222                     }
223                 }
224                 return true;
225             }
226 
227             // Eat right and left key presses when we are in dismiss state
228             case KeyEvent.KEYCODE_DPAD_LEFT :
229             case KeyEvent.KEYCODE_DPAD_RIGHT : {
230                 if (isInDismissState()) {
231                     return true;
232                 }
233                 break;
234             }
235         }
236         return super.dispatchKeyEvent(event);
237     }
238 
setDismissState(boolean dismissState)239     private void setDismissState(boolean dismissState) {
240         if (mDismissState != dismissState) {
241             mDismissState = dismissState;
242             // Check for touch exploration to ensure dismiss icon/text do not
243             // get animated. This should be removed based on decision from
244             // b/29208918
245             if (!mTouchExplorationEnabled) {
246                 if (dismissState) {
247                     mDismissAnimationsHolder.startEnterAnimation();
248                 } else {
249                     mDismissAnimationsHolder.startExitAnimation();
250                 }
251             }
252         }
253     }
254 
isInDismissState()255     public boolean isInDismissState() {
256         return mDismissState;
257     }
258 
startDismissTaskAnimation(Animator.AnimatorListener listener)259     public void startDismissTaskAnimation(Animator.AnimatorListener listener) {
260         mDismissState = false;
261         mDismissAnimationsHolder.startDismissAnimation(listener);
262     }
263 
getViewFocusAnimator()264     public ViewFocusAnimator getViewFocusAnimator() {
265         return mViewFocusAnimator;
266     }
267 
getRecentsRowFocusAnimationHolder()268     public RecentsRowFocusAnimationHolder getRecentsRowFocusAnimationHolder() {
269         return mRecentsRowFocusAnimationHolder;
270     }
271 
setThumbnailView()272     private void setThumbnailView() {
273         ImageView screenshotView = (ImageView) findViewById(R.id.card_view_banner_icon);
274         PackageManager pm = getContext().getPackageManager();
275         if (mTask.thumbnail != null) {
276             setAsScreenShotView(mTask.thumbnail, screenshotView);
277         } else {
278             try {
279                 Drawable banner = null;
280                 if (mTask.key != null) {
281                     banner = pm.getActivityBanner(mTask.key.baseIntent);
282                 }
283                 if (banner != null) {
284                     setAsBannerView(banner, screenshotView);
285                 } else {
286                     setAsIconView(mTask.icon, screenshotView);
287                 }
288             } catch (PackageManager.NameNotFoundException e) {
289                 Log.e(TAG, "Package not found : " + e);
290                 setAsIconView(mTask.icon, screenshotView);
291             }
292         }
293     }
294 
setAsScreenShotView(Bitmap screenshot, ImageView screenshotView)295     private void setAsScreenShotView(Bitmap screenshot, ImageView screenshotView) {
296         LayoutParams lp = (LayoutParams) screenshotView.getLayoutParams();
297         lp.width = LayoutParams.MATCH_PARENT;
298         lp.height = LayoutParams.MATCH_PARENT;
299 
300         screenshotView.setLayoutParams(lp);
301         screenshotView.setClipToOutline(true);
302         screenshotView.setOutlineProvider(new ViewOutlineProvider() {
303             @Override
304             public void getOutline(View view, Outline outline) {
305                 outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
306             }
307         });
308         screenshotView.setImageBitmap(screenshot);
309     }
310 
setAsBannerView(Drawable banner, ImageView bannerView)311     private void setAsBannerView(Drawable banner, ImageView bannerView) {
312         LayoutParams lp = (LayoutParams) bannerView.getLayoutParams();
313         lp.width = getResources()
314                 .getDimensionPixelSize(R.dimen.recents_tv_banner_width);
315         lp.height = getResources()
316                 .getDimensionPixelSize(R.dimen.recents_tv_banner_height);
317         bannerView.setLayoutParams(lp);
318         bannerView.setImageDrawable(banner);
319     }
320 
setAsIconView(Drawable icon, ImageView iconView)321     private void setAsIconView(Drawable icon, ImageView iconView) {
322         LayoutParams lp = (LayoutParams) iconView.getLayoutParams();
323         lp.width = getResources()
324                 .getDimensionPixelSize(R.dimen.recents_tv_fallback_icon_width);
325         lp.height = getResources()
326                 .getDimensionPixelSize(R.dimen.recents_tv_fallback_icon_height);
327 
328         iconView.setLayoutParams(lp);
329         iconView.setImageDrawable(icon);
330     }
331 
getThumbnailView()332     public View getThumbnailView() {
333         return mThumbnailView;
334     }
335 
getInfoFieldView()336     public View getInfoFieldView() {
337         return mInfoFieldView;
338     }
339 
getDismissIconView()340     public View getDismissIconView() {
341         return mDismissIconView;
342     }
343 
getNumberOfVisibleTasks(Context context)344     public static int getNumberOfVisibleTasks(Context context) {
345         Resources res = context.getResources();
346         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
347         Display display = wm.getDefaultDisplay();
348         Point size = new Point();
349         display.getSize(size);
350         int screenWidth = size.x;
351         int cardWidth = res.getDimensionPixelSize(R.dimen.recents_tv_card_width);
352         int spacing = res.getDimensionPixelSize(R.dimen.recents_tv_gird_card_spacing);
353         return (int) (1.0 + Math.ceil(screenWidth / (cardWidth + spacing * 2.0)));
354     }
355 }
356