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.graphics.Canvas; 28 import android.graphics.Color; 29 import android.graphics.drawable.ColorDrawable; 30 import android.graphics.drawable.Drawable; 31 import android.graphics.drawable.GradientDrawable; 32 import android.graphics.drawable.RippleDrawable; 33 import android.graphics.Outline; 34 import android.graphics.Paint; 35 import android.graphics.PorterDuff; 36 import android.graphics.PorterDuffColorFilter; 37 import android.graphics.PorterDuffXfermode; 38 import android.graphics.Rect; 39 import android.util.AttributeSet; 40 import android.view.View; 41 import android.view.ViewOutlineProvider; 42 import android.widget.FrameLayout; 43 import android.widget.ImageView; 44 import android.widget.TextView; 45 import com.android.systemui.R; 46 import com.android.systemui.recents.Constants; 47 import com.android.systemui.recents.RecentsConfiguration; 48 import com.android.systemui.recents.misc.SystemServicesProxy; 49 import com.android.systemui.recents.misc.Utilities; 50 import com.android.systemui.recents.model.RecentsTaskLoader; 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 private SystemServicesProxy mSsp; 59 60 // Header views 61 ImageView mMoveTaskButton; 62 ImageView mDismissButton; 63 ImageView mApplicationIcon; 64 TextView mActivityDescription; 65 66 // Header drawables 67 boolean mCurrentPrimaryColorIsDark; 68 int mCurrentPrimaryColor; 69 int mBackgroundColor; 70 Drawable mLightDismissDrawable; 71 Drawable mDarkDismissDrawable; 72 RippleDrawable mBackground; 73 GradientDrawable mBackgroundColorDrawable; 74 AnimatorSet mFocusAnimator; 75 String mDismissContentDescription; 76 77 // Static highlight that we draw at the top of each view 78 static Paint sHighlightPaint; 79 80 // Header dim, which is only used when task view hardware layers are not used 81 Paint mDimLayerPaint = new Paint(); 82 PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP); 83 84 boolean mLayersDisabled; 85 86 public TaskViewHeader(Context context) { 87 this(context, null); 88 } 89 90 public TaskViewHeader(Context context, AttributeSet attrs) { 91 this(context, attrs, 0); 92 } 93 94 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) { 95 this(context, attrs, defStyleAttr, 0); 96 } 97 98 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 99 super(context, attrs, defStyleAttr, defStyleRes); 100 mConfig = RecentsConfiguration.getInstance(); 101 mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); 102 setWillNotDraw(false); 103 setClipToOutline(true); 104 setOutlineProvider(new ViewOutlineProvider() { 105 @Override 106 public void getOutline(View view, Outline outline) { 107 outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight()); 108 } 109 }); 110 111 // Load the dismiss resources 112 mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light); 113 mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark); 114 mDismissContentDescription = 115 context.getString(R.string.accessibility_recents_item_will_be_dismissed); 116 117 // Configure the highlight paint 118 if (sHighlightPaint == null) { 119 sHighlightPaint = new Paint(); 120 sHighlightPaint.setStyle(Paint.Style.STROKE); 121 sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx); 122 sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor); 123 sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); 124 sHighlightPaint.setAntiAlias(true); 125 } 126 } 127 128 @Override 129 protected void onFinishInflate() { 130 // Initialize the icon and description views 131 mApplicationIcon = (ImageView) findViewById(R.id.application_icon); 132 mActivityDescription = (TextView) findViewById(R.id.activity_description); 133 mDismissButton = (ImageView) findViewById(R.id.dismiss_task); 134 mMoveTaskButton = (ImageView) findViewById(R.id.move_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 mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable 144 .recents_task_view_header_bg_color); 145 // Copy the ripple drawable since we are going to be manipulating it 146 mBackground = (RippleDrawable) 147 getContext().getDrawable(R.drawable.recents_task_view_header_bg); 148 mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable(); 149 mBackground.setColor(ColorStateList.valueOf(0)); 150 mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColorDrawable); 151 setBackground(mBackground); 152 } 153 154 @Override 155 protected void onDraw(Canvas canvas) { 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 int count = canvas.save(Canvas.CLIP_SAVE_FLAG); 160 canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight()); 161 canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset, 162 getMeasuredHeight() + radius, radius, radius, sHighlightPaint); 163 canvas.restoreToCount(count); 164 } 165 166 @Override 167 public boolean hasOverlappingRendering() { 168 return false; 169 } 170 171 /** 172 * Sets the dim alpha, only used when we are not using hardware layers. 173 * (see RecentsConfiguration.useHardwareLayers) 174 */ 175 void setDimAlpha(int alpha) { 176 mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0)); 177 mDimLayerPaint.setColorFilter(mDimColorFilter); 178 if (!mLayersDisabled) { 179 setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); 180 } 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 if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { 199 mActivityDescription.setText(t.activityLabel); 200 } 201 mActivityDescription.setContentDescription(t.contentDescription); 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(String.format(mDismissContentDescription, 217 t.contentDescription)); 218 mMoveTaskButton.setVisibility((mConfig.multiStackEnabled) ? View.VISIBLE : View.INVISIBLE); 219 if (mConfig.multiStackEnabled) { 220 updateResizeTaskBarIcon(t); 221 } 222 } 223 224 /** Updates the resize task bar button. */ 225 void updateResizeTaskBarIcon(Task t) { 226 Rect display = mSsp.getWindowRect(); 227 Rect taskRect = mSsp.getTaskBounds(t.key.stackId); 228 int resId = R.drawable.star; 229 if (display.equals(taskRect) || taskRect.isEmpty()) { 230 resId = R.drawable.vector_drawable_place_fullscreen; 231 } else { 232 boolean top = display.top == taskRect.top; 233 boolean bottom = display.bottom == taskRect.bottom; 234 boolean left = display.left == taskRect.left; 235 boolean right = display.right == taskRect.right; 236 if (top && bottom && left) { 237 resId = R.drawable.vector_drawable_place_left; 238 } else if (top && bottom && right) { 239 resId = R.drawable.vector_drawable_place_right; 240 } else if (top && left && right) { 241 resId = R.drawable.vector_drawable_place_top; 242 } else if (bottom && left && right) { 243 resId = R.drawable.vector_drawable_place_bottom; 244 } else if (top && right) { 245 resId = R.drawable.vector_drawable_place_top_right; 246 } else if (top && left) { 247 resId = R.drawable.vector_drawable_place_top_left; 248 } else if (bottom && right) { 249 resId = R.drawable.vector_drawable_place_bottom_right; 250 } else if (bottom && left) { 251 resId = R.drawable.vector_drawable_place_bottom_left; 252 } 253 } 254 mMoveTaskButton.setImageResource(resId); 255 } 256 257 /** Unbinds the bar view from the task */ 258 void unbindFromTask() { 259 mApplicationIcon.setImageDrawable(null); 260 } 261 262 /** Animates this task bar dismiss button when launching a task. */ 263 void startLaunchTaskDismissAnimation() { 264 if (mDismissButton.getVisibility() == View.VISIBLE) { 265 mDismissButton.animate().cancel(); 266 mDismissButton.animate() 267 .alpha(0f) 268 .setStartDelay(0) 269 .setInterpolator(mConfig.fastOutSlowInInterpolator) 270 .setDuration(mConfig.taskViewExitToAppDuration) 271 .start(); 272 } 273 } 274 275 /** Animates this task bar if the user does not interact with the stack after a certain time. */ 276 void startNoUserInteractionAnimation() { 277 if (mDismissButton.getVisibility() != View.VISIBLE) { 278 mDismissButton.setVisibility(View.VISIBLE); 279 mDismissButton.setAlpha(0f); 280 mDismissButton.animate() 281 .alpha(1f) 282 .setStartDelay(0) 283 .setInterpolator(mConfig.fastOutLinearInInterpolator) 284 .setDuration(mConfig.taskViewEnterFromAppDuration) 285 .start(); 286 } 287 } 288 289 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 290 void setNoUserInteractionState() { 291 if (mDismissButton.getVisibility() != View.VISIBLE) { 292 mDismissButton.animate().cancel(); 293 mDismissButton.setVisibility(View.VISIBLE); 294 mDismissButton.setAlpha(1f); 295 } 296 } 297 298 /** Resets the state tracking that the user has not interacted with the stack after a certain time. */ 299 void resetNoUserInteractionState() { 300 mDismissButton.setVisibility(View.INVISIBLE); 301 } 302 303 @Override 304 protected int[] onCreateDrawableState(int extraSpace) { 305 306 // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged. 307 // This is to prevent layer trashing when the view is pressed. 308 return new int[] {}; 309 } 310 311 @Override 312 protected void dispatchDraw(Canvas canvas) { 313 super.dispatchDraw(canvas); 314 if (mLayersDisabled) { 315 mLayersDisabled = false; 316 postOnAnimation(new Runnable() { 317 @Override 318 public void run() { 319 mLayersDisabled = false; 320 setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); 321 } 322 }); 323 } 324 } 325 326 public void disableLayersForOneFrame() { 327 mLayersDisabled = true; 328 329 // Disable layer for a frame so we can draw our first frame faster. 330 setLayerType(LAYER_TYPE_NONE, null); 331 } 332 333 /** Notifies the associated TaskView has been focused. */ 334 void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) { 335 // If we are not animating the visible state, just return 336 if (!animateFocusedState) return; 337 338 boolean isRunning = false; 339 if (mFocusAnimator != null) { 340 isRunning = mFocusAnimator.isRunning(); 341 Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator); 342 } 343 344 if (focused) { 345 int currentColor = mBackgroundColor; 346 int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark); 347 int[][] states = new int[][] { 348 new int[] {}, 349 new int[] { android.R.attr.state_enabled }, 350 new int[] { android.R.attr.state_pressed } 351 }; 352 int[] newStates = new int[]{ 353 0, 354 android.R.attr.state_enabled, 355 android.R.attr.state_pressed 356 }; 357 int[] colors = new int[] { 358 currentColor, 359 secondaryColor, 360 secondaryColor 361 }; 362 mBackground.setColor(new ColorStateList(states, colors)); 363 mBackground.setState(newStates); 364 // Pulse the background color 365 int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark); 366 ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(), 367 currentColor, lightPrimaryColor); 368 backgroundColor.addListener(new AnimatorListenerAdapter() { 369 @Override 370 public void onAnimationStart(Animator animation) { 371 mBackground.setState(new int[]{}); 372 } 373 }); 374 backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 375 @Override 376 public void onAnimationUpdate(ValueAnimator animation) { 377 int color = (int) animation.getAnimatedValue(); 378 mBackgroundColorDrawable.setColor(color); 379 mBackgroundColor = color; 380 } 381 }); 382 backgroundColor.setRepeatCount(ValueAnimator.INFINITE); 383 backgroundColor.setRepeatMode(ValueAnimator.REVERSE); 384 // Pulse the translation 385 ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f); 386 translation.setRepeatCount(ValueAnimator.INFINITE); 387 translation.setRepeatMode(ValueAnimator.REVERSE); 388 389 mFocusAnimator = new AnimatorSet(); 390 mFocusAnimator.playTogether(backgroundColor, translation); 391 mFocusAnimator.setStartDelay(150); 392 mFocusAnimator.setDuration(750); 393 mFocusAnimator.start(); 394 } else { 395 if (isRunning) { 396 // Restore the background color 397 int currentColor = mBackgroundColor; 398 ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(), 399 currentColor, mCurrentPrimaryColor); 400 backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 401 @Override 402 public void onAnimationUpdate(ValueAnimator animation) { 403 int color = (int) animation.getAnimatedValue(); 404 mBackgroundColorDrawable.setColor(color); 405 mBackgroundColor = color; 406 } 407 }); 408 // Restore the translation 409 ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f); 410 411 mFocusAnimator = new AnimatorSet(); 412 mFocusAnimator.playTogether(backgroundColor, translation); 413 mFocusAnimator.setDuration(150); 414 mFocusAnimator.start(); 415 } else { 416 mBackground.setState(new int[] {}); 417 setTranslationZ(0f); 418 } 419 } 420 } 421 } 422