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