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