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