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