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