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