TaskViewHeader.java revision 37fc513d01503f15ee611595c248374696056bd8
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.ObjectAnimator; 20import android.content.Context; 21import android.content.res.ColorStateList; 22import android.graphics.Canvas; 23import android.graphics.Color; 24import android.graphics.Outline; 25import android.graphics.Paint; 26import android.graphics.PorterDuff; 27import android.graphics.PorterDuffColorFilter; 28import android.graphics.PorterDuffXfermode; 29import android.graphics.Rect; 30import android.graphics.drawable.ColorDrawable; 31import android.graphics.drawable.Drawable; 32import android.graphics.drawable.GradientDrawable; 33import android.graphics.drawable.RippleDrawable; 34import android.util.AttributeSet; 35import android.view.View; 36import android.view.ViewOutlineProvider; 37import android.view.animation.AnimationUtils; 38import android.view.animation.Interpolator; 39import android.widget.FrameLayout; 40import android.widget.ImageView; 41import android.widget.TextView; 42import com.android.internal.logging.MetricsLogger; 43import com.android.systemui.R; 44import com.android.systemui.recents.Constants; 45import com.android.systemui.recents.Recents; 46import com.android.systemui.recents.events.EventBus; 47import com.android.systemui.recents.events.ui.ResizeTaskEvent; 48import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; 49import com.android.systemui.recents.misc.SystemServicesProxy; 50import com.android.systemui.recents.misc.Utilities; 51import com.android.systemui.recents.model.Task; 52 53 54/* The task bar view */ 55public class TaskViewHeader extends FrameLayout 56 implements View.OnClickListener, View.OnLongClickListener { 57 58 Task mTask; 59 60 // Header views 61 ImageView mMoveTaskButton; 62 ImageView mDismissButton; 63 ImageView mApplicationIcon; 64 TextView mActivityDescription; 65 66 // Header drawables 67 int mCornerRadius; 68 int mHighlightHeight; 69 Drawable mLightDismissDrawable; 70 Drawable mDarkDismissDrawable; 71 RippleDrawable mBackground; 72 GradientDrawable mBackgroundColorDrawable; 73 ObjectAnimator 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 Interpolator mFastOutSlowInInterpolator; 84 Interpolator mFastOutLinearInInterpolator; 85 86 boolean mLayersDisabled; 87 88 public TaskViewHeader(Context context) { 89 this(context, null); 90 } 91 92 public TaskViewHeader(Context context, AttributeSet attrs) { 93 this(context, attrs, 0); 94 } 95 96 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) { 97 this(context, attrs, defStyleAttr, 0); 98 } 99 100 public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 101 super(context, attrs, defStyleAttr, defStyleRes); 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 mCornerRadius = getResources().getDimensionPixelSize( 117 R.dimen.recents_task_view_rounded_corners_radius); 118 mHighlightHeight = getResources().getDimensionPixelSize( 119 R.dimen.recents_task_view_highlight); 120 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, 121 com.android.internal.R.interpolator.fast_out_slow_in); 122 mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, 123 com.android.internal.R.interpolator.fast_out_linear_in); 124 125 // Configure the highlight paint 126 if (sHighlightPaint == null) { 127 sHighlightPaint = new Paint(); 128 sHighlightPaint.setStyle(Paint.Style.STROKE); 129 sHighlightPaint.setStrokeWidth(mHighlightHeight); 130 sHighlightPaint.setColor(context.getColor(R.color.recents_task_bar_highlight_color)); 131 sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); 132 sHighlightPaint.setAntiAlias(true); 133 } 134 } 135 136 @Override 137 protected void onFinishInflate() { 138 // Initialize the icon and description views 139 mApplicationIcon = (ImageView) findViewById(R.id.application_icon); 140 mApplicationIcon.setOnLongClickListener(this); 141 mActivityDescription = (TextView) findViewById(R.id.activity_description); 142 mDismissButton = (ImageView) findViewById(R.id.dismiss_task); 143 mDismissButton.setOnClickListener(this); 144 mMoveTaskButton = (ImageView) findViewById(R.id.move_task); 145 146 // Hide the backgrounds if they are ripple drawables 147 if (mApplicationIcon.getBackground() instanceof RippleDrawable) { 148 mApplicationIcon.setBackground(null); 149 } 150 151 mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable 152 .recents_task_view_header_bg_color); 153 // Copy the ripple drawable since we are going to be manipulating it 154 mBackground = (RippleDrawable) 155 getContext().getDrawable(R.drawable.recents_task_view_header_bg); 156 mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable(); 157 mBackground.setColor(ColorStateList.valueOf(0)); 158 mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColorDrawable); 159 setBackground(mBackground); 160 } 161 162 @Override 163 protected void onDraw(Canvas canvas) { 164 // Draw the highlight at the top edge (but put the bottom edge just out of view) 165 float offset = (float) Math.ceil(mHighlightHeight / 2f); 166 float radius = mCornerRadius; 167 int count = canvas.save(Canvas.CLIP_SAVE_FLAG); 168 canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight()); 169 canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset, 170 getMeasuredHeight() + radius, radius, radius, sHighlightPaint); 171 canvas.restoreToCount(count); 172 } 173 174 @Override 175 public boolean hasOverlappingRendering() { 176 return false; 177 } 178 179 /** 180 * Sets the dim alpha, only used when we are not using hardware layers. 181 * (see RecentsConfiguration.useHardwareLayers) 182 */ 183 void setDimAlpha(int alpha) { 184 mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0)); 185 mDimLayerPaint.setColorFilter(mDimColorFilter); 186 if (!mLayersDisabled) { 187 setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); 188 } 189 } 190 191 /** Returns the secondary color for a primary color. */ 192 int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) { 193 int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK; 194 return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f); 195 } 196 197 /** Binds the bar view to the task */ 198 public void rebindToTask(Task t) { 199 mTask = t; 200 201 // If an activity icon is defined, then we use that as the primary icon to show in the bar, 202 // otherwise, we fall back to the application icon 203 if (t.activityIcon != null) { 204 mApplicationIcon.setImageDrawable(t.activityIcon); 205 } else if (t.applicationIcon != null) { 206 mApplicationIcon.setImageDrawable(t.applicationIcon); 207 } 208 if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { 209 mActivityDescription.setText(t.activityLabel); 210 } 211 mActivityDescription.setContentDescription(t.contentDescription); 212 213 // Try and apply the system ui tint 214 int existingBgColor = (getBackground() instanceof ColorDrawable) ? 215 ((ColorDrawable) getBackground()).getColor() : 0; 216 if (existingBgColor != t.colorPrimary) { 217 mBackgroundColorDrawable.setColor(t.colorPrimary); 218 } 219 220 int taskBarViewLightTextColor = getResources().getColor( 221 R.color.recents_task_bar_light_text_color); 222 int taskBarViewDarkTextColor = getResources().getColor( 223 R.color.recents_task_bar_dark_text_color); 224 mActivityDescription.setTextColor(t.useLightOnPrimaryColor ? 225 taskBarViewLightTextColor : taskBarViewDarkTextColor); 226 mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? 227 mLightDismissDrawable : mDarkDismissDrawable); 228 mDismissButton.setContentDescription(String.format(mDismissContentDescription, 229 t.contentDescription)); 230 updateResizeTaskBarIcon(t); 231 mMoveTaskButton.setVisibility(View.VISIBLE); 232 mMoveTaskButton.setOnClickListener(this); 233 234 // In accessibility, a single click on the focused app info button will show it 235 SystemServicesProxy ssp = Recents.getSystemServices(); 236 if (ssp.isTouchExplorationEnabled()) { 237 mApplicationIcon.setOnClickListener(this); 238 } 239 } 240 241 /** Unbinds the bar view from the task */ 242 void unbindFromTask() { 243 mTask = null; 244 mApplicationIcon.setImageDrawable(null); 245 mApplicationIcon.setOnClickListener(null); 246 mMoveTaskButton.setOnClickListener(null); 247 248 // Stop any focus animations 249 Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator); 250 } 251 252 /** Updates the resize task bar button. */ 253 void updateResizeTaskBarIcon(Task t) { 254 SystemServicesProxy ssp = Recents.getSystemServices(); 255 Rect display = ssp.getWindowRect(); 256 Rect taskRect = ssp.getTaskBounds(t.key.id); 257 int resId = R.drawable.star; 258 if (display.equals(taskRect) || taskRect.isEmpty()) { 259 resId = R.drawable.vector_drawable_place_fullscreen; 260 } else { 261 boolean top = display.top == taskRect.top; 262 boolean bottom = display.bottom == taskRect.bottom; 263 boolean left = display.left == taskRect.left; 264 boolean right = display.right == taskRect.right; 265 if (top && bottom && left) { 266 resId = R.drawable.vector_drawable_place_left; 267 } else if (top && bottom && right) { 268 resId = R.drawable.vector_drawable_place_right; 269 } else if (top && left && right) { 270 resId = R.drawable.vector_drawable_place_top; 271 } else if (bottom && left && right) { 272 resId = R.drawable.vector_drawable_place_bottom; 273 } else if (top && right) { 274 resId = R.drawable.vector_drawable_place_top_right; 275 } else if (top && left) { 276 resId = R.drawable.vector_drawable_place_top_left; 277 } else if (bottom && right) { 278 resId = R.drawable.vector_drawable_place_bottom_right; 279 } else if (bottom && left) { 280 resId = R.drawable.vector_drawable_place_bottom_left; 281 } 282 } 283 mMoveTaskButton.setImageResource(resId); 284 } 285 286 /** Animates this task bar dismiss button when launching a task. */ 287 void startLaunchTaskDismissAnimation(final Runnable postAnimationRunanble) { 288 if (mDismissButton.getVisibility() == View.VISIBLE) { 289 int taskViewExitToAppDuration = mContext.getResources().getInteger( 290 R.integer.recents_task_exit_to_app_duration); 291 mDismissButton.animate().cancel(); 292 mDismissButton.animate() 293 .alpha(0f) 294 .setStartDelay(0) 295 .setInterpolator(mFastOutSlowInInterpolator) 296 .setDuration(taskViewExitToAppDuration) 297 .withEndAction(postAnimationRunanble) 298 .start(); 299 } 300 } 301 302 /** Animates this task bar if the user does not interact with the stack after a certain time. */ 303 void startNoUserInteractionAnimation() { 304 if (mDismissButton.getVisibility() != View.VISIBLE) { 305 mDismissButton.setVisibility(View.VISIBLE); 306 mDismissButton.setAlpha(0f); 307 mDismissButton.animate() 308 .alpha(1f) 309 .setStartDelay(0) 310 .setInterpolator(mFastOutLinearInInterpolator) 311 .setDuration(getResources().getInteger( 312 R.integer.recents_task_enter_from_app_duration)) 313 .start(); 314 } 315 } 316 317 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 318 void setNoUserInteractionState() { 319 if (mDismissButton.getVisibility() != View.VISIBLE) { 320 mDismissButton.animate().cancel(); 321 mDismissButton.setVisibility(View.VISIBLE); 322 mDismissButton.setAlpha(1f); 323 } 324 } 325 326 /** Resets the state tracking that the user has not interacted with the stack after a certain time. */ 327 void resetNoUserInteractionState() { 328 mDismissButton.setVisibility(View.INVISIBLE); 329 } 330 331 @Override 332 protected int[] onCreateDrawableState(int extraSpace) { 333 334 // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged. 335 // This is to prevent layer trashing when the view is pressed. 336 return new int[] {}; 337 } 338 339 @Override 340 protected void dispatchDraw(Canvas canvas) { 341 super.dispatchDraw(canvas); 342 if (mLayersDisabled) { 343 mLayersDisabled = false; 344 postOnAnimation(new Runnable() { 345 @Override 346 public void run() { 347 mLayersDisabled = false; 348 setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); 349 } 350 }); 351 } 352 } 353 354 public void disableLayersForOneFrame() { 355 mLayersDisabled = true; 356 357 // Disable layer for a frame so we can draw our first frame faster. 358 setLayerType(LAYER_TYPE_NONE, null); 359 } 360 361 /** Notifies the associated TaskView has been focused. */ 362 void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) { 363 boolean isRunning = false; 364 if (mFocusAnimator != null) { 365 isRunning = mFocusAnimator.isRunning(); 366 } 367 Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator); 368 369 if (focused) { 370 // If we are not animating the visible state, just return 371 if (!animateFocusedState) return; 372 373 // Bump up the translation 374 mFocusAnimator = ObjectAnimator.ofFloat(this, "translationZ", 8f); 375 mFocusAnimator.setDuration(200); 376 mFocusAnimator.start(); 377 } else { 378 if (isRunning) { 379 // Restore the translation 380 mFocusAnimator = ObjectAnimator.ofFloat(this, "translationZ", 0f); 381 mFocusAnimator.setDuration(150); 382 mFocusAnimator.start(); 383 } else { 384 setTranslationZ(0f); 385 } 386 } 387 } 388 389 @Override 390 public void onClick(View v) { 391 if (v == mApplicationIcon) { 392 // In accessibility, a single click on the focused app info button will show it 393 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 394 } else if (v == mDismissButton) { 395 TaskView tv = Utilities.findParent(this, TaskView.class); 396 tv.dismissTask(); 397 398 // Keep track of deletions by the dismiss button 399 MetricsLogger.histogram(getContext(), "overview_task_dismissed_source", 400 Constants.Metrics.DismissSourceHeaderButton); 401 } else if (v == mMoveTaskButton) { 402 EventBus.getDefault().send(new ResizeTaskEvent(mTask)); 403 } 404 } 405 406 @Override 407 public boolean onLongClick(View v) { 408 if (v == mApplicationIcon) { 409 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 410 return true; 411 } 412 return false; 413 } 414} 415