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