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