TaskViewHeader.java revision dd7930354aaf2baf91810bc7a3e47543dbcc7f28
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.PorterDuffXfermode; 34import android.graphics.drawable.ColorDrawable; 35import android.graphics.drawable.Drawable; 36import android.graphics.drawable.RippleDrawable; 37import android.util.AttributeSet; 38import android.view.MotionEvent; 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.Utilities; 48import com.android.systemui.recents.model.Task; 49 50 51/* The task bar view */ 52class TaskViewHeader extends FrameLayout { 53 54 RecentsConfiguration mConfig; 55 56 ImageView mDismissButton; 57 ImageView mApplicationIcon; 58 TextView mActivityDescription; 59 60 RippleDrawable mBackground; 61 ColorDrawable mBackgroundColor; 62 Drawable mLightDismissDrawable; 63 Drawable mDarkDismissDrawable; 64 AnimatorSet mFocusAnimator; 65 ValueAnimator backgroundColorAnimator; 66 67 boolean mIsFullscreen; 68 boolean mCurrentPrimaryColorIsDark; 69 int mCurrentPrimaryColor; 70 71 static Paint sHighlightPaint; 72 73 public TaskViewHeader(Context context) { 74 this(context, null); 75 } 76 77 public TaskViewHeader(Context context, AttributeSet attrs) { 78 this(context, attrs, 0); 79 } 80 81 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) { 82 this(context, attrs, defStyleAttr, 0); 83 } 84 85 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 86 super(context, attrs, defStyleAttr, defStyleRes); 87 mConfig = RecentsConfiguration.getInstance(); 88 setWillNotDraw(false); 89 setClipToOutline(true); 90 setOutlineProvider(new ViewOutlineProvider() { 91 @Override 92 public void getOutline(View view, Outline outline) { 93 outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight()); 94 } 95 }); 96 97 // Load the dismiss resources 98 Resources res = context.getResources(); 99 mLightDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_light); 100 mDarkDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_dark); 101 102 // Configure the highlight paint 103 if (sHighlightPaint == null) { 104 sHighlightPaint = new Paint(); 105 sHighlightPaint.setStyle(Paint.Style.STROKE); 106 sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx); 107 sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor); 108 sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); 109 sHighlightPaint.setAntiAlias(true); 110 } 111 } 112 113 @Override 114 public boolean onTouchEvent(MotionEvent event) { 115 // We ignore taps on the task bar except on the filter and dismiss buttons 116 if (!Constants.DebugFlags.App.EnableTaskBarTouchEvents) return true; 117 118 return super.onTouchEvent(event); 119 } 120 121 @Override 122 protected void onFinishInflate() { 123 // Set the outline provider 124 setOutlineProvider(new ViewOutlineProvider() { 125 @Override 126 public void getOutline(View view, Outline outline) { 127 outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight()); 128 } 129 }); 130 131 // Initialize the icon and description views 132 mApplicationIcon = (ImageView) findViewById(R.id.application_icon); 133 mActivityDescription = (TextView) findViewById(R.id.activity_description); 134 mDismissButton = (ImageView) findViewById(R.id.dismiss_task); 135 136 // Hide the backgrounds if they are ripple drawables 137 if (!Constants.DebugFlags.App.EnableTaskFiltering) { 138 if (mApplicationIcon.getBackground() instanceof RippleDrawable) { 139 mApplicationIcon.setBackground(null); 140 } 141 } 142 143 mBackgroundColor = new ColorDrawable(0); 144 // Copy the ripple drawable since we are going to be manipulating it 145 mBackground = (RippleDrawable) 146 getResources().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), mBackgroundColor); 150 setBackground(mBackground); 151 } 152 153 @Override 154 protected void onDraw(Canvas canvas) { 155 if (!mIsFullscreen) { 156 // Draw the highlight at the top edge (but put the bottom edge just out of view) 157 float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f); 158 float radius = mConfig.taskViewRoundedCornerRadiusPx; 159 canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset, 160 getMeasuredHeight() + radius, radius, radius, sHighlightPaint); 161 } 162 } 163 164 /** Sets whether the current task is full screen or not. */ 165 void setIsFullscreen(boolean isFullscreen) { 166 mIsFullscreen = isFullscreen; 167 } 168 169 @Override 170 public boolean hasOverlappingRendering() { 171 return false; 172 } 173 174 /** Returns the secondary color for a primary color. */ 175 int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) { 176 int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK; 177 return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f); 178 } 179 180 /** Binds the bar view to the task */ 181 void rebindToTask(Task t) { 182 // If an activity icon is defined, then we use that as the primary icon to show in the bar, 183 // otherwise, we fall back to the application icon 184 if (t.activityIcon != null) { 185 mApplicationIcon.setImageDrawable(t.activityIcon); 186 } else if (t.applicationIcon != null) { 187 mApplicationIcon.setImageDrawable(t.applicationIcon); 188 } 189 mApplicationIcon.setContentDescription(t.activityLabel); 190 if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { 191 mActivityDescription.setText(t.activityLabel); 192 } 193 // Try and apply the system ui tint 194 int existingBgColor = (getBackground() instanceof ColorDrawable) ? 195 ((ColorDrawable) getBackground()).getColor() : 0; 196 if (existingBgColor != t.colorPrimary) { 197 mBackgroundColor.setColor(t.colorPrimary); 198 } 199 mCurrentPrimaryColor = t.colorPrimary; 200 mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor; 201 mActivityDescription.setTextColor(t.useLightOnPrimaryColor ? 202 mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor); 203 mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? 204 mLightDismissDrawable : mDarkDismissDrawable); 205 mDismissButton.setContentDescription( 206 getContext().getString(R.string.accessibility_recents_item_will_be_dismissed, 207 t.activityLabel)); 208 } 209 210 /** Unbinds the bar view from the task */ 211 void unbindFromTask() { 212 mApplicationIcon.setImageDrawable(null); 213 } 214 215 /** Prepares this task view for the enter-recents animations. This is called earlier in the 216 * first layout because the actual animation into recents may take a long time. */ 217 void prepareEnterRecentsAnimation() { 218 setVisibility(View.INVISIBLE); 219 } 220 221 /** Animates this task bar as it enters recents */ 222 void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) { 223 // Animate the task bar of the first task view 224 setVisibility(View.VISIBLE); 225 setAlpha(0f); 226 animate() 227 .alpha(1f) 228 .setStartDelay(delay) 229 .setInterpolator(mConfig.linearOutSlowInInterpolator) 230 .setDuration(mConfig.taskBarEnterAnimDuration) 231 .withEndAction(postAnimRunnable) 232 .withLayer() 233 .start(); 234 } 235 236 /** Animates this task bar as it exits recents */ 237 void startLaunchTaskAnimation(Runnable preAnimRunnable, final Runnable postAnimRunnable, 238 boolean isFocused) { 239 if (isFocused) { 240 onTaskViewFocusChanged(false); 241 } 242 243 // Animate the task bar out of the first task view 244 animate() 245 .alpha(0f) 246 .setStartDelay(0) 247 .setInterpolator(mConfig.linearOutSlowInInterpolator) 248 .setDuration(mConfig.taskBarExitAnimDuration) 249 .withStartAction(preAnimRunnable) 250 .withEndAction(new Runnable() { 251 @Override 252 public void run() { 253 post(postAnimRunnable); 254 } 255 }) 256 .withLayer() 257 .start(); 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.taskBarExitAnimDuration) 269 .withLayer() 270 .start(); 271 } 272 } 273 274 /** Animates this task bar if the user does not interact with the stack after a certain time. */ 275 void startNoUserInteractionAnimation() { 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.taskBarEnterAnimDuration) 283 .withLayer() 284 .start(); 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 /** Notifies the associated TaskView has been focused. */ 297 void onTaskViewFocusChanged(boolean focused) { 298 boolean isRunning = false; 299 if (mFocusAnimator != null) { 300 isRunning = mFocusAnimator.isRunning(); 301 mFocusAnimator.removeAllListeners(); 302 mFocusAnimator.cancel(); 303 } 304 if (focused) { 305 int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark); 306 int[][] states = new int[][] { 307 new int[] { android.R.attr.state_enabled }, 308 new int[] { android.R.attr.state_pressed } 309 }; 310 int[] newStates = new int[]{ 311 android.R.attr.state_enabled, 312 android.R.attr.state_pressed 313 }; 314 int[] colors = new int[] { 315 secondaryColor, 316 secondaryColor 317 }; 318 mBackground.setColor(new ColorStateList(states, colors)); 319 mBackground.setState(newStates); 320 // Pulse the background color 321 int currentColor = mBackgroundColor.getColor(); 322 int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark); 323 ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(), 324 lightPrimaryColor, currentColor); 325 backgroundColor.addListener(new AnimatorListenerAdapter() { 326 @Override 327 public void onAnimationStart(Animator animation) { 328 mBackground.setState(new int[]{}); 329 } 330 }); 331 backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 332 @Override 333 public void onAnimationUpdate(ValueAnimator animation) { 334 mBackgroundColor.setColor((Integer) animation.getAnimatedValue()); 335 } 336 }); 337 backgroundColor.setRepeatCount(ValueAnimator.INFINITE); 338 backgroundColor.setRepeatMode(ValueAnimator.REVERSE); 339 // Pulse the translation 340 ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f); 341 translation.setRepeatCount(ValueAnimator.INFINITE); 342 translation.setRepeatMode(ValueAnimator.REVERSE); 343 344 mFocusAnimator = new AnimatorSet(); 345 mFocusAnimator.playTogether(backgroundColor, translation); 346 mFocusAnimator.setStartDelay(750); 347 mFocusAnimator.setDuration(750); 348 mFocusAnimator.start(); 349 } else { 350 if (isRunning) { 351 // Restore the background color 352 int currentColor = mBackgroundColor.getColor(); 353 ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(), 354 currentColor, mCurrentPrimaryColor); 355 backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 356 @Override 357 public void onAnimationUpdate(ValueAnimator animation) { 358 mBackgroundColor.setColor((Integer) animation.getAnimatedValue()); 359 } 360 }); 361 // Restore the translation 362 ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f); 363 364 mFocusAnimator = new AnimatorSet(); 365 mFocusAnimator.playTogether(backgroundColor, translation); 366 mFocusAnimator.setDuration(150); 367 mFocusAnimator.start(); 368 } else { 369 mBackground.setState(new int[] {}); 370 setTranslationZ(0f); 371 } 372 } 373 } 374} 375