• 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.model.data.PackageItemInfo.CONVERSATIONS;
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         updateAppWidget(null);
89         setOnClickListener(ItemClickHandler.INSTANCE);
90 
91         if (info.pendingItemInfo == null) {
92             info.pendingItemInfo = new PackageItemInfo(info.providerName.getPackageName());
93             info.pendingItemInfo.user = 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(
163                             FastBitmapDrawable.getDisabledFColorFilter(/* disabledAlpha= */ 1f));
164                     mCenterDrawable = widgetCategoryIcon;
165                 }
166                 mSettingIconDrawable = null;
167             } else if (isReadyForClickSetup()) {
168                 mCenterDrawable = widgetCategoryIcon == null
169                         ? info.newIcon(getContext())
170                         : widgetCategoryIcon;
171                 mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
172                 updateSettingColor(info.bitmap.color);
173             } else {
174                 mCenterDrawable = widgetCategoryIcon == null
175                         ? newPendingIcon(getContext(), info)
176                         : widgetCategoryIcon;
177                 mSettingIconDrawable = null;
178                 applyState();
179             }
180             mCenterDrawable.setCallback(this);
181             mDrawableSizeChanged = true;
182         }
183         invalidate();
184     }
185 
updateSettingColor(int dominantColor)186     private void updateSettingColor(int dominantColor) {
187         // Make the dominant color bright.
188         float[] hsv = new float[3];
189         Color.colorToHSV(dominantColor, hsv);
190         hsv[1] = Math.min(hsv[1], MIN_SATUNATION);
191         hsv[2] = 1;
192         mSettingIconDrawable.setColorFilter(Color.HSVToColor(hsv),  PorterDuff.Mode.SRC_IN);
193     }
194 
195     @Override
verifyDrawable(Drawable who)196     protected boolean verifyDrawable(Drawable who) {
197         return (who == mCenterDrawable) || super.verifyDrawable(who);
198     }
199 
applyState()200     public void applyState() {
201         if (mCenterDrawable != null) {
202             mCenterDrawable.setLevel(Math.max(mInfo.installProgress, 0));
203         }
204     }
205 
206     @Override
onClick(View v)207     public void onClick(View v) {
208         // AppWidgetHostView blocks all click events on the root view. Instead handle click events
209         // on the content and pass it along.
210         if (mClickListener != null) {
211             mClickListener.onClick(this);
212         }
213     }
214 
215     /**
216      * A pending widget is ready for setup after the provider is installed and
217      *   1) Widget id is not valid: the widget id is not yet bound to the provider, probably
218      *                              because the launcher doesn't have appropriate permissions.
219      *                              Note that we would still have an allocated id as that does not
220      *                              require any permissions and can be done during view inflation.
221      *   2) UI is not ready: the id is valid and the bound. But the widget has a configure activity
222      *                       which needs to be called once.
223      */
isReadyForClickSetup()224     public boolean isReadyForClickSetup() {
225         return !mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
226                 && (mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
227                 || mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID));
228     }
229 
updateDrawableBounds()230     private void updateDrawableBounds() {
231         DeviceProfile grid = mLauncher.getDeviceProfile();
232         int paddingTop = getPaddingTop();
233         int paddingBottom = getPaddingBottom();
234         int paddingLeft = getPaddingLeft();
235         int paddingRight = getPaddingRight();
236 
237         int minPadding = getResources()
238                 .getDimensionPixelSize(R.dimen.pending_widget_min_padding);
239 
240         int availableWidth = getWidth() - paddingLeft - paddingRight - 2 * minPadding;
241         int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
242 
243         if (mSettingIconDrawable == null) {
244             int maxSize = grid.iconSizePx;
245             int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
246 
247             mRect.set(0, 0, size, size);
248             mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
249             mCenterDrawable.setBounds(mRect);
250         } else  {
251             float iconSize = Math.max(0, Math.min(availableWidth, availableHeight));
252 
253             // Use twice the setting size factor, as the setting is drawn at a corner and the
254             // icon is drawn in the center.
255             float settingIconScaleFactor = 1 + SETUP_ICON_SIZE_FACTOR * 2;
256             int maxSize = Math.max(availableWidth, availableHeight);
257             if (iconSize * settingIconScaleFactor > maxSize) {
258                 // There is an overlap
259                 iconSize = maxSize / settingIconScaleFactor;
260             }
261 
262             int actualIconSize = (int) Math.min(iconSize, grid.iconSizePx);
263 
264             // Icon top when we do not draw the text
265             int iconTop = (getHeight() - actualIconSize) / 2;
266             mSetupTextLayout = null;
267 
268             if (availableWidth > 0) {
269                 // Recreate the setup text.
270                 mSetupTextLayout = new StaticLayout(
271                         getResources().getText(R.string.gadget_setup_text), mPaint, availableWidth,
272                         Layout.Alignment.ALIGN_CENTER, 1, 0, true);
273                 int textHeight = mSetupTextLayout.getHeight();
274 
275                 // Extra icon size due to the setting icon
276                 float minHeightWithText = textHeight + actualIconSize * settingIconScaleFactor
277                         + grid.iconDrawablePaddingPx;
278 
279                 if (minHeightWithText < availableHeight) {
280                     // We can draw the text as well
281                     iconTop = (getHeight() - textHeight -
282                             grid.iconDrawablePaddingPx - actualIconSize) / 2;
283 
284                 } else {
285                     // We can't draw the text. Let the iconTop be same as before.
286                     mSetupTextLayout = null;
287                 }
288             }
289 
290             mRect.set(0, 0, actualIconSize, actualIconSize);
291             mRect.offset((getWidth() - actualIconSize) / 2, iconTop);
292             mCenterDrawable.setBounds(mRect);
293 
294             mRect.left = paddingLeft + minPadding;
295             mRect.right = mRect.left + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
296             mRect.top = paddingTop + minPadding;
297             mRect.bottom = mRect.top + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
298             mSettingIconDrawable.setBounds(mRect);
299 
300             if (mSetupTextLayout != null) {
301                 // Set up position for dragging the text
302                 mRect.left = paddingLeft + minPadding;
303                 mRect.top = mCenterDrawable.getBounds().bottom + grid.iconDrawablePaddingPx;
304             }
305         }
306     }
307 
308     @Override
onDraw(Canvas canvas)309     protected void onDraw(Canvas canvas) {
310         if (mCenterDrawable == null) {
311             // Nothing to draw
312             return;
313         }
314 
315         if (mDrawableSizeChanged) {
316             updateDrawableBounds();
317             mDrawableSizeChanged = false;
318         }
319 
320         mCenterDrawable.draw(canvas);
321         if (mSettingIconDrawable != null) {
322             mSettingIconDrawable.draw(canvas);
323         }
324         if (mSetupTextLayout != null) {
325             canvas.save();
326             canvas.translate(mRect.left, mRect.top);
327             mSetupTextLayout.draw(canvas);
328             canvas.restore();
329         }
330 
331     }
332 
333     /**
334      * Returns the widget category icon for {@link #mInfo}.
335      *
336      * <p>If {@link #mInfo}'s category is {@code PackageItemInfo#NO_CATEGORY} or unknown, returns
337      * {@code null}.
338      */
339     @Nullable
getWidgetCategoryIcon()340     private Drawable getWidgetCategoryIcon() {
341         switch (mInfo.pendingItemInfo.category) {
342             case CONVERSATIONS:
343                 return getContext().getDrawable(R.drawable.ic_conversations_widget_category);
344         }
345         return null;
346     }
347 }
348