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.util.AttributeSet;
     40 import android.view.MotionEvent;
     41 import android.view.View;
     42 import android.view.ViewOutlineProvider;
     43 import android.widget.FrameLayout;
     44 import android.widget.ImageView;
     45 import android.widget.TextView;
     46 import com.android.systemui.R;
     47 import com.android.systemui.recents.Constants;
     48 import com.android.systemui.recents.RecentsConfiguration;
     49 import com.android.systemui.recents.misc.Utilities;
     50 import com.android.systemui.recents.model.Task;
     51 
     52 
     53 /* The task bar view */
     54 public class TaskViewHeader extends FrameLayout {
     55 
     56     RecentsConfiguration mConfig;
     57 
     58     // Header views
     59     ImageView mDismissButton;
     60     ImageView mApplicationIcon;
     61     TextView mActivityDescription;
     62 
     63     // Header drawables
     64     boolean mCurrentPrimaryColorIsDark;
     65     int mCurrentPrimaryColor;
     66     int mBackgroundColor;
     67     Drawable mLightDismissDrawable;
     68     Drawable mDarkDismissDrawable;
     69     RippleDrawable mBackground;
     70     GradientDrawable mBackgroundColorDrawable;
     71     AnimatorSet mFocusAnimator;
     72     String mDismissContentDescription;
     73 
     74     // Static highlight that we draw at the top of each view
     75     static Paint sHighlightPaint;
     76 
     77     // Header dim, which is only used when task view hardware layers are not used
     78     Paint mDimLayerPaint = new Paint();
     79     PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
     80 
     81     public TaskViewHeader(Context context) {
     82         this(context, null);
     83     }
     84 
     85     public TaskViewHeader(Context context, AttributeSet attrs) {
     86         this(context, attrs, 0);
     87     }
     88 
     89     public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
     90         this(context, attrs, defStyleAttr, 0);
     91     }
     92 
     93     public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
     94         super(context, attrs, defStyleAttr, defStyleRes);
     95         mConfig = RecentsConfiguration.getInstance();
     96         setWillNotDraw(false);
     97         setClipToOutline(true);
     98         setOutlineProvider(new ViewOutlineProvider() {
     99             @Override
    100             public void getOutline(View view, Outline outline) {
    101                 outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
    102             }
    103         });
    104 
    105         // Load the dismiss resources
    106         Resources res = context.getResources();
    107         mLightDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_light);
    108         mDarkDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_dark);
    109         mDismissContentDescription =
    110                 res.getString(R.string.accessibility_recents_item_will_be_dismissed);
    111 
    112         // Configure the highlight paint
    113         if (sHighlightPaint == null) {
    114             sHighlightPaint = new Paint();
    115             sHighlightPaint.setStyle(Paint.Style.STROKE);
    116             sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx);
    117             sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor);
    118             sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
    119             sHighlightPaint.setAntiAlias(true);
    120         }
    121     }
    122 
    123     @Override
    124     public boolean onTouchEvent(MotionEvent event) {
    125         // We ignore taps on the task bar except on the filter and dismiss buttons
    126         if (!Constants.DebugFlags.App.EnableTaskBarTouchEvents) return true;
    127 
    128         return super.onTouchEvent(event);
    129     }
    130 
    131     @Override
    132     protected void onFinishInflate() {
    133         // Initialize the icon and description views
    134         mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
    135         mActivityDescription = (TextView) findViewById(R.id.activity_description);
    136         mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
    137 
    138         // Hide the backgrounds if they are ripple drawables
    139         if (!Constants.DebugFlags.App.EnableTaskFiltering) {
    140             if (mApplicationIcon.getBackground() instanceof RippleDrawable) {
    141                 mApplicationIcon.setBackground(null);
    142             }
    143         }
    144 
    145         mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable
    146                 .recents_task_view_header_bg_color);
    147         // Copy the ripple drawable since we are going to be manipulating it
    148         mBackground = (RippleDrawable)
    149                 getContext().getDrawable(R.drawable.recents_task_view_header_bg);
    150         mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable();
    151         mBackground.setColor(ColorStateList.valueOf(0));
    152         mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColorDrawable);
    153         setBackground(mBackground);
    154     }
    155 
    156     @Override
    157     protected void onDraw(Canvas canvas) {
    158         // Draw the highlight at the top edge (but put the bottom edge just out of view)
    159         float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f);
    160         float radius = mConfig.taskViewRoundedCornerRadiusPx;
    161         int count = canvas.save(Canvas.CLIP_SAVE_FLAG);
    162         canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
    163         canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
    164                 getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
    165         canvas.restoreToCount(count);
    166     }
    167 
    168     @Override
    169     public boolean hasOverlappingRendering() {
    170         return false;
    171     }
    172 
    173     /**
    174      * Sets the dim alpha, only used when we are not using hardware layers.
    175      * (see RecentsConfiguration.useHardwareLayers)
    176      */
    177     void setDimAlpha(int alpha) {
    178         mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0));
    179         mDimLayerPaint.setColorFilter(mDimColorFilter);
    180         setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
    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         mApplicationIcon.setContentDescription(t.activityLabel);
    199         if (!mActivityDescription.getText().toString().equals(t.activityLabel)) {
    200             mActivityDescription.setText(t.activityLabel);
    201         }
    202         // Try and apply the system ui tint
    203         int existingBgColor = (getBackground() instanceof ColorDrawable) ?
    204                 ((ColorDrawable) getBackground()).getColor() : 0;
    205         if (existingBgColor != t.colorPrimary) {
    206             mBackgroundColorDrawable.setColor(t.colorPrimary);
    207             mBackgroundColor = t.colorPrimary;
    208         }
    209         mCurrentPrimaryColor = t.colorPrimary;
    210         mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
    211         mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
    212                 mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
    213         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
    214                 mLightDismissDrawable : mDarkDismissDrawable);
    215         mDismissButton.setContentDescription(String.format(mDismissContentDescription,
    216                 t.activityLabel));
    217     }
    218 
    219     /** Unbinds the bar view from the task */
    220     void unbindFromTask() {
    221         mApplicationIcon.setImageDrawable(null);
    222     }
    223 
    224     /** Animates this task bar dismiss button when launching a task. */
    225     void startLaunchTaskDismissAnimation() {
    226         if (mDismissButton.getVisibility() == View.VISIBLE) {
    227             mDismissButton.animate().cancel();
    228             mDismissButton.animate()
    229                     .alpha(0f)
    230                     .setStartDelay(0)
    231                     .setInterpolator(mConfig.fastOutSlowInInterpolator)
    232                     .setDuration(mConfig.taskViewExitToAppDuration)
    233                     .withLayer()
    234                     .start();
    235         }
    236     }
    237 
    238     /** Animates this task bar if the user does not interact with the stack after a certain time. */
    239     void startNoUserInteractionAnimation() {
    240         if (mDismissButton.getVisibility() != View.VISIBLE) {
    241             mDismissButton.setVisibility(View.VISIBLE);
    242             mDismissButton.setAlpha(0f);
    243             mDismissButton.animate()
    244                     .alpha(1f)
    245                     .setStartDelay(0)
    246                     .setInterpolator(mConfig.fastOutLinearInInterpolator)
    247                     .setDuration(mConfig.taskViewEnterFromAppDuration)
    248                     .withLayer()
    249                     .start();
    250         }
    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     /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
    263     void resetNoUserInteractionState() {
    264         mDismissButton.setVisibility(View.INVISIBLE);
    265     }
    266 
    267     @Override
    268     protected int[] onCreateDrawableState(int extraSpace) {
    269 
    270         // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
    271         // This is to prevent layer trashing when the view is pressed.
    272         return new int[] {};
    273     }
    274 
    275     /** Notifies the associated TaskView has been focused. */
    276     void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
    277         // If we are not animating the visible state, just return
    278         if (!animateFocusedState) return;
    279 
    280         boolean isRunning = false;
    281         if (mFocusAnimator != null) {
    282             isRunning = mFocusAnimator.isRunning();
    283             Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
    284         }
    285 
    286         if (focused) {
    287             int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
    288             int[][] states = new int[][] {
    289                     new int[] { android.R.attr.state_enabled },
    290                     new int[] { android.R.attr.state_pressed }
    291             };
    292             int[] newStates = new int[]{
    293                     android.R.attr.state_enabled,
    294                     android.R.attr.state_pressed
    295             };
    296             int[] colors = new int[] {
    297                     secondaryColor,
    298                     secondaryColor
    299             };
    300             mBackground.setColor(new ColorStateList(states, colors));
    301             mBackground.setState(newStates);
    302             // Pulse the background color
    303             int currentColor = mBackgroundColor;
    304             int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
    305             ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
    306                     currentColor, lightPrimaryColor);
    307             backgroundColor.addListener(new AnimatorListenerAdapter() {
    308                 @Override
    309                 public void onAnimationStart(Animator animation) {
    310                     mBackground.setState(new int[]{});
    311                 }
    312             });
    313             backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    314                 @Override
    315                 public void onAnimationUpdate(ValueAnimator animation) {
    316                     int color = (int) animation.getAnimatedValue();
    317                     mBackgroundColorDrawable.setColor(color);
    318                     mBackgroundColor = color;
    319                 }
    320             });
    321             backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
    322             backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
    323             // Pulse the translation
    324             ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
    325             translation.setRepeatCount(ValueAnimator.INFINITE);
    326             translation.setRepeatMode(ValueAnimator.REVERSE);
    327 
    328             mFocusAnimator = new AnimatorSet();
    329             mFocusAnimator.playTogether(backgroundColor, translation);
    330             mFocusAnimator.setStartDelay(750);
    331             mFocusAnimator.setDuration(750);
    332             mFocusAnimator.start();
    333         } else {
    334             if (isRunning) {
    335                 // Restore the background color
    336                 int currentColor = mBackgroundColor;
    337                 ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
    338                         currentColor, mCurrentPrimaryColor);
    339                 backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    340                     @Override
    341                     public void onAnimationUpdate(ValueAnimator animation) {
    342                         int color = (int) animation.getAnimatedValue();
    343                         mBackgroundColorDrawable.setColor(color);
    344                         mBackgroundColor = color;
    345                     }
    346                 });
    347                 // Restore the translation
    348                 ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
    349 
    350                 mFocusAnimator = new AnimatorSet();
    351                 mFocusAnimator.playTogether(backgroundColor, translation);
    352                 mFocusAnimator.setDuration(150);
    353                 mFocusAnimator.start();
    354             } else {
    355                 mBackground.setState(new int[] {});
    356                 setTranslationZ(0f);
    357             }
    358         }
    359     }
    360 }
    361