TaskViewHeader.java revision 296278a0679375b8c43962a9e3c9bb4e8ab201e7
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.content.Context; 20import android.content.res.ColorStateList; 21import android.graphics.Canvas; 22import android.graphics.Color; 23import android.graphics.Paint; 24import android.graphics.PorterDuff; 25import android.graphics.PorterDuffXfermode; 26import android.graphics.Rect; 27import android.graphics.drawable.ColorDrawable; 28import android.graphics.drawable.Drawable; 29import android.graphics.drawable.GradientDrawable; 30import android.graphics.drawable.RippleDrawable; 31import android.util.AttributeSet; 32import android.view.View; 33import android.view.animation.AnimationUtils; 34import android.view.animation.Interpolator; 35import android.widget.FrameLayout; 36import android.widget.ImageView; 37import android.widget.TextView; 38import com.android.internal.logging.MetricsLogger; 39import com.android.systemui.R; 40import com.android.systemui.recents.Constants; 41import com.android.systemui.recents.Recents; 42import com.android.systemui.recents.events.EventBus; 43import com.android.systemui.recents.events.activity.LaunchTaskEvent; 44import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; 45import com.android.systemui.recents.misc.SystemServicesProxy; 46import com.android.systemui.recents.misc.Utilities; 47import com.android.systemui.recents.model.Task; 48 49import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 50import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; 51import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 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 mIconView; 64 TextView mTitleView; 65 int mMoveTaskTargetStackId = INVALID_STACK_ID; 66 67 // Header drawables 68 Rect mTaskViewRect = new Rect(); 69 int mCornerRadius; 70 int mHighlightHeight; 71 Drawable mLightDismissDrawable; 72 Drawable mDarkDismissDrawable; 73 RippleDrawable mBackground; 74 GradientDrawable mBackgroundColorDrawable; 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 83 Interpolator mFastOutSlowInInterpolator; 84 Interpolator mFastOutLinearInInterpolator; 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 setWillNotDraw(false); 101 102 // Load the dismiss resources 103 mDimLayerPaint.setColor(Color.argb(0, 0, 0, 0)); 104 mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light); 105 mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark); 106 mDismissContentDescription = 107 context.getString(R.string.accessibility_recents_item_will_be_dismissed); 108 mCornerRadius = getResources().getDimensionPixelSize( 109 R.dimen.recents_task_view_rounded_corners_radius); 110 mHighlightHeight = getResources().getDimensionPixelSize( 111 R.dimen.recents_task_view_highlight); 112 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, 113 com.android.internal.R.interpolator.fast_out_slow_in); 114 mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, 115 com.android.internal.R.interpolator.fast_out_linear_in); 116 117 // Configure the highlight paint 118 if (sHighlightPaint == null) { 119 sHighlightPaint = new Paint(); 120 sHighlightPaint.setStyle(Paint.Style.STROKE); 121 sHighlightPaint.setStrokeWidth(mHighlightHeight); 122 sHighlightPaint.setColor(context.getColor(R.color.recents_task_bar_highlight_color)); 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 mIconView = (ImageView) findViewById(R.id.icon); 132 mIconView.setOnLongClickListener(this); 133 mTitleView = (TextView) findViewById(R.id.title); 134 mDismissButton = (ImageView) findViewById(R.id.dismiss_task); 135 mDismissButton.setOnClickListener(this); 136 mMoveTaskButton = (ImageView) findViewById(R.id.move_task); 137 138 // Hide the backgrounds if they are ripple drawables 139 if (mIconView.getBackground() instanceof RippleDrawable) { 140 mIconView.setBackground(null); 141 } 142 143 mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable( 144 R.drawable.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 /** 155 * Called when the task view frame changes, allowing us to move the contents of the header 156 * to match the frame changes. 157 */ 158 public void onTaskViewSizeChanged(int width, int height) { 159 mTaskViewRect.set(0, 0, width, height); 160 boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE; 161 int appIconWidth = mIconView.getMeasuredWidth(); 162 int activityDescWidth = mTitleView.getMeasuredWidth(); 163 int dismissIconWidth = mDismissButton.getMeasuredWidth(); 164 int moveTaskIconWidth = mMoveTaskButton.getVisibility() == View.VISIBLE 165 ? mMoveTaskButton.getMeasuredWidth() 166 : 0; 167 168 // Priority-wise, we show the activity icon first, the dismiss icon if there is room, the 169 // move-task icon if there is room, and then finally, the activity label if there is room 170 if (width < (appIconWidth + dismissIconWidth)) { 171 mTitleView.setVisibility(View.INVISIBLE); 172 if (updateMoveTaskButton) { 173 mMoveTaskButton.setVisibility(View.INVISIBLE); 174 } 175 mDismissButton.setVisibility(View.INVISIBLE); 176 } else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth)) { 177 mTitleView.setVisibility(View.INVISIBLE); 178 if (updateMoveTaskButton) { 179 mMoveTaskButton.setVisibility(View.INVISIBLE); 180 } 181 mDismissButton.setVisibility(View.VISIBLE); 182 } else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth + 183 activityDescWidth)) { 184 mTitleView.setVisibility(View.INVISIBLE); 185 if (updateMoveTaskButton) { 186 mMoveTaskButton.setVisibility(View.VISIBLE); 187 } 188 mDismissButton.setVisibility(View.VISIBLE); 189 } else { 190 mTitleView.setVisibility(View.VISIBLE); 191 if (updateMoveTaskButton) { 192 mMoveTaskButton.setVisibility(View.VISIBLE); 193 } 194 mDismissButton.setVisibility(View.VISIBLE); 195 } 196 if (updateMoveTaskButton) { 197 mMoveTaskButton.setTranslationX(width - getMeasuredWidth()); 198 } 199 mDismissButton.setTranslationX(width - getMeasuredWidth()); 200 invalidate(); 201 } 202 203 @Override 204 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 205 onTaskViewSizeChanged(mTaskViewRect.width(), mTaskViewRect.height()); 206 } 207 208 @Override 209 protected void onDraw(Canvas canvas) { 210 // Draw the highlight at the top edge (but put the bottom edge just out of view) 211 float offset = (float) Math.ceil(mHighlightHeight / 2f); 212 float radius = mCornerRadius; 213 int count = canvas.save(Canvas.CLIP_SAVE_FLAG); 214 canvas.clipRect(0, 0, mTaskViewRect.width(), getMeasuredHeight()); 215 canvas.drawRoundRect(-offset, 0f, (float) mTaskViewRect.width() + offset, 216 getMeasuredHeight() + radius, radius, radius, sHighlightPaint); 217 canvas.restoreToCount(count); 218 } 219 220 /** 221 * Sets the dim alpha, only used when we are not using hardware layers. 222 * (see RecentsConfiguration.useHardwareLayers) 223 */ 224 void setDimAlpha(int alpha) { 225 mDimLayerPaint.setColor(Color.argb(alpha, 0, 0, 0)); 226 invalidate(); 227 } 228 229 /** Binds the bar view to the task */ 230 public void rebindToTask(Task t) { 231 SystemServicesProxy ssp = Recents.getSystemServices(); 232 mTask = t; 233 234 // If an activity icon is defined, then we use that as the primary icon to show in the bar, 235 // otherwise, we fall back to the application icon 236 if (t.icon != null) { 237 mIconView.setImageDrawable(t.icon); 238 } 239 if (!mTitleView.getText().toString().equals(t.title)) { 240 mTitleView.setText(t.title); 241 } 242 mTitleView.setContentDescription(t.contentDescription); 243 244 // Try and apply the system ui tint 245 int existingBgColor = (getBackground() instanceof ColorDrawable) ? 246 ((ColorDrawable) getBackground()).getColor() : 0; 247 if (existingBgColor != t.colorPrimary) { 248 mBackgroundColorDrawable.setColor(t.colorPrimary); 249 } 250 251 int taskBarViewLightTextColor = getResources().getColor( 252 R.color.recents_task_bar_light_text_color); 253 int taskBarViewDarkTextColor = getResources().getColor( 254 R.color.recents_task_bar_dark_text_color); 255 mTitleView.setTextColor(t.useLightOnPrimaryColor ? 256 taskBarViewLightTextColor : taskBarViewDarkTextColor); 257 mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? 258 mLightDismissDrawable : mDarkDismissDrawable); 259 mDismissButton.setContentDescription(String.format(mDismissContentDescription, 260 t.contentDescription)); 261 262 // When freeform workspaces are enabled, then update the move-task button depending on the 263 // current task 264 if (ssp.hasFreeformWorkspaceSupport()) { 265 if (t.isFreeformTask()) { 266 mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID; 267 mMoveTaskButton.setImageResource(t.useLightOnPrimaryColor 268 ? R.drawable.recents_move_task_fullscreen_light 269 : R.drawable.recents_move_task_fullscreen_dark); 270 } else { 271 mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID; 272 mMoveTaskButton.setImageResource(t.useLightOnPrimaryColor 273 ? R.drawable.recents_move_task_freeform_light 274 : R.drawable.recents_move_task_freeform_dark); 275 } 276 mMoveTaskButton.setVisibility(View.VISIBLE); 277 mMoveTaskButton.setOnClickListener(this); 278 } 279 280 // In accessibility, a single click on the focused app info button will show it 281 if (ssp.isTouchExplorationEnabled()) { 282 mIconView.setOnClickListener(this); 283 } 284 } 285 286 /** Unbinds the bar view from the task */ 287 void unbindFromTask() { 288 mTask = null; 289 mIconView.setImageDrawable(null); 290 mIconView.setOnClickListener(null); 291 mMoveTaskButton.setOnClickListener(null); 292 } 293 294 /** Animates this task bar dismiss button when launching a task. */ 295 void startLaunchTaskDismissAnimation(final Runnable postAnimationRunanble) { 296 if (mDismissButton.getVisibility() == View.VISIBLE) { 297 int taskViewExitToAppDuration = mContext.getResources().getInteger( 298 R.integer.recents_task_exit_to_app_duration); 299 mDismissButton.animate().cancel(); 300 mDismissButton.animate() 301 .alpha(0f) 302 .setStartDelay(0) 303 .setInterpolator(mFastOutSlowInInterpolator) 304 .setDuration(taskViewExitToAppDuration) 305 .withEndAction(postAnimationRunanble) 306 .start(); 307 } 308 } 309 310 /** Animates this task bar if the user does not interact with the stack after a certain time. */ 311 void startNoUserInteractionAnimation() { 312 if (mDismissButton.getVisibility() != View.VISIBLE) { 313 mDismissButton.setVisibility(View.VISIBLE); 314 mDismissButton.setAlpha(0f); 315 mDismissButton.animate() 316 .alpha(1f) 317 .setStartDelay(0) 318 .setInterpolator(mFastOutLinearInInterpolator) 319 .setDuration(getResources().getInteger( 320 R.integer.recents_task_enter_from_app_duration)) 321 .start(); 322 } 323 } 324 325 /** Mark this task view that the user does has not interacted with the stack after a certain time. */ 326 void setNoUserInteractionState() { 327 if (mDismissButton.getVisibility() != View.VISIBLE) { 328 mDismissButton.animate().cancel(); 329 mDismissButton.setVisibility(View.VISIBLE); 330 mDismissButton.setAlpha(1f); 331 } 332 } 333 334 /** Resets the state tracking that the user has not interacted with the stack after a certain time. */ 335 void resetNoUserInteractionState() { 336 mDismissButton.setVisibility(View.INVISIBLE); 337 } 338 339 @Override 340 protected int[] onCreateDrawableState(int extraSpace) { 341 342 // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged. 343 // This is to prevent layer trashing when the view is pressed. 344 return new int[] {}; 345 } 346 347 @Override 348 protected void dispatchDraw(Canvas canvas) { 349 super.dispatchDraw(canvas); 350 351 // Draw the dim layer with the rounded corners 352 canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight(), 353 mCornerRadius, mCornerRadius, mDimLayerPaint); 354 } 355 356 @Override 357 public void onClick(View v) { 358 if (v == mIconView) { 359 // In accessibility, a single click on the focused app info button will show it 360 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 361 } else if (v == mDismissButton) { 362 TaskView tv = Utilities.findParent(this, TaskView.class); 363 tv.dismissTask(); 364 365 // Keep track of deletions by the dismiss button 366 MetricsLogger.histogram(getContext(), "overview_task_dismissed_source", 367 Constants.Metrics.DismissSourceHeaderButton); 368 } else if (v == mMoveTaskButton) { 369 TaskView tv = Utilities.findParent(this, TaskView.class); 370 Rect bounds = mMoveTaskTargetStackId == FREEFORM_WORKSPACE_STACK_ID 371 ? new Rect(mTaskViewRect) 372 : new Rect(); 373 EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, bounds, 374 mMoveTaskTargetStackId, false)); 375 } 376 } 377 378 @Override 379 public boolean onLongClick(View v) { 380 if (v == mIconView) { 381 EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); 382 return true; 383 } 384 return false; 385 } 386} 387