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