Home | History | Annotate | Download | only in cards
      1 /*
      2  * Copyright (C) 2016 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.car.cluster.sample.cards;
     18 
     19 import android.content.Context;
     20 import android.graphics.Bitmap;
     21 import android.graphics.Bitmap.Config;
     22 import android.graphics.Canvas;
     23 import android.graphics.Color;
     24 import android.graphics.Paint;
     25 import android.graphics.Paint.Style;
     26 import android.graphics.PorterDuff.Mode;
     27 import android.graphics.PorterDuffXfermode;
     28 import android.os.Handler;
     29 import android.os.Looper;
     30 import android.os.SystemClock;
     31 import android.util.AttributeSet;
     32 import android.util.Log;
     33 import android.view.ViewGroup;
     34 import android.view.animation.DecelerateInterpolator;
     35 import android.widget.FrameLayout;
     36 import android.widget.ImageView;
     37 import android.widget.ImageView.ScaleType;
     38 import android.widget.ViewSwitcher;
     39 
     40 import com.android.car.cluster.sample.DebugUtil;
     41 import com.android.car.cluster.sample.R;
     42 
     43 /**
     44  * View that responsible for displaying cards in instrument cluster (including media, phone and
     45  * maps).
     46  */
     47 public class CardView extends FrameLayout implements Comparable<CardView> {
     48 
     49     private final static String TAG = DebugUtil.getTag(CardView.class);
     50 
     51     protected final static long SHOW_ANIMATION_DURATION = 1000 * DebugUtil.ANIMATION_FACTOR;
     52 
     53     protected ImageView mBackgroundImage;
     54     protected ViewGroup mDetailsPanel;
     55     protected ViewSwitcher mLeftIconSwitcher;
     56     protected ViewSwitcher mRightIconSwitcher;
     57 
     58     private Bitmap mBitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888);
     59 
     60     protected long mLastUpdated = SystemClock.elapsedRealtime();
     61 
     62     private Canvas mCanvas = new Canvas();
     63     private final Paint mBackgroundCirclePaint;
     64 
     65     private final static Handler mHandler = new Handler(Looper.getMainLooper());
     66 
     67     protected final int mCardWidth;
     68     protected final int mCardHeight;
     69     protected final int mCurveRadius;
     70     protected final int mTextPanelWidth;
     71     protected final float mLeftPadding;
     72     protected final float mIconSize;
     73     protected final float mIconsOverlap;
     74 
     75     protected int mPriority = 9;
     76 
     77     protected final static int PRIORITY_GARBAGE = 10;
     78 
     79     protected final static int PRIORITY_CALL_INCOMING = 3;
     80     protected final static int PRIORITY_CALL_ACTIVE = 5;
     81     protected final static int PRIORITY_MEDIA_NOTIFICATION = 3;
     82     protected final static int PRIORITY_MEDIA_ACTIVE = 6;
     83     protected final static int PRIORITY_WEATHER_CARD = 9;
     84     protected final static int PRIORITY_NAVIGATION_ACTIVE = 3;
     85     protected final static int PRIORITY_HANGOUT_NOTIFICATION = 3;
     86 
     87     @CardType
     88     private int mCardType;
     89     private final PriorityChangedListener mPriorityChangedListener;
     90 
     91     public @interface CardType {
     92         int WEATHER = 1;
     93         int MEDIA = 2;
     94         int PHONE_CALL = 3;
     95         int NAV = 4;
     96         int HANGOUT = 5;
     97     }
     98 
     99     public CardView(Context context, @CardType int cardType, PriorityChangedListener listener) {
    100         this(context, null, cardType, listener);
    101     }
    102 
    103     public CardView(Context context, AttributeSet attrs, @CardType int cardType,
    104             PriorityChangedListener listener) {
    105         super(context, attrs);
    106         mPriorityChangedListener = listener;
    107         if (DebugUtil.DEBUG) {
    108             Log.d(TAG, "ctor");
    109         }
    110         mCardType = cardType;
    111 
    112         setWillNotDraw(false);  // This will trigger onDraw method.
    113 
    114         mBackgroundCirclePaint = createBackgroundCirclePaint();
    115         mCardWidth = (int)getResources().getDimension(R.dimen.card_width);
    116         mCardHeight = (int) getResources().getDimension(R.dimen.card_height);
    117         mTextPanelWidth = (int)getResources().getDimension(R.dimen.card_message_panel_width);
    118         mLeftPadding = getResources().getDimension(R.dimen.card_content_left_padding);
    119         mIconSize = getResources().getDimension(R.dimen.card_icon_size);
    120         mIconsOverlap = mIconSize - mLeftPadding;
    121         mCurveRadius = (int)(mCardWidth * 0.643f);
    122 
    123         inflate(getContext(), R.layout.card_view, this);
    124 
    125         if (this.isInEditMode()) {
    126             return;
    127         }
    128 
    129         mLeftIconSwitcher = viewById(R.id.left_icon_switcher);
    130         mRightIconSwitcher = viewById(R.id.right_icon_switcher);
    131         mBackgroundImage = viewById(R.id.image_background);
    132 
    133         init();
    134     }
    135 
    136     protected void inflate(int layoutId) {
    137         inflate(getContext(), layoutId, (ViewGroup) getChildAt(0));
    138     }
    139 
    140     protected void init() {
    141     }
    142 
    143     @CardType
    144     public int getCardType() {
    145         return mCardType;
    146     }
    147 
    148     public void setLeftIcon(Bitmap bitmap) {
    149         setLeftIcon(bitmap, false /* animated */);
    150     }
    151 
    152     public void setLeftIcon(Bitmap bitmap, boolean animated) {
    153         if (DebugUtil.DEBUG) {
    154             Log.d(TAG, "setLeftIcon, bitmap: " + bitmap);
    155         }
    156         switchImageViewBitmpa(bitmap, mLeftIconSwitcher, animated);
    157     }
    158 
    159 
    160     public void setRightIcon(Bitmap bitmap) {
    161         setRightIcon(bitmap, false /* animated */);
    162     }
    163 
    164     /**
    165      * @param bitmap if null, the image won't be displayed and message panel will be placed
    166      * accordingly.
    167      */
    168     public void setRightIcon(Bitmap bitmap, boolean animated) {
    169         if (DebugUtil.DEBUG) {
    170             Log.d(TAG, "setRightIcon, bitmap: " + bitmap);
    171         }
    172         if (bitmap == null && mRightIconSwitcher.getVisibility() == VISIBLE) {
    173             mRightIconSwitcher.setVisibility(GONE);
    174         } else if (bitmap != null && mRightIconSwitcher.getVisibility() == GONE) {
    175             mRightIconSwitcher.setVisibility(VISIBLE);
    176         }
    177 
    178         switchImageViewBitmpa(bitmap, mRightIconSwitcher, animated);
    179     }
    180 
    181     private void switchImageViewBitmpa(Bitmap bitmap, ViewSwitcher switcher, boolean animated) {
    182         ImageView icon = (ImageView) (animated
    183                 ? switcher.getNextView() : switcher.getCurrentView());
    184 
    185         icon.setBackground(null);
    186         icon.setImageBitmap(bitmap);
    187 
    188         if (animated) {
    189             switcher.showNext();
    190         }
    191     }
    192 
    193     /** Called by {@code ClusterView} when card should go away using unreveal animation */
    194     public void onPlayUnrevealAnimation() {
    195         if (DebugUtil.DEBUG) {
    196             Log.d(TAG, "onPlayUnrevealAnimation");
    197         }
    198     }
    199 
    200     public void onPlayRevealAnimation() {
    201         if (DebugUtil.DEBUG) {
    202             Log.d(TAG, "onPlayRevealAnimation");
    203         }
    204 
    205         if (mLeftIconSwitcher.getVisibility() == VISIBLE) {
    206             mLeftIconSwitcher.setTranslationX(mCardWidth / 2);
    207             mLeftIconSwitcher.animate()
    208                     .translationX(getLeftIconTargetX())
    209                     .setDuration(SHOW_ANIMATION_DURATION)
    210                     .setInterpolator(getDecelerateInterpolator());
    211         }
    212 
    213         if (mRightIconSwitcher.getVisibility() == VISIBLE) {
    214             mRightIconSwitcher.setTranslationX( mCardWidth - mTextPanelWidth / 2 - mIconSize);
    215             mRightIconSwitcher.animate()
    216                     .translationX(getRightIconTargetX())
    217                     .setDuration(SHOW_ANIMATION_DURATION)
    218                     .setInterpolator(getDecelerateInterpolator());
    219         }
    220 
    221         showDetailsPanelAnimation(getDetailsPanelTargetX());
    222     }
    223 
    224     protected float getLeftIconTargetX() {
    225         return mLeftIconSwitcher.getVisibility() == VISIBLE ? mLeftPadding : 0;
    226     }
    227 
    228     protected float getRightIconTargetX() {
    229         if (mRightIconSwitcher.getVisibility() != VISIBLE) {
    230             return 0;
    231         }
    232         float x = mLeftPadding;
    233         if (mLeftIconSwitcher.getVisibility() == VISIBLE) {
    234             x += mIconsOverlap;
    235         }
    236         return  x;
    237     }
    238 
    239     protected float getDetailsPanelTargetX() {
    240         return Math.max(getLeftIconTargetX(), getRightIconTargetX()) + mIconSize + mLeftPadding;
    241     }
    242 
    243     protected void showDetailsPanelAnimation(float textX) {
    244         if (mDetailsPanel != null) {
    245             mDetailsPanel.setTranslationX(mCardWidth - mTextPanelWidth / 2);
    246             mDetailsPanel.animate()
    247                     .translationX(textX)
    248                     .setDuration(SHOW_ANIMATION_DURATION)
    249                     .setInterpolator(getDecelerateInterpolator());
    250         }
    251     }
    252 
    253     protected DecelerateInterpolator getDecelerateInterpolator() {
    254         return new DecelerateInterpolator(2f);
    255     }
    256 
    257     public void setBackground(Bitmap bmpPicture, int backgroundColor) {
    258         Bitmap bmpBackground = Bitmap.createBitmap(
    259                 mCardWidth,
    260                 (int)getResources().getDimension(R.dimen.card_height),
    261                 Config.ARGB_8888);
    262         Canvas canvas = new Canvas(bmpBackground);
    263         //clear previous drawings
    264         canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
    265 
    266         Paint p = new Paint();
    267         p.setColor(backgroundColor);
    268         p.setAntiAlias(true);
    269         p.setStyle(Style.FILL);
    270         // Draw curved background.
    271         canvas.drawCircle(mCurveRadius, (int)getResources().getDimension(
    272                 R.dimen.card_height) / 2,
    273                 mCurveRadius, p);
    274 
    275         // Draw image respecting curved background.
    276         p.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
    277         float x = canvas.getWidth() - bmpPicture.getWidth();
    278         float y = canvas.getHeight() - bmpPicture.getHeight();
    279         if (y < 0) {
    280             y = y / 2; // Center image if it is not fitting.
    281         }
    282         canvas.drawBitmap(bmpPicture, x, y, p);
    283 
    284         mBackgroundImage.setScaleType(ScaleType.CENTER_CROP);
    285         mBackgroundImage.setImageBitmap(bmpBackground);
    286         if (mBackgroundImage.getVisibility() != VISIBLE) {
    287             mBackgroundImage.setVisibility(VISIBLE);
    288         }
    289     }
    290 
    291     public void setBackgroundColor(int color) {
    292         mBackgroundCirclePaint.setColor(color);
    293     }
    294 
    295     private Paint createBackgroundCirclePaint() {
    296         Paint p = new Paint();
    297         p.setAntiAlias(true);
    298         p.setXfermode(new PorterDuffXfermode(Mode.ADD));
    299         p.setStyle(Style.FILL);
    300         p.setColor(getResources().getColor(R.color.cluster_active_area_background, null));
    301         return p;
    302     }
    303 
    304     @Override
    305     protected void onDraw(Canvas canvas) {
    306         super.onDraw(canvas);
    307 
    308         if(mBitmap.isRecycled() || mBitmap.getWidth() != canvas.getWidth()
    309                 || mBitmap.getHeight() != canvas.getHeight()) {
    310             Log.d(TAG, "creating bitmap...");
    311             mBitmap.recycle();
    312             mBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Config.ARGB_8888);
    313             mCanvas.setBitmap(mBitmap);
    314         }
    315 
    316         //clear previous drawings
    317         mCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
    318 
    319         mCanvas.drawCircle(mCurveRadius, getHeight() / 2, mCurveRadius, mBackgroundCirclePaint);
    320 
    321         canvas.drawBitmap(mBitmap, 0, 0, null);
    322     }
    323 
    324     public int getIconSize() {
    325         return (int)mIconSize;
    326     }
    327 
    328     public static void runDelayed(long delay, final Runnable task) {
    329         mHandler.postDelayed(task, delay);
    330     }
    331 
    332     public void setPriority(int priority) {
    333         mPriority = priority;
    334         mPriorityChangedListener.onPriorityChanged(this, priority);
    335     }
    336 
    337     /**
    338      * Should return number from 0 to 9. 0 - is most important, 9 is less important.
    339      */
    340     public int getPriority() {
    341         return mPriority;
    342     }
    343 
    344     public boolean isGarbage() {
    345         return getPriority() == PRIORITY_GARBAGE;
    346     }
    347 
    348     public void removeGracefully() {
    349         setPriority(PRIORITY_GARBAGE);
    350     }
    351 
    352     @Override
    353     public int compareTo(CardView another) {
    354         int res = this.getPriority() - another.getPriority();
    355         if (res == 0) {
    356             // If objects have the same priorities, check the last time they were updated.
    357             res = this.mLastUpdated > another.mLastUpdated ? -1 : 1;
    358             if (DebugUtil.DEBUG) {
    359                 Log.d(TAG, "Found card with the same priority: " + this + " and " + another + ","
    360                         + "this.mLastUpdated: " + mLastUpdated
    361                         + ", another.mLastUpdated:" + another.mLastUpdated + ", res: " + res);
    362 
    363             }
    364         }
    365         return res;
    366     }
    367 
    368     @SuppressWarnings("unchecked")
    369     protected <E> E viewById(int id) {
    370         return (E) findViewById(id);
    371     }
    372 
    373     public interface PriorityChangedListener {
    374         void onPriorityChanged(CardView cardView, int newPriority);
    375     }
    376 }
    377