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