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