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.graphics.Bitmap; 21 import android.os.CancellationSignal; 22 import android.util.AttributeSet; 23 import android.util.Log; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.view.View.OnLayoutChangeListener; 27 import android.view.ViewGroup; 28 import android.view.ViewPropertyAnimator; 29 import android.view.accessibility.AccessibilityNodeInfo; 30 import android.widget.LinearLayout; 31 import android.widget.TextView; 32 33 import com.android.launcher3.BaseActivity; 34 import com.android.launcher3.DeviceProfile; 35 import com.android.launcher3.R; 36 import com.android.launcher3.SimpleOnStylusPressListener; 37 import com.android.launcher3.StylusEventHelper; 38 import com.android.launcher3.WidgetPreviewLoader; 39 import com.android.launcher3.graphics.DrawableFactory; 40 import com.android.launcher3.model.WidgetItem; 41 42 /** 43 * Represents the individual cell of the widget inside the widget tray. The preview is drawn 44 * horizontally centered, and scaled down if needed. 45 * 46 * This view does not support padding. Since the image is scaled down to fit the view, padding will 47 * further decrease the scaling factor. Drag-n-drop uses the view bounds for showing a smooth 48 * transition from the view to drag view, so when adding padding support, DnD would need to 49 * consider the appropriate scaling factor. 50 */ 51 public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { 52 53 private static final String TAG = "WidgetCell"; 54 private static final boolean DEBUG = false; 55 56 private static final int FADE_IN_DURATION_MS = 90; 57 58 /** Widget cell width is calculated by multiplying this factor to grid cell width. */ 59 private static final float WIDTH_SCALE = 2.6f; 60 61 /** Widget preview width is calculated by multiplying this factor to the widget cell width. */ 62 private static final float PREVIEW_SCALE = 0.8f; 63 64 protected int mPresetPreviewSize; 65 private int mCellSize; 66 67 private WidgetImageView mWidgetImage; 68 private TextView mWidgetName; 69 private TextView mWidgetDims; 70 71 protected WidgetItem mItem; 72 73 private WidgetPreviewLoader mWidgetPreviewLoader; 74 private StylusEventHelper mStylusEventHelper; 75 76 protected CancellationSignal mActiveRequest; 77 private boolean mAnimatePreview = true; 78 79 private boolean mApplyBitmapDeferred = false; 80 private Bitmap mDeferredBitmap; 81 82 protected final BaseActivity mActivity; 83 WidgetCell(Context context)84 public WidgetCell(Context context) { 85 this(context, null); 86 } 87 WidgetCell(Context context, AttributeSet attrs)88 public WidgetCell(Context context, AttributeSet attrs) { 89 this(context, attrs, 0); 90 } 91 WidgetCell(Context context, AttributeSet attrs, int defStyle)92 public WidgetCell(Context context, AttributeSet attrs, int defStyle) { 93 super(context, attrs, defStyle); 94 95 mActivity = BaseActivity.fromContext(context); 96 mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); 97 98 setContainerWidth(); 99 setWillNotDraw(false); 100 setClipToPadding(false); 101 setAccessibilityDelegate(mActivity.getAccessibilityDelegate()); 102 } 103 setContainerWidth()104 private void setContainerWidth() { 105 DeviceProfile profile = mActivity.getDeviceProfile(); 106 mCellSize = (int) (profile.cellWidthPx * WIDTH_SCALE); 107 mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE); 108 } 109 110 @Override onFinishInflate()111 protected void onFinishInflate() { 112 super.onFinishInflate(); 113 114 mWidgetImage = (WidgetImageView) findViewById(R.id.widget_preview); 115 mWidgetName = ((TextView) findViewById(R.id.widget_name)); 116 mWidgetDims = ((TextView) findViewById(R.id.widget_dims)); 117 } 118 119 /** 120 * Called to clear the view and free attached resources. (e.g., {@link Bitmap} 121 */ clear()122 public void clear() { 123 if (DEBUG) { 124 Log.d(TAG, "reset called on:" + mWidgetName.getText()); 125 } 126 mWidgetImage.animate().cancel(); 127 mWidgetImage.setBitmap(null, null); 128 mWidgetName.setText(null); 129 mWidgetDims.setText(null); 130 131 if (mActiveRequest != null) { 132 mActiveRequest.cancel(); 133 mActiveRequest = null; 134 } 135 } 136 applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader)137 public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) { 138 mItem = item; 139 mWidgetName.setText(mItem.label); 140 mWidgetDims.setText(getContext().getString(R.string.widget_dims_format, 141 mItem.spanX, mItem.spanY)); 142 mWidgetDims.setContentDescription(getContext().getString( 143 R.string.widget_accessible_dims_format, mItem.spanX, mItem.spanY)); 144 mWidgetPreviewLoader = loader; 145 146 if (item.activityInfo != null) { 147 setTag(new PendingAddShortcutInfo(item.activityInfo)); 148 } else { 149 setTag(new PendingAddWidgetInfo(item.widgetInfo)); 150 } 151 } 152 getWidgetView()153 public WidgetImageView getWidgetView() { 154 return mWidgetImage; 155 } 156 157 /** 158 * Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but 159 * will not cause invalidate, so that when deferring is disabled later, all the bitmaps are 160 * ready. 161 * This prevents invalidates while the animation is running. 162 */ setApplyBitmapDeferred(boolean isDeferred)163 public void setApplyBitmapDeferred(boolean isDeferred) { 164 if (mApplyBitmapDeferred != isDeferred) { 165 mApplyBitmapDeferred = isDeferred; 166 if (!mApplyBitmapDeferred && mDeferredBitmap != null) { 167 applyPreview(mDeferredBitmap); 168 mDeferredBitmap = null; 169 } 170 } 171 } 172 setAnimatePreview(boolean shouldAnimate)173 public void setAnimatePreview(boolean shouldAnimate) { 174 mAnimatePreview = shouldAnimate; 175 } 176 applyPreview(Bitmap bitmap)177 public void applyPreview(Bitmap bitmap) { 178 if (mApplyBitmapDeferred) { 179 mDeferredBitmap = bitmap; 180 return; 181 } 182 if (bitmap != null) { 183 mWidgetImage.setBitmap(bitmap, DrawableFactory.INSTANCE.get(getContext()) 184 .getBadgeForUser(mItem.user, getContext())); 185 if (mAnimatePreview) { 186 mWidgetImage.setAlpha(0f); 187 ViewPropertyAnimator anim = mWidgetImage.animate(); 188 anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS); 189 } else { 190 mWidgetImage.setAlpha(1f); 191 } 192 } 193 } 194 ensurePreview()195 public void ensurePreview() { 196 if (mActiveRequest != null) { 197 return; 198 } 199 mActiveRequest = mWidgetPreviewLoader.getPreview( 200 mItem, mPresetPreviewSize, mPresetPreviewSize, this); 201 } 202 203 @Override onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)204 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 205 int oldTop, int oldRight, int oldBottom) { 206 removeOnLayoutChangeListener(this); 207 ensurePreview(); 208 } 209 210 @Override onTouchEvent(MotionEvent ev)211 public boolean onTouchEvent(MotionEvent ev) { 212 boolean handled = super.onTouchEvent(ev); 213 if (mStylusEventHelper.onMotionEvent(ev)) { 214 return true; 215 } 216 return handled; 217 } 218 219 /** 220 * Helper method to get the string info of the tag. 221 */ getTagToString()222 private String getTagToString() { 223 if (getTag() instanceof PendingAddWidgetInfo || 224 getTag() instanceof PendingAddShortcutInfo) { 225 return getTag().toString(); 226 } 227 return ""; 228 } 229 230 @Override setLayoutParams(ViewGroup.LayoutParams params)231 public void setLayoutParams(ViewGroup.LayoutParams params) { 232 params.width = params.height = mCellSize; 233 super.setLayoutParams(params); 234 } 235 236 @Override getAccessibilityClassName()237 public CharSequence getAccessibilityClassName() { 238 return WidgetCell.class.getName(); 239 } 240 241 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)242 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 243 super.onInitializeAccessibilityNodeInfo(info); 244 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); 245 } 246 } 247