Home | History | Annotate | Download | only in launcher3
      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.launcher3;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.res.Resources.Theme;
     23 import android.graphics.Bitmap;
     24 import android.graphics.Canvas;
     25 import android.graphics.Color;
     26 import android.graphics.PorterDuff;
     27 import android.graphics.Rect;
     28 import android.graphics.drawable.Drawable;
     29 import android.os.Build;
     30 import android.os.Bundle;
     31 import android.text.Layout;
     32 import android.text.StaticLayout;
     33 import android.text.TextPaint;
     34 import android.util.TypedValue;
     35 import android.view.View;
     36 import android.view.View.OnClickListener;
     37 
     38 public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener {
     39     private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
     40     private static final float MIN_SATUNATION = 0.7f;
     41 
     42     private static Theme sPreloaderTheme;
     43 
     44     private final Rect mRect = new Rect();
     45     private View mDefaultView;
     46     private OnClickListener mClickListener;
     47     private final LauncherAppWidgetInfo mInfo;
     48     private final int mStartState;
     49     private final Intent mIconLookupIntent;
     50     private final boolean mDisabledForSafeMode;
     51     private Launcher mLauncher;
     52 
     53     private Bitmap mIcon;
     54 
     55     private Drawable mCenterDrawable;
     56     private Drawable mSettingIconDrawable;
     57 
     58     private boolean mDrawableSizeChanged;
     59 
     60     private final TextPaint mPaint;
     61     private Layout mSetupTextLayout;
     62 
     63     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     64     public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
     65             boolean disabledForSafeMode) {
     66         super(context);
     67 
     68         mLauncher = (Launcher) context;
     69         mInfo = info;
     70         mStartState = info.restoreStatus;
     71         mIconLookupIntent = new Intent().setComponent(info.providerName);
     72         mDisabledForSafeMode = disabledForSafeMode;
     73 
     74         mPaint = new TextPaint();
     75         mPaint.setColor(0xFFFFFFFF);
     76         mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
     77                 mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
     78         setBackgroundResource(R.drawable.quantum_panel_dark);
     79         setWillNotDraw(false);
     80 
     81         if (Utilities.ATLEAST_LOLLIPOP) {
     82             setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
     83         }
     84     }
     85 
     86     @Override
     87     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
     88             int maxHeight) {
     89         // No-op
     90     }
     91 
     92     @Override
     93     protected View getDefaultView() {
     94         if (mDefaultView == null) {
     95             mDefaultView = mInflater.inflate(R.layout.appwidget_not_ready, this, false);
     96             mDefaultView.setOnClickListener(this);
     97             applyState();
     98         }
     99         return mDefaultView;
    100     }
    101 
    102     @Override
    103     public void setOnClickListener(OnClickListener l) {
    104         mClickListener = l;
    105     }
    106 
    107     @Override
    108     public boolean isReinflateRequired() {
    109         // Re inflate is required any time the widget restore status changes
    110         return mStartState != mInfo.restoreStatus;
    111     }
    112 
    113     @Override
    114     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    115         super.onSizeChanged(w, h, oldw, oldh);
    116         mDrawableSizeChanged = true;
    117     }
    118 
    119     public void updateIcon(IconCache cache) {
    120         Bitmap icon = cache.getIcon(mIconLookupIntent, mInfo.user);
    121         if (mIcon == icon) {
    122             return;
    123         }
    124         mIcon = icon;
    125         if (mCenterDrawable != null) {
    126             mCenterDrawable.setCallback(null);
    127             mCenterDrawable = null;
    128         }
    129         if (mIcon != null) {
    130             // The view displays three modes,
    131             //   1) App icon in the center
    132             //   2) Preload icon in the center
    133             //   3) Setup icon in the center and app icon in the top right corner.
    134             if (mDisabledForSafeMode) {
    135                 FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
    136                 disabledIcon.setState(FastBitmapDrawable.State.DISABLED);
    137                 mCenterDrawable = disabledIcon;
    138                 mSettingIconDrawable = null;
    139             } else if (isReadyForClickSetup()) {
    140                 mCenterDrawable = new FastBitmapDrawable(mIcon);
    141                 mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
    142 
    143                 updateSettingColor();
    144             } else {
    145                 if (sPreloaderTheme == null) {
    146                     sPreloaderTheme = getResources().newTheme();
    147                     sPreloaderTheme.applyStyle(R.style.PreloadIcon, true);
    148                 }
    149 
    150                 FastBitmapDrawable drawable = mLauncher.createIconDrawable(mIcon);
    151                 mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
    152                 mCenterDrawable.setCallback(this);
    153                 mSettingIconDrawable = null;
    154                 applyState();
    155             }
    156             mDrawableSizeChanged = true;
    157         }
    158     }
    159 
    160     private void updateSettingColor() {
    161         int color = Utilities.findDominantColorByHue(mIcon, 20);
    162         // Make the dominant color bright.
    163         float[] hsv = new float[3];
    164         Color.colorToHSV(color, hsv);
    165         hsv[1] = Math.min(hsv[1], MIN_SATUNATION);
    166         hsv[2] = 1;
    167         color = Color.HSVToColor(hsv);
    168 
    169         mSettingIconDrawable.setColorFilter(color,  PorterDuff.Mode.SRC_IN);
    170     }
    171 
    172     @Override
    173     protected boolean verifyDrawable(Drawable who) {
    174         return (who == mCenterDrawable) || super.verifyDrawable(who);
    175     }
    176 
    177     public void applyState() {
    178         if (mCenterDrawable != null) {
    179             mCenterDrawable.setLevel(Math.max(mInfo.installProgress, 0));
    180         }
    181     }
    182 
    183     @Override
    184     public void onClick(View v) {
    185         // AppWidgetHostView blocks all click events on the root view. Instead handle click events
    186         // on the content and pass it along.
    187         if (mClickListener != null) {
    188             mClickListener.onClick(this);
    189         }
    190     }
    191 
    192     public boolean isReadyForClickSetup() {
    193         return (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0
    194                 && (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0;
    195     }
    196 
    197     private void updateDrawableBounds() {
    198         DeviceProfile grid = mLauncher.getDeviceProfile();
    199         int paddingTop = getPaddingTop();
    200         int paddingBottom = getPaddingBottom();
    201         int paddingLeft = getPaddingLeft();
    202         int paddingRight = getPaddingRight();
    203 
    204         int minPadding = getResources()
    205                 .getDimensionPixelSize(R.dimen.pending_widget_min_padding);
    206 
    207         int availableWidth = getWidth() - paddingLeft - paddingRight - 2 * minPadding;
    208         int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
    209 
    210         if (mSettingIconDrawable == null) {
    211             int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
    212                     ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
    213             int maxSize = grid.iconSizePx + 2 * outset;
    214             int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
    215 
    216             mRect.set(0, 0, size, size);
    217             mRect.inset(outset, outset);
    218             mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
    219             mCenterDrawable.setBounds(mRect);
    220         } else  {
    221             float iconSize = Math.max(0, Math.min(availableWidth, availableHeight));
    222 
    223             // Use twice the setting size factor, as the setting is drawn at a corner and the
    224             // icon is drawn in the center.
    225             float settingIconScaleFactor = 1 + SETUP_ICON_SIZE_FACTOR * 2;
    226             int maxSize = Math.max(availableWidth, availableHeight);
    227             if (iconSize * settingIconScaleFactor > maxSize) {
    228                 // There is an overlap
    229                 iconSize = maxSize / settingIconScaleFactor;
    230             }
    231 
    232             int actualIconSize = (int) Math.min(iconSize, grid.iconSizePx);
    233 
    234             // Icon top when we do not draw the text
    235             int iconTop = (getHeight() - actualIconSize) / 2;
    236             mSetupTextLayout = null;
    237 
    238             if (availableWidth > 0) {
    239                 // Recreate the setup text.
    240                 mSetupTextLayout = new StaticLayout(
    241                         getResources().getText(R.string.gadget_setup_text), mPaint, availableWidth,
    242                         Layout.Alignment.ALIGN_CENTER, 1, 0, true);
    243                 int textHeight = mSetupTextLayout.getHeight();
    244 
    245                 // Extra icon size due to the setting icon
    246                 float minHeightWithText = textHeight + actualIconSize * settingIconScaleFactor
    247                         + grid.iconDrawablePaddingPx;
    248 
    249                 if (minHeightWithText < availableHeight) {
    250                     // We can draw the text as well
    251                     iconTop = (getHeight() - textHeight -
    252                             grid.iconDrawablePaddingPx - actualIconSize) / 2;
    253 
    254                 } else {
    255                     // We can't draw the text. Let the iconTop be same as before.
    256                     mSetupTextLayout = null;
    257                 }
    258             }
    259 
    260             mRect.set(0, 0, actualIconSize, actualIconSize);
    261             mRect.offset((getWidth() - actualIconSize) / 2, iconTop);
    262             mCenterDrawable.setBounds(mRect);
    263 
    264             mRect.left = paddingLeft + minPadding;
    265             mRect.right = mRect.left + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
    266             mRect.top = paddingTop + minPadding;
    267             mRect.bottom = mRect.top + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
    268             mSettingIconDrawable.setBounds(mRect);
    269 
    270             if (mSetupTextLayout != null) {
    271                 // Set up position for dragging the text
    272                 mRect.left = paddingLeft + minPadding;
    273                 mRect.top = mCenterDrawable.getBounds().bottom + grid.iconDrawablePaddingPx;
    274             }
    275         }
    276     }
    277 
    278     @Override
    279     protected void onDraw(Canvas canvas) {
    280         if (mCenterDrawable == null) {
    281             // Nothing to draw
    282             return;
    283         }
    284 
    285         if (mDrawableSizeChanged) {
    286             updateDrawableBounds();
    287             mDrawableSizeChanged = false;
    288         }
    289 
    290         mCenterDrawable.draw(canvas);
    291         if (mSettingIconDrawable != null) {
    292             mSettingIconDrawable.draw(canvas);
    293         }
    294         if (mSetupTextLayout != null) {
    295             canvas.save();
    296             canvas.translate(mRect.left, mRect.top);
    297             mSetupTextLayout.draw(canvas);
    298             canvas.restore();
    299         }
    300 
    301     }
    302 }
    303