• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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)
PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info, boolean disabledForSafeMode)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
updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight)88     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
89             int maxHeight) {
90         // No-op
91     }
92 
93     @Override
getDefaultView()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
setOnClickListener(OnClickListener l)104     public void setOnClickListener(OnClickListener l) {
105         mClickListener = l;
106     }
107 
108     @Override
isReinflateRequired()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
onSizeChanged(int w, int h, int oldw, int oldh)115     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
116         super.onSizeChanged(w, h, oldw, oldh);
117         mDrawableSizeChanged = true;
118     }
119 
updateIcon(IconCache cache)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 
updateSettingColor()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
verifyDrawable(Drawable who)174     protected boolean verifyDrawable(Drawable who) {
175         return (who == mCenterDrawable) || super.verifyDrawable(who);
176     }
177 
applyState()178     public void applyState() {
179         if (mCenterDrawable != null) {
180             mCenterDrawable.setLevel(Math.max(mInfo.installProgress, 0));
181         }
182     }
183 
184     @Override
onClick(View v)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      */
isReadyForClickSetup()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 
updateDrawableBounds()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
onDraw(Canvas canvas)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