1 package com.android.launcher3.widget;
2 
3 import static com.android.launcher3.Utilities.ATLEAST_S;
4 
5 import android.appwidget.AppWidgetProviderInfo;
6 import android.content.ComponentName;
7 import android.content.Context;
8 import android.content.pm.PackageManager;
9 import android.graphics.Point;
10 import android.graphics.Rect;
11 import android.graphics.drawable.Drawable;
12 import android.os.Parcel;
13 import android.os.UserHandle;
14 
15 import com.android.launcher3.DeviceProfile;
16 import com.android.launcher3.InvariantDeviceProfile;
17 import com.android.launcher3.LauncherAppState;
18 import com.android.launcher3.icons.ComponentWithLabelAndIcon;
19 import com.android.launcher3.icons.IconCache;
20 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
21 
22 /**
23  * This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords
24  * a common object for describing both framework provided AppWidgets as well as custom widgets
25  * (who's implementation is owned by the launcher). This object represents a widget type / class,
26  * as opposed to a widget instance, and so should not be confused with {@link LauncherAppWidgetInfo}
27  */
28 public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo
29         implements ComponentWithLabelAndIcon {
30 
31     public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-";
32 
33     /**
34      * The desired number of cells that this widget occupies horizontally in
35      * {@link com.android.launcher3.CellLayout}.
36      */
37     public int spanX;
38 
39     /**
40      * The desired number of cells that this widget occupies vertically in
41      * {@link com.android.launcher3.CellLayout}.
42      */
43     public int spanY;
44 
45     /**
46      * The minimum number of cells that this widget can occupy horizontally in
47      * {@link com.android.launcher3.CellLayout}.
48      */
49     public int minSpanX;
50 
51     /**
52      * The minimum number of cells that this widget can occupy vertically in
53      * {@link com.android.launcher3.CellLayout}.
54      */
55     public int minSpanY;
56 
57     /**
58      * The maximum number of cells that this widget can occupy horizontally in
59      * {@link com.android.launcher3.CellLayout}.
60      */
61     public int maxSpanX;
62 
63     /**
64      * The maximum number of cells that this widget can occupy vertically in
65      * {@link com.android.launcher3.CellLayout}.
66      */
67     public int maxSpanY;
68 
69     protected boolean mIsMinSizeFulfilled;
70 
fromProviderInfo(Context context, AppWidgetProviderInfo info)71     public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
72             AppWidgetProviderInfo info) {
73         final LauncherAppWidgetProviderInfo launcherInfo;
74         if (info instanceof LauncherAppWidgetProviderInfo) {
75             launcherInfo = (LauncherAppWidgetProviderInfo) info;
76         } else {
77 
78             // In lieu of a public super copy constructor, we first write the AppWidgetProviderInfo
79             // into a parcel, and then construct a new LauncherAppWidgetProvider info from the
80             // associated super parcel constructor. This allows us to copy non-public members without
81             // using reflection.
82             Parcel p = Parcel.obtain();
83             info.writeToParcel(p, 0);
84             p.setDataPosition(0);
85             launcherInfo = new LauncherAppWidgetProviderInfo(p);
86             p.recycle();
87         }
88         launcherInfo.initSpans(context, LauncherAppState.getIDP(context));
89         return launcherInfo;
90     }
91 
LauncherAppWidgetProviderInfo()92     protected LauncherAppWidgetProviderInfo() {}
93 
LauncherAppWidgetProviderInfo(Parcel in)94     protected LauncherAppWidgetProviderInfo(Parcel in) {
95         super(in);
96     }
97 
initSpans(Context context, InvariantDeviceProfile idp)98     public void initSpans(Context context, InvariantDeviceProfile idp) {
99         int minSpanX = 0;
100         int minSpanY = 0;
101         int maxSpanX = idp.numColumns;
102         int maxSpanY = idp.numRows;
103         int spanX = 0;
104         int spanY = 0;
105 
106 
107         Point cellSize = new Point();
108         for (DeviceProfile dp : idp.supportedProfiles) {
109             dp.getCellSize(cellSize);
110             Rect widgetPadding = dp.widgetPadding;
111 
112             minSpanX = Math.max(minSpanX,
113                     getSpanX(widgetPadding, minResizeWidth, dp.cellLayoutBorderSpacePx.x,
114                             cellSize.x));
115             minSpanY = Math.max(minSpanY,
116                     getSpanY(widgetPadding, minResizeHeight, dp.cellLayoutBorderSpacePx.y,
117                             cellSize.y));
118 
119             if (ATLEAST_S) {
120                 if (maxResizeWidth > 0) {
121                     maxSpanX = Math.min(maxSpanX, getSpanX(widgetPadding, maxResizeWidth,
122                             dp.cellLayoutBorderSpacePx.x, cellSize.x));
123                 }
124                 if (maxResizeHeight > 0) {
125                     maxSpanY = Math.min(maxSpanY, getSpanY(widgetPadding, maxResizeHeight,
126                             dp.cellLayoutBorderSpacePx.y, cellSize.y));
127                 }
128             }
129 
130             spanX = Math.max(spanX,
131                     getSpanX(widgetPadding, minWidth, dp.cellLayoutBorderSpacePx.x,
132                             cellSize.x));
133             spanY = Math.max(spanY,
134                     getSpanY(widgetPadding, minHeight, dp.cellLayoutBorderSpacePx.y,
135                             cellSize.y));
136         }
137 
138         if (ATLEAST_S) {
139             // Ensures maxSpan >= minSpan
140             maxSpanX = Math.max(maxSpanX, minSpanX);
141             maxSpanY = Math.max(maxSpanY, minSpanY);
142 
143             // Use targetCellWidth/Height if it is within the min/max ranges.
144             // Otherwise, use the span of minWidth/Height.
145             if (targetCellWidth >= minSpanX && targetCellWidth <= maxSpanX
146                     && targetCellHeight >= minSpanY && targetCellHeight <= maxSpanY) {
147                 spanX = targetCellWidth;
148                 spanY = targetCellHeight;
149             }
150         }
151 
152         // If minSpanX/Y > spanX/Y, ignore the minSpanX/Y to match the behavior described in
153         // minResizeWidth & minResizeHeight Android documentation. See
154         // https://developer.android.com/reference/android/appwidget/AppWidgetProviderInfo
155         this.minSpanX = Math.min(spanX, minSpanX);
156         this.minSpanY = Math.min(spanY, minSpanY);
157         this.maxSpanX = maxSpanX;
158         this.maxSpanY = maxSpanY;
159         this.mIsMinSizeFulfilled = Math.min(spanX, minSpanX) <= idp.numColumns
160             && Math.min(spanY, minSpanY) <= idp.numRows;
161         // Ensures the default span X and span Y will not exceed the current grid size.
162         this.spanX = Math.min(spanX, idp.numColumns);
163         this.spanY = Math.min(spanY, idp.numRows);
164     }
165 
166     /**
167      * Returns {@code true} if the widget's minimum size requirement can be fulfilled in the device
168      * grid setting, {@link InvariantDeviceProfile}, that was passed in
169      * {@link #initSpans(Context, InvariantDeviceProfile)}.
170      */
isMinSizeFulfilled()171     public boolean isMinSizeFulfilled() {
172         return mIsMinSizeFulfilled;
173     }
174 
getSpanX(Rect widgetPadding, int widgetWidth, int cellSpacing, float cellWidth)175     private int getSpanX(Rect widgetPadding, int widgetWidth, int cellSpacing, float cellWidth) {
176         return getSpan(widgetPadding.left + widgetPadding.right,
177                 widgetWidth, cellSpacing, cellWidth);
178     }
179 
getSpanY(Rect widgetPadding, int widgetHeight, int cellSpacing, float cellHeight)180     private int getSpanY(Rect widgetPadding, int widgetHeight, int cellSpacing, float cellHeight) {
181         return getSpan(widgetPadding.top + widgetPadding.bottom, widgetHeight,
182                 cellSpacing, cellHeight);
183     }
184 
185     /**
186      * Solving the equation:
187      *   n * cellSize + (n - 1) * cellSpacing - widgetPadding = widgetSize
188      */
getSpan(int widgetPadding, int widgetSize, int cellSpacing, float cellSize)189     private int getSpan(int widgetPadding, int widgetSize, int cellSpacing, float cellSize) {
190         return Math.max(1, (int) Math.ceil(
191                 (widgetSize + widgetPadding + cellSpacing) / (cellSize + cellSpacing)));
192     }
193 
getLabel(PackageManager packageManager)194     public String getLabel(PackageManager packageManager) {
195         return super.loadLabel(packageManager);
196     }
197 
getMinSpans()198     public Point getMinSpans() {
199         return new Point((resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1,
200                 (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1);
201     }
202 
isCustomWidget()203     public boolean isCustomWidget() {
204         return provider.getClassName().startsWith(CLS_CUSTOM_WIDGET_PREFIX);
205     }
206 
getWidgetFeatures()207     public int getWidgetFeatures() {
208         return widgetFeatures;
209     }
210 
isReconfigurable()211     public boolean isReconfigurable() {
212         return configure != null && (getWidgetFeatures() & WIDGET_FEATURE_RECONFIGURABLE) != 0;
213     }
214 
isConfigurationOptional()215     public boolean isConfigurationOptional() {
216         return ATLEAST_S
217                 && isReconfigurable()
218                 && (getWidgetFeatures() & WIDGET_FEATURE_CONFIGURATION_OPTIONAL) != 0;
219     }
220 
221     @Override
getComponent()222     public final ComponentName getComponent() {
223         return provider;
224     }
225 
226     @Override
getUser()227     public final UserHandle getUser() {
228         return getProfile();
229     }
230 
231     @Override
getFullResIcon(IconCache cache)232     public Drawable getFullResIcon(IconCache cache) {
233         return cache.getFullResIcon(provider.getPackageName(), icon);
234     }
235 }