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.animation.Animator;
19import android.content.Context;
20import android.content.pm.PackageManager;
21import android.content.res.Configuration;
22import android.content.res.Resources;
23import android.graphics.Bitmap;
24import android.graphics.Outline;
25import android.graphics.Point;
26import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
28import android.util.AttributeSet;
29import android.util.Log;
30import android.util.TypedValue;
31import android.view.Display;
32import android.view.KeyEvent;
33import android.view.View;
34import android.view.ViewOutlineProvider;
35import android.view.WindowManager;
36import android.widget.ImageView;
37import android.widget.LinearLayout;
38import android.widget.TextView;
39
40import com.android.systemui.R;
41import com.android.systemui.recents.Recents;
42import com.android.systemui.recents.misc.SystemServicesProxy;
43import com.android.systemui.recents.model.Task;
44import com.android.systemui.recents.tv.RecentsTvActivity;
45import com.android.systemui.recents.tv.animations.DismissAnimationsHolder;
46import com.android.systemui.recents.tv.animations.RecentsRowFocusAnimationHolder;
47import com.android.systemui.recents.tv.animations.ViewFocusAnimator;
48
49public 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
66    public TaskCardView(Context context) {
67        this(context, null);
68    }
69
70    public TaskCardView(Context context, AttributeSet attrs) {
71        this(context, attrs, 0);
72    }
73
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
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
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
114    public Task getTask() {
115        return mTask;
116    }
117
118    @Override
119    public void getFocusedRect(Rect r) {
120        mThumbnailView.getFocusedRect(r);
121    }
122
123    public Rect getFocusedThumbnailRect() {
124        Rect r = new Rect();
125        mThumbnailView.getGlobalVisibleRect(r);
126        return r;
127    }
128
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
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
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
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
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
255    public boolean isInDismissState() {
256        return mDismissState;
257    }
258
259    public void startDismissTaskAnimation(Animator.AnimatorListener listener) {
260        mDismissState = false;
261        mDismissAnimationsHolder.startDismissAnimation(listener);
262    }
263
264    public ViewFocusAnimator getViewFocusAnimator() {
265        return mViewFocusAnimator;
266    }
267
268    public RecentsRowFocusAnimationHolder getRecentsRowFocusAnimationHolder() {
269        return mRecentsRowFocusAnimationHolder;
270    }
271
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
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
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
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
332    public View getThumbnailView() {
333        return mThumbnailView;
334    }
335
336    public View getInfoFieldView() {
337        return mInfoFieldView;
338    }
339
340    public View getDismissIconView() {
341        return mDismissIconView;
342    }
343
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