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