TaskViewHeader.java revision e7f138c7f0a190c86cec10fb32fa106aacae4093
1/* 2 * Copyright (C) 2014 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 17package com.android.systemui.recents.views; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.AnimatorSet; 22import android.animation.ArgbEvaluator; 23import android.animation.ObjectAnimator; 24import android.animation.ValueAnimator; 25import android.content.Context; 26import android.content.res.ColorStateList; 27import android.graphics.Canvas; 28import android.graphics.Color; 29import android.graphics.Outline; 30import android.graphics.Paint; 31import android.graphics.PorterDuff; 32import android.graphics.PorterDuffColorFilter; 33import android.graphics.PorterDuffXfermode; 34import android.graphics.Rect; 35import android.graphics.drawable.ColorDrawable; 36import android.graphics.drawable.Drawable; 37import android.graphics.drawable.GradientDrawable; 38import android.graphics.drawable.RippleDrawable; 39import android.util.AttributeSet; 40import android.view.View; 41import android.view.ViewOutlineProvider; 42import android.view.accessibility.AccessibilityManager; 43import android.view.animation.AnimationUtils; 44import android.view.animation.Interpolator; 45import android.widget.FrameLayout; 46import android.widget.ImageView; 47import android.widget.TextView; 48import com.android.internal.logging.MetricsLogger; 49import com.android.systemui.R; 50import com.android.systemui.recents.Constants; 51import com.android.systemui.recents.Recents; 52import com.android.systemui.recents.events.EventBus; 53import com.android.systemui.recents.events.ui.ResizeTaskEvent; 54import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; 55import com.android.systemui.recents.misc.SystemServicesProxy; 56import com.android.systemui.recents.misc.Utilities; 57import com.android.systemui.recents.model.Task; 58 59 60/* The task bar view */ 61public class TaskViewHeader extends FrameLayout 62 implements View.OnClickListener, View.OnLongClickListener { 63 64 Task mTask; 65 66 // Header views 67 ImageView mMoveTaskButton; 68 ImageView mDismissButton; 69 ImageView mApplicationIcon; 70 TextView mActivityDescription; 71 72 // Header drawables 73 boolean mCurrentPrimaryColorIsDark; 74 int mCurrentPrimaryColor; 75 int mBackgroundColor; 76 int mCornerRadius; 77 int mHighlightHeight; 78 Drawable mLightDismissDrawable; 79 Drawable mDarkDismissDrawable; 80 RippleDrawable mBackground; 81 GradientDrawable mBackgroundColorDrawable; 82 AnimatorSet mFocusAnimator; 83 String mDismissContentDescription; 84 85 // Static highlight that we draw at the top of each view 86 static Paint sHighlightPaint; 87 88 // Header dim, which is only used when task view hardware layers are not used 89 Paint mDimLayerPaint = new Paint(); 90 PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP); 91 92 Interpolator mFastOutSlowInInterpolator; 93 Interpolator mFastOutLinearInInterpolator; 94 95 boolean mLayersDisabled; 96 97 public TaskViewHeader(Context context) { 98 this(context, null); 99 } 100 101 public TaskViewHeader(Context context, AttributeSet attrs) { 102 this(context, attrs, 0); 103 } 104 105 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) { 106 this(context, attrs, defStyleAttr, 0); 107 } 108 109 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 110 super(context, attrs, defStyleAttr, defStyleRes); 111 setWillNotDraw(false); 112 setClipToOutline(true); 113 setOutlineProvider(new ViewOutlineProvider() { 114 @Override 115 public void getOutline(View view, Outline outline) { 116 outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight()); 117 } 118 }); 119 120 // Load the dismiss resources 121 mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light); 122 mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark); 123 mDismissContentDescription = 124 context.getString(R.string.accessibility_recents_item_will_be_dismissed); 125 mCornerRadius = getResources().getDimensionPixelSize( 126 R.dimen.recents_task_view_rounded_corners_radius); 127 mHighlightHeight = getResources().getDimensionPixelSize( 128 R.dimen.recents_task_view_highlight); 129 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, 130 com.android.internal.R.interpolator.fast_out_slow_in); 131 mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, 132 com.android.internal.R.interpolator.fast_out_linear_in); 133 134 // Configure the highlight paint 135 if (sHighlightPaint == null) { 136 sHighlightPaint = new Paint(); 137 sHighlightPaint.setStyle(Paint.Style.STROKE); 138 sHighlightPaint.setStrokeWidth(mHighlightHeight); 139 sHighlightPaint.setColor(context.getColor(R.color.recents_task_bar_highlight_color)); 140 sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); 141 sHighlightPaint.setAntiAlias(true); 142 } 143 } 144 145 @Override 146 protected void onFinishInflate() { 147 // Initialize the icon and description views 148 mApplicationIcon = (ImageView) findViewById(R.id.application_icon); 149 mApplicationIcon.setOnLongClickListener(this); 150 mActivityDescription = (TextView) findViewById(R.id.activity_description); 151 mDismissButton = (ImageView) findViewById(R.id.dismiss_task); 152 mDismissButton.setOnClickListener(this); 153 mMoveTaskButton = (ImageView) findViewById(R.id.move_task); 154 155 // Hide the backgrounds if they are ripple drawables 156 if (mApplicationIcon.getBackground() instanceof RippleDrawable) { 157 mApplicationIcon.setBackground(null); 158 } 159 160 mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable 161 .recents_task_view_header_bg_color); 162 // Copy the ripple drawable since we are going to be manipulating it 163 mBackground = (RippleDrawable) 164 getContext().getDrawable(R.drawable.recents_task_view_header_bg); 165 mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable(); 166 mBackground.setColor(ColorStateList.valueOf(0)); 167 mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColorDrawable); 168 setBackground(mBackground); 169 } 170 171 @Override 172 protected void onDraw(Canvas canvas) { 173 // Draw the highlight at the top edge (but put the bottom edge just out of view) 174 float offset = (float) Math.ceil(mHighlightHeight / 2f); 175 float radius = mCornerRadius; 176 int count = canvas.save(Canvas.CLIP_SAVE_FLAG); 177 canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight()); 178 canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset, 179 getMeasuredHeight() + radius, radius, radius, sHighlightPaint); 180 canvas.restoreToCount(count); 181 } 182 183 @Override 184 public boolean hasOverlappingRendering() { 185 return false; 186 } 187 188 /** 189 * Sets the dim alpha, only used when we are not using hardware layers. 190 * (see RecentsConfiguration.useHardwareLayers) 191 */ 192 void setDimAlpha(int alpha) { 193 mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0)); 194 mDimLayerPaint.setColorFilter(mDimColorFilter); 195 if (!mLayersDisabled) { 196 setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); 197 } 198 } 199 200 /** Returns the secondary color for a primary color. */ 201 int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) { 202 int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK; 203 return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f); 204 } 205 206 /** Binds the bar view to the task */ 207 public void rebindToTask(Task t) { 208 mTask = t; 209 210 // If an activity icon is defined, then we use that as the primary icon to show in the bar, 211 // otherwise, we fall back to the application icon 212 if (t.activityIcon != null) { 213 mApplicationIcon.setImageDrawable(t.activityIcon); 214 } else if (t.applicationIcon != null) { 215 mApplicationIcon.setImageDrawable(t.applicationIcon); 216 } 217 if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { 218 mActivityDescription.setText(t.activityLabel); 219 } 220 mActivityDescription.setContentDescription(t.contentDescription); 221 222 // Try and apply the system ui tint 223 int existingBgColor = (getBackground() instanceof ColorDrawable) ? 224 ((ColorDrawable) getBackground()).getColor() : 0; 225 if (existingBgColor != t.colorPrimary) { 226 mBackgroundColorDrawable.setColor(t.colorPrimary); 227 mBackgroundColor = t.colorPrimary; 228 } 229 230 int taskBarViewLightTextColor = getResources().getColor( 231 R.color.recents_task_bar_light_text_color); 232 int taskBarViewDarkTextColor = getResources().getColor( 233 R.color.recents_task_bar_dark_text_color); 234 mCurrentPrimaryColor = t.colorPrimary; 235 mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor; 236 mActivityDescription.setTextColor(t.useLightOnPrimaryColor ? 237 taskBarViewLightTextColor : taskBarViewDarkTextColor); 238 mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? 239 mLightDismissDrawable : mDarkDismissDrawable); 240 mDismissButton.setContentDescription(String.format(mDismissContentDescription, 241 t.contentDescription)); 242 updateResizeTaskBarIcon(t); 243 mMoveTaskButton.setVisibility(View.VISIBLE); 244 mMoveTaskButton.setOnClickListener(this); 245 246 // In accessibility, a single click on the focused app info button will show it 247 AccessibilityManager am = (AccessibilityManager) getContext(). 248 getSystemService(Context.ACCESSIBILITY_SERVICE); 249 if (am != null && am.isEnabled()) { 250 mApplicationIcon.setOnClickListener(this); 251 } 252 } 253 254 /** Unbinds the bar view from the task */ 255 void unbindFromTask() { 256 mTask = null; 257 mApplicationIcon.setImageDrawable(null); 258 mApplicationIcon.setOnClickListener(null); 259 mMoveTaskButton.setOnClickListener(null); 260 } 261 262 /** Updates the resize task bar button. */ 263 void updateResizeTaskBarIcon(Task t) { 264 SystemServicesProxy ssp = Recents.getSystemServices(); 265 Rect display = ssp.getWindowRect(); 266 Rect taskRect = ssp.getTaskBounds(t.key.id); 267 int resId = R.drawable.star; 268 if (display.equals(taskRect) || taskRect.isEmpty()) { 269 resId = R.drawable.vector_drawable_place_fullscreen; 270 } else { 271 boolean top = display.top == taskRect.top; 272 boolean bottom = display.bottom == taskRect.bottom; 273 boolean left = display.left == taskRect.left; 274 boolean right = display.right == taskRect.right; 275 if (top && bottom && left) { 276 resId = R.drawable.vector_drawable_place_left; 277 } else if (top && bottom && right) { 278 resId = R.drawable.vector_drawable_place_right; 279 } else if (top && left && right) { 280 resId = R.drawable.vector_drawable_place_top; 281 } else if (bottom && left && right) { 282 resId = R.drawable.vector_drawable_place_bottom; 283 } else if (top && right) { 284 resId = R.drawable.vector_drawable_place_top_right; 285 } else if (top && left) { 286 resId = R.drawable.vector_drawable_place_top_left; 287 } else if (bottom && right) { 288 resId = R.drawable.vector_drawable_place_bottom_right; 289 } else if (bottom && left) { 290 resId = R.drawable.vector_drawable_place_bottom_left; 291 } 292 } 293 mMoveTaskButton.setImageResource(resId); 294 } 295 296 /** Animates this task bar dismiss button when launching a task. */ 297 void startLaunchTaskDismissAnimation() { 298 if (mDismissButton.getVisibility() == View.VISIBLE) { 299 int taskViewExitToAppDuration = mContext.getResources().getInteger( 300 R.integer.recents_task_exit_to_app_duration); 301 mDismissButton.animate().cancel(); 302 mDismissButton.animate() 303 .alpha(0f) 304 .setStartDelay(0) 305 .setInterpolator(mFastOutSlowInInterpolator) 306 .setDuration(taskViewExitToAppDuration) 307 .start(); 308 } 309 } 310 311 /** Animates this task bar if the user does not interact with the stack after a certain time. */ 312 void startNoUserInteractionAnimation() { 313 if (mDismissButton.getVisibility() != View.VISIBLE) { 314 mDismissButton.setVisibility(View.VISIBLE); 315 mDismissButton.setAlpha(0f); 316 mDismissButton.animate() 317 .alpha(1f) 318 .setStartDelay(0) 319 .setInterpolator(mFastOutLinearInInterpolator) 320 .setDuration(getResources().getInteger( 321 R.integer.recents_task_enter_from_app_duration)) 322 .start(); 323 } 324 } 325 326 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 327 void setNoUserInteractionState() { 328 if (mDismissButton.getVisibility() != View.VISIBLE) { 329 mDismissButton.animate().cancel(); 330 mDismissButton.setVisibility(View.VISIBLE); 331 mDismissButton.setAlpha(1f); 332 } 333 } 334 335 /** Resets the state tracking that the user has not interacted with the stack after a certain time. */ 336 void resetNoUserInteractionState() { 337 mDismissButton.setVisibility(View.INVISIBLE); 338 } 339 340 @Override 341 protected int[] onCreateDrawableState(int extraSpace) { 342 343 // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged. 344 // This is to prevent layer trashing when the view is pressed. 345 return new int[] {}; 346 } 347 348 @Override 349 protected void dispatchDraw(Canvas canvas) { 350 super.dispatchDraw(canvas); 351 if (mLayersDisabled) { 352 mLayersDisabled = false; 353 postOnAnimation(new Runnable() { 354 @Override 355 public void run() { 356 mLayersDisabled = false; 357 setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); 358 } 359 }); 360 } 361 } 362 363 public void disableLayersForOneFrame() { 364 mLayersDisabled = true; 365 366 // Disable layer for a frame so we can draw our first frame faster. 367 setLayerType(LAYER_TYPE_NONE, null); 368 } 369 370 /** Notifies the associated TaskView has been focused. */ 371 void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) { 372 // If we are not animating the visible state, just return 373 if (!animateFocusedState) return; 374 375 boolean isRunning = false; 376 if (mFocusAnimator != null) { 377 isRunning = mFocusAnimator.isRunning(); 378 Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator); 379 } 380 381 if (focused) { 382 int currentColor = mBackgroundColor; 383 int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark); 384 int[][] states = new int[][] { 385 new int[] {}, 386 new int[] { android.R.attr.state_enabled }, 387 new int[] { android.R.attr.state_pressed } 388 }; 389 int[] newStates = new int[]{ 390 0, 391 android.R.attr.state_enabled, 392 android.R.attr.state_pressed 393 }; 394 int[] colors = new int[] { 395 currentColor, 396 secondaryColor, 397 secondaryColor 398 }; 399 mBackground.setColor(new ColorStateList(states, colors)); 400 mBackground.setState(newStates); 401 // Pulse the background color 402 int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark); 403 ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(), 404 currentColor, lightPrimaryColor); 405 backgroundColor.addListener(new AnimatorListenerAdapter() { 406 @Override 407 public void onAnimationStart(Animator animation) { 408 mBackground.setState(new int[]{}); 409 } 410 }); 411 backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 412 @Override 413 public void onAnimationUpdate(ValueAnimator animation) { 414 int color = (int) animation.getAnimatedValue(); 415 mBackgroundColorDrawable.setColor(color); 416 mBackgroundColor = color; 417 } 418 }); 419 backgroundColor.setRepeatCount(ValueAnimator.INFINITE); 420 backgroundColor.setRepeatMode(ValueAnimator.REVERSE); 421 // Pulse the translation 422 ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f); 423 translation.setRepeatCount(ValueAnimator.INFINITE); 424 translation.setRepeatMode(ValueAnimator.REVERSE); 425 426 mFocusAnimator = new AnimatorSet(); 427 mFocusAnimator.playTogether(backgroundColor, translation); 428 mFocusAnimator.setStartDelay(150); 429 mFocusAnimator.setDuration(750); 430 mFocusAnimator.start(); 431 } else { 432 if (isRunning) { 433 // Restore the background color 434 int currentColor = mBackgroundColor; 435 ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(), 436 currentColor, mCurrentPrimaryColor); 437 backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 438 @Override 439 public void onAnimationUpdate(ValueAnimator animation) { 440 int color = (int) animation.getAnimatedValue(); 441 mBackgroundColorDrawable.setColor(color); 442 mBackgroundColor = color; 443 } 444 }); 445 // Restore the translation 446 ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f); 447 448 mFocusAnimator = new AnimatorSet(); 449 mFocusAnimator.playTogether(backgroundColor, translation); 450 mFocusAnimator.setDuration(150); 451 mFocusAnimator.start(); 452 } else { 453 mBackground.setState(new int[] {}); 454 setTranslationZ(0f); 455 } 456 } 457 } 458 459 @Override 460 public void onClick(View v) { 461 if (v == mApplicationIcon) { 462 // In accessibility, a single click on the focused app info button will show it 463 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 464 } else if (v == mDismissButton) { 465 TaskView tv = Utilities.findParent(this, TaskView.class); 466 tv.dismissTask(); 467 468 // Keep track of deletions by the dismiss button 469 MetricsLogger.histogram(getContext(), "overview_task_dismissed_source", 470 Constants.Metrics.DismissSourceHeaderButton); 471 } else if (v == mMoveTaskButton) { 472 EventBus.getDefault().send(new ResizeTaskEvent(mTask)); 473 } 474 } 475 476 @Override 477 public boolean onLongClick(View v) { 478 if (v == mApplicationIcon) { 479 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 480 return true; 481 } 482 return false; 483 } 484} 485