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