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.graphics.Canvas;
     28 import android.graphics.Color;
     29 import android.graphics.drawable.ColorDrawable;
     30 import android.graphics.drawable.Drawable;
     31 import android.graphics.drawable.GradientDrawable;
     32 import android.graphics.drawable.RippleDrawable;
     33 import android.graphics.Outline;
     34 import android.graphics.Paint;
     35 import android.graphics.PorterDuff;
     36 import android.graphics.PorterDuffColorFilter;
     37 import android.graphics.PorterDuffXfermode;
     38 import android.graphics.Rect;
     39 import android.util.AttributeSet;
     40 import android.view.View;
     41 import android.view.ViewOutlineProvider;
     42 import android.widget.FrameLayout;
     43 import android.widget.ImageView;
     44 import android.widget.TextView;
     45 import com.android.systemui.R;
     46 import com.android.systemui.recents.Constants;
     47 import com.android.systemui.recents.RecentsConfiguration;
     48 import com.android.systemui.recents.misc.SystemServicesProxy;
     49 import com.android.systemui.recents.misc.Utilities;
     50 import com.android.systemui.recents.model.RecentsTaskLoader;
     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     private SystemServicesProxy mSsp;
     59 
     60     // Header views
     61     ImageView mMoveTaskButton;
     62     ImageView mDismissButton;
     63     ImageView mApplicationIcon;
     64     TextView mActivityDescription;
     65 
     66     // Header drawables
     67     boolean mCurrentPrimaryColorIsDark;
     68     int mCurrentPrimaryColor;
     69     int mBackgroundColor;
     70     Drawable mLightDismissDrawable;
     71     Drawable mDarkDismissDrawable;
     72     RippleDrawable mBackground;
     73     GradientDrawable mBackgroundColorDrawable;
     74     AnimatorSet mFocusAnimator;
     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     PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
     83 
     84     boolean mLayersDisabled;
     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         mConfig = RecentsConfiguration.getInstance();
    101         mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
    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 
    117         // Configure the highlight paint
    118         if (sHighlightPaint == null) {
    119             sHighlightPaint = new Paint();
    120             sHighlightPaint.setStyle(Paint.Style.STROKE);
    121             sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx);
    122             sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor);
    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         mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
    132         mActivityDescription = (TextView) findViewById(R.id.activity_description);
    133         mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
    134         mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
    135 
    136         // Hide the backgrounds if they are ripple drawables
    137         if (!Constants.DebugFlags.App.EnableTaskFiltering) {
    138             if (mApplicationIcon.getBackground() instanceof RippleDrawable) {
    139                 mApplicationIcon.setBackground(null);
    140             }
    141         }
    142 
    143         mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable
    144                 .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     @Override
    155     protected void onDraw(Canvas canvas) {
    156         // Draw the highlight at the top edge (but put the bottom edge just out of view)
    157         float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f);
    158         float radius = mConfig.taskViewRoundedCornerRadiusPx;
    159         int count = canvas.save(Canvas.CLIP_SAVE_FLAG);
    160         canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
    161         canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
    162                 getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
    163         canvas.restoreToCount(count);
    164     }
    165 
    166     @Override
    167     public boolean hasOverlappingRendering() {
    168         return false;
    169     }
    170 
    171     /**
    172      * Sets the dim alpha, only used when we are not using hardware layers.
    173      * (see RecentsConfiguration.useHardwareLayers)
    174      */
    175     void setDimAlpha(int alpha) {
    176         mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0));
    177         mDimLayerPaint.setColorFilter(mDimColorFilter);
    178         if (!mLayersDisabled) {
    179             setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
    180         }
    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         if (!mActivityDescription.getText().toString().equals(t.activityLabel)) {
    199             mActivityDescription.setText(t.activityLabel);
    200         }
    201         mActivityDescription.setContentDescription(t.contentDescription);
    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(String.format(mDismissContentDescription,
    217                 t.contentDescription));
    218         mMoveTaskButton.setVisibility((mConfig.multiStackEnabled) ? View.VISIBLE : View.INVISIBLE);
    219         if (mConfig.multiStackEnabled) {
    220             updateResizeTaskBarIcon(t);
    221         }
    222     }
    223 
    224     /** Updates the resize task bar button. */
    225     void updateResizeTaskBarIcon(Task t) {
    226         Rect display = mSsp.getWindowRect();
    227         Rect taskRect = mSsp.getTaskBounds(t.key.stackId);
    228         int resId = R.drawable.star;
    229         if (display.equals(taskRect) || taskRect.isEmpty()) {
    230             resId = R.drawable.vector_drawable_place_fullscreen;
    231         } else {
    232             boolean top = display.top == taskRect.top;
    233             boolean bottom = display.bottom == taskRect.bottom;
    234             boolean left = display.left == taskRect.left;
    235             boolean right = display.right == taskRect.right;
    236             if (top && bottom && left) {
    237                 resId = R.drawable.vector_drawable_place_left;
    238             } else if (top && bottom && right) {
    239                 resId = R.drawable.vector_drawable_place_right;
    240             } else if (top && left && right) {
    241                 resId = R.drawable.vector_drawable_place_top;
    242             } else if (bottom && left && right) {
    243                 resId = R.drawable.vector_drawable_place_bottom;
    244             } else if (top && right) {
    245                 resId = R.drawable.vector_drawable_place_top_right;
    246             } else if (top && left) {
    247                 resId = R.drawable.vector_drawable_place_top_left;
    248             } else if (bottom && right) {
    249                 resId = R.drawable.vector_drawable_place_bottom_right;
    250             } else if (bottom && left) {
    251                 resId = R.drawable.vector_drawable_place_bottom_left;
    252             }
    253         }
    254         mMoveTaskButton.setImageResource(resId);
    255     }
    256 
    257     /** Unbinds the bar view from the task */
    258     void unbindFromTask() {
    259         mApplicationIcon.setImageDrawable(null);
    260     }
    261 
    262     /** Animates this task bar dismiss button when launching a task. */
    263     void startLaunchTaskDismissAnimation() {
    264         if (mDismissButton.getVisibility() == View.VISIBLE) {
    265             mDismissButton.animate().cancel();
    266             mDismissButton.animate()
    267                     .alpha(0f)
    268                     .setStartDelay(0)
    269                     .setInterpolator(mConfig.fastOutSlowInInterpolator)
    270                     .setDuration(mConfig.taskViewExitToAppDuration)
    271                     .start();
    272         }
    273     }
    274 
    275     /** Animates this task bar if the user does not interact with the stack after a certain time. */
    276     void startNoUserInteractionAnimation() {
    277         if (mDismissButton.getVisibility() != View.VISIBLE) {
    278             mDismissButton.setVisibility(View.VISIBLE);
    279             mDismissButton.setAlpha(0f);
    280             mDismissButton.animate()
    281                     .alpha(1f)
    282                     .setStartDelay(0)
    283                     .setInterpolator(mConfig.fastOutLinearInInterpolator)
    284                     .setDuration(mConfig.taskViewEnterFromAppDuration)
    285                     .start();
    286         }
    287     }
    288 
    289     /** Mark this task view that the user does has not interacted with the stack after a certain time. */
    290     void setNoUserInteractionState() {
    291         if (mDismissButton.getVisibility() != View.VISIBLE) {
    292             mDismissButton.animate().cancel();
    293             mDismissButton.setVisibility(View.VISIBLE);
    294             mDismissButton.setAlpha(1f);
    295         }
    296     }
    297 
    298     /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
    299     void resetNoUserInteractionState() {
    300         mDismissButton.setVisibility(View.INVISIBLE);
    301     }
    302 
    303     @Override
    304     protected int[] onCreateDrawableState(int extraSpace) {
    305 
    306         // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
    307         // This is to prevent layer trashing when the view is pressed.
    308         return new int[] {};
    309     }
    310 
    311     @Override
    312     protected void dispatchDraw(Canvas canvas) {
    313         super.dispatchDraw(canvas);
    314         if (mLayersDisabled) {
    315             mLayersDisabled = false;
    316             postOnAnimation(new Runnable() {
    317                 @Override
    318                 public void run() {
    319                     mLayersDisabled = false;
    320                     setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
    321                 }
    322             });
    323         }
    324     }
    325 
    326     public void disableLayersForOneFrame() {
    327         mLayersDisabled = true;
    328 
    329         // Disable layer for a frame so we can draw our first frame faster.
    330         setLayerType(LAYER_TYPE_NONE, null);
    331     }
    332 
    333     /** Notifies the associated TaskView has been focused. */
    334     void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
    335         // If we are not animating the visible state, just return
    336         if (!animateFocusedState) return;
    337 
    338         boolean isRunning = false;
    339         if (mFocusAnimator != null) {
    340             isRunning = mFocusAnimator.isRunning();
    341             Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
    342         }
    343 
    344         if (focused) {
    345             int currentColor = mBackgroundColor;
    346             int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
    347             int[][] states = new int[][] {
    348                     new int[] {},
    349                     new int[] { android.R.attr.state_enabled },
    350                     new int[] { android.R.attr.state_pressed }
    351             };
    352             int[] newStates = new int[]{
    353                     0,
    354                     android.R.attr.state_enabled,
    355                     android.R.attr.state_pressed
    356             };
    357             int[] colors = new int[] {
    358                     currentColor,
    359                     secondaryColor,
    360                     secondaryColor
    361             };
    362             mBackground.setColor(new ColorStateList(states, colors));
    363             mBackground.setState(newStates);
    364             // Pulse the background color
    365             int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
    366             ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
    367                     currentColor, lightPrimaryColor);
    368             backgroundColor.addListener(new AnimatorListenerAdapter() {
    369                 @Override
    370                 public void onAnimationStart(Animator animation) {
    371                     mBackground.setState(new int[]{});
    372                 }
    373             });
    374             backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    375                 @Override
    376                 public void onAnimationUpdate(ValueAnimator animation) {
    377                     int color = (int) animation.getAnimatedValue();
    378                     mBackgroundColorDrawable.setColor(color);
    379                     mBackgroundColor = color;
    380                 }
    381             });
    382             backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
    383             backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
    384             // Pulse the translation
    385             ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
    386             translation.setRepeatCount(ValueAnimator.INFINITE);
    387             translation.setRepeatMode(ValueAnimator.REVERSE);
    388 
    389             mFocusAnimator = new AnimatorSet();
    390             mFocusAnimator.playTogether(backgroundColor, translation);
    391             mFocusAnimator.setStartDelay(150);
    392             mFocusAnimator.setDuration(750);
    393             mFocusAnimator.start();
    394         } else {
    395             if (isRunning) {
    396                 // Restore the background color
    397                 int currentColor = mBackgroundColor;
    398                 ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
    399                         currentColor, mCurrentPrimaryColor);
    400                 backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    401                     @Override
    402                     public void onAnimationUpdate(ValueAnimator animation) {
    403                         int color = (int) animation.getAnimatedValue();
    404                         mBackgroundColorDrawable.setColor(color);
    405                         mBackgroundColor = color;
    406                     }
    407                 });
    408                 // Restore the translation
    409                 ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
    410 
    411                 mFocusAnimator = new AnimatorSet();
    412                 mFocusAnimator.playTogether(backgroundColor, translation);
    413                 mFocusAnimator.setDuration(150);
    414                 mFocusAnimator.start();
    415             } else {
    416                 mBackground.setState(new int[] {});
    417                 setTranslationZ(0f);
    418             }
    419         }
    420     }
    421 }
    422