1 /* 2 * Copyright (C) 2015 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 android.content.Context; 20 import android.content.pm.PackageManager; 21 import android.content.pm.ResolveInfo; 22 import android.content.res.Resources; 23 import android.graphics.Bitmap; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.view.MotionEvent; 27 import android.view.View; 28 import android.view.View.OnLayoutChangeListener; 29 import android.view.ViewPropertyAnimator; 30 import android.widget.LinearLayout; 31 import android.widget.TextView; 32 33 import com.android.launcher3.DeviceProfile; 34 import com.android.launcher3.InvariantDeviceProfile; 35 import com.android.launcher3.ItemInfo; 36 import com.android.launcher3.Launcher; 37 import com.android.launcher3.LauncherAppState; 38 import com.android.launcher3.LauncherAppWidgetProviderInfo; 39 import com.android.launcher3.R; 40 import com.android.launcher3.StylusEventHelper; 41 import com.android.launcher3.WidgetPreviewLoader; 42 import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest; 43 import com.android.launcher3.compat.AppWidgetManagerCompat; 44 45 /** 46 * Represents the individual cell of the widget inside the widget tray. The preview is drawn 47 * horizontally centered, and scaled down if needed. 48 * 49 * This view does not support padding. Since the image is scaled down to fit the view, padding will 50 * further decrease the scaling factor. Drag-n-drop uses the view bounds for showing a smooth 51 * transition from the view to drag view, so when adding padding support, DnD would need to 52 * consider the appropriate scaling factor. 53 */ 54 public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { 55 56 private static final String TAG = "WidgetCell"; 57 private static final boolean DEBUG = false; 58 59 private static final int FADE_IN_DURATION_MS = 90; 60 61 /** Widget cell width is calculated by multiplying this factor to grid cell width. */ 62 private static final float WIDTH_SCALE = 2.6f; 63 64 /** Widget preview width is calculated by multiplying this factor to the widget cell width. */ 65 private static final float PREVIEW_SCALE = 0.8f; 66 67 private int mPresetPreviewSize; 68 int cellSize; 69 70 private WidgetImageView mWidgetImage; 71 private TextView mWidgetName; 72 private TextView mWidgetDims; 73 74 private String mDimensionsFormatString; 75 private Object mInfo; 76 77 private WidgetPreviewLoader mWidgetPreviewLoader; 78 private PreviewLoadRequest mActiveRequest; 79 private StylusEventHelper mStylusEventHelper; 80 81 private Launcher mLauncher; 82 83 public WidgetCell(Context context) { 84 this(context, null); 85 } 86 87 public WidgetCell(Context context, AttributeSet attrs) { 88 this(context, attrs, 0); 89 } 90 91 public WidgetCell(Context context, AttributeSet attrs, int defStyle) { 92 super(context, attrs, defStyle); 93 94 final Resources r = context.getResources(); 95 mLauncher = (Launcher) context; 96 mStylusEventHelper = new StylusEventHelper(this); 97 98 mDimensionsFormatString = r.getString(R.string.widget_dims_format); 99 setContainerWidth(); 100 setWillNotDraw(false); 101 setClipToPadding(false); 102 setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); 103 } 104 105 private void setContainerWidth() { 106 DeviceProfile profile = mLauncher.getDeviceProfile(); 107 cellSize = (int) (profile.cellWidthPx * WIDTH_SCALE); 108 mPresetPreviewSize = (int) (cellSize * PREVIEW_SCALE); 109 } 110 111 @Override 112 protected void onFinishInflate() { 113 super.onFinishInflate(); 114 115 mWidgetImage = (WidgetImageView) findViewById(R.id.widget_preview); 116 mWidgetName = ((TextView) findViewById(R.id.widget_name)); 117 mWidgetDims = ((TextView) findViewById(R.id.widget_dims)); 118 } 119 120 /** 121 * Called to clear the view and free attached resources. (e.g., {@link Bitmap} 122 */ 123 public void clear() { 124 if (DEBUG) { 125 Log.d(TAG, "reset called on:" + mWidgetName.getText()); 126 } 127 mWidgetImage.animate().cancel(); 128 mWidgetImage.setBitmap(null); 129 mWidgetName.setText(null); 130 mWidgetDims.setText(null); 131 132 if (mActiveRequest != null) { 133 mActiveRequest.cleanup(); 134 mActiveRequest = null; 135 } 136 } 137 138 /** 139 * Apply the widget provider info to the view. 140 */ 141 public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info, 142 WidgetPreviewLoader loader) { 143 144 InvariantDeviceProfile profile = 145 LauncherAppState.getInstance().getInvariantDeviceProfile(); 146 mInfo = info; 147 // TODO(hyunyoungs): setup a cache for these labels. 148 mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info)); 149 int hSpan = Math.min(info.spanX, profile.numColumns); 150 int vSpan = Math.min(info.spanY, profile.numRows); 151 mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan)); 152 mWidgetPreviewLoader = loader; 153 } 154 155 /** 156 * Apply the resolve info to the view. 157 */ 158 public void applyFromResolveInfo( 159 PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) { 160 mInfo = info; 161 CharSequence label = info.loadLabel(pm); 162 mWidgetName.setText(label); 163 mWidgetDims.setText(String.format(mDimensionsFormatString, 1, 1)); 164 mWidgetPreviewLoader = loader; 165 } 166 167 public int[] getPreviewSize() { 168 int[] maxSize = new int[2]; 169 170 maxSize[0] = mPresetPreviewSize; 171 maxSize[1] = mPresetPreviewSize; 172 return maxSize; 173 } 174 175 public void applyPreview(Bitmap bitmap) { 176 if (bitmap != null) { 177 mWidgetImage.setBitmap(bitmap); 178 mWidgetImage.setAlpha(0f); 179 ViewPropertyAnimator anim = mWidgetImage.animate(); 180 anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS); 181 } 182 } 183 184 public void ensurePreview() { 185 if (mActiveRequest != null) { 186 return; 187 } 188 int[] size = getPreviewSize(); 189 if (DEBUG) { 190 Log.d(TAG, String.format("[tag=%s] ensurePreview (%d, %d):", 191 getTagToString(), size[0], size[1])); 192 } 193 mActiveRequest = mWidgetPreviewLoader.getPreview(mInfo, size[0], size[1], this); 194 } 195 196 @Override 197 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 198 int oldTop, int oldRight, int oldBottom) { 199 removeOnLayoutChangeListener(this); 200 ensurePreview(); 201 } 202 203 public int getActualItemWidth() { 204 ItemInfo info = (ItemInfo) getTag(); 205 int[] size = getPreviewSize(); 206 int cellWidth = mLauncher.getDeviceProfile().cellWidthPx; 207 208 return Math.min(size[0], info.spanX * cellWidth); 209 } 210 211 @Override 212 public boolean onTouchEvent(MotionEvent ev) { 213 boolean handled = super.onTouchEvent(ev); 214 if (mStylusEventHelper.checkAndPerformStylusEvent(ev)) { 215 return true; 216 } 217 return handled; 218 } 219 220 /** 221 * Helper method to get the string info of the tag. 222 */ 223 private String getTagToString() { 224 if (getTag() instanceof PendingAddWidgetInfo || 225 getTag() instanceof PendingAddShortcutInfo) { 226 return getTag().toString(); 227 } 228 return ""; 229 } 230 } 231