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